厦门市住房和建设局网站,在线图片编辑工具,做网站维护需要懂什么,百度网页推广类型的 GUID#xff08;全局唯一标识符#xff09; 是在 COM 编程#xff08;Component Object Model#xff09; 和某些大型 C 架构#xff08;如 Office、DirectX、跨 DLL 接口#xff09;中关联类型信息和实现运行时类型识别与动态接口查询的重要机制。 下面我们分层解…类型的 GUID全局唯一标识符 是在 COM 编程Component Object Model 和某些大型 C 架构如 Office、DirectX、跨 DLL 接口中关联类型信息和实现运行时类型识别与动态接口查询的重要机制。 下面我们分层解释——
类型的 GUID 是什么
GUIDGlobally Unique Identifier128 位的唯一标识用来唯一标识某种类型接口/类。 比如
__declspec(uuid(4D675322-F6F5-4E85-94EF-2927DFAA1409))
struct IWorkerCallback : IUnknown { ... };这个 GUID 表示IWorkerCallback 类型的唯一身份标识是这个字符串值。
GUID 的用途为什么类型需要它
1. COM 接口查询核心用途
COM 是基于接口的架构组件之间只能通过接口通信。你不能像 C 的 dynamic_cast 那样直接转换接口指针所以需要
HRESULT QueryInterface(const IID iid, void** out);你必须提供要“转换到的接口”的 IID也就是接口的 GUID比如
pUnknown-QueryInterface(__uuidof(IMyInterface), (void**)pMyInterface);这背后的意思是我想知道你是否实现了 IMyInterface请给我这个接口指针如果有的话。
2. 替代 RTTI无需开启 -frtti
有些大型项目如 Office**禁用了 RTTI运行时类型信息**以节省空间。通过 GUID我们就能在运行时识别类型、接口并动态转换而不用启用 C 的原生 typeid 或 dynamic_cast。
3. 跨 DLL 类型识别
在 Windows DLL 边界上类型信息是不能直接共享的但 GUID 可以。于是
DLL A 定义了 IFoo。DLL B 想和它通信只要知道 IFoo 的 GUID 就行了无需包含 IFoo 的完整实现。
4. 插件机制和反射支持
如果你要构建
跨平台插件框架脚本调用如 Lua → C元数据反射识别支持的功能 你也可以使用类型 GUID 来注册所有类型用 GUID 映射构造函数、工厂函数、接口表甚至用于序列化/反序列化type-safe
不是 C 标准的机制
GUID 和 __uuidof() 不是 C 标准提供的功能而是
Microsoft Visual C 提供的扩展__declspec(uuid)Clang/GCC 等可以通过宏和模板模拟如你前面看到的 STRUCT_GUID
举个实际例子
IUnknown* p ...;
IFoo* foo nullptr;
if (SUCCEEDED(p-QueryInterface(__uuidof(IFoo), (void**)foo))) {foo-DoSomething();
}没有 GUID你就无法调用 QueryInterface()类型系统没法知道“你到底想要哪个接口”。
总结一句话 类型的 GUID 是类型在运行时的身份证它是 COM 等架构中实现接口查询QueryInterface、替代 RTTI、支持插件与 DLL 通信的核心机制。 总结一下为什么需要类型的 GUID
GUID全局唯一标识符用于标识接口类型特别是在 COM组件对象模型编程中比如 Microsoft Office 里的代码。通过 IUnknown::QueryInterface 方法可以根据 GUID 实现接口的动态查询和类型转换类似于动态类型转换但不依赖于 C 的 RTTI。Office 里不使用 C 标准的 RTTI而是用 GUID 来保证跨模块、跨语言的一致性和安全性。Visual C 提供了对 GUID 的内置支持通过 __declspec(uuid(...)) 来声明 GUID通过 __uuidof(Type) 获取类型对应的 GUID。C 标准并没有内建对 GUID 的支持所以这是 Microsoft 平台特有的扩展。
这段代码展示了一个跨平台特别是支持 MSVC 和 Clang 编译器对 GUID 与接口类型关联的典型写法核心点如下
__declspec(uuid(...)) 是 MSVC 特有的语法用于给接口如 IWorkerCallback附加 GUID。在 MSVC 下__uuidof(type) 可以直接获取这个 GUID。Clang 编译器不支持 __declspec(uuid) 的模板特化所以采用模板特化结构体 guid_oftype 来手动绑定 GUID。通过宏 #define __uuidof(type) guid_oftype::value无论在哪个编译器环境都能统一用 __uuidof(type) 方式获取 GUID。这样写能实现跨编译器一致访问 GUID同时保证代码跨平台兼容。
总结一下这个理想方案的问题点
[uuid(“…”)] 标注虽然写起来直观简洁但它是 微软专有扩展不属于标准 C只能在 Visual C 使用其实现会在对象实例中增加额外指针导致对象体积变大这对性能敏感或跨平台项目是不友好的。 所以虽然它“看上去理想”但实际用时要慎重最好还是用兼容性更好、对实例大小无影响的传统 __declspec(uuid(...)) 模板特化方案。
总结一下这个用宏简化 GUID 绑定的关键点
宏写成 STRUCT_GUID(IWorkerCallback, 4D675322-F6F5-4E85-94EF-2927DFAA1409) 形式 宏只负责绑定 GUID不包含 struct 或 class 关键字 这样更利于 IDE 和工具解析代码语法高亮、跳转等。struct 或 class 关键字放在宏外写方便代码风格统一和工具支持。注意宏绑定的 GUID 必须和类型的声明匹配比如不能宏里用 class类型定义里用 struct否则会产生 Visual C 的 Level 1 警告。保证这点对 Visual C 的 ABI 稳定性很重要避免潜在兼容性问题。
How can we implement the STRUCT_GUID macro?
这段代码意思是
#define STRUCT_GUID(type, guidString) \struct __declspec(uuid(guidString)) type;这个宏利用 Visual C 的 __declspec(uuid(...)) 特性直接把 GUID 绑定到 struct 上。宏只负责给类型添加 GUID 属性不管是定义、前置声明还是重新声明都可以使用这个宏。实现非常简单明了使用方便。 比如
STRUCT_GUID(IWorkerCallback, 4D675322-F6F5-4E85-94EF-2927DFAA1409)
struct IWorkerCallback : IUnknown {virtual void Invoke(IWorkerObject* pObj) 0;
};这样IWorkerCallback 类型就绑定了对应的 GUIDVC 编译器会自动生成相关信息。
这段描述是 Clang 下跨平台实现 GUID 关联的思路主要点如下
核心思想
不像 VC 直接用 __declspec(uuid(...))Clang 没有类似的内建支持需要用模板和函数来“模拟” GUID 关联。定义一个模板结构体 guid_ofT它有一个 static constexpr GUID value存储对应类型的 GUID。__uuidof(type) 宏映射到 guid_oftype::value这样用起来语义一致。
关键实现细节
// 默认模板调用 get_guid 函数
templatetypename T
struct guid_of {static constexpr GUID value get_guid(static_castT*(nullptr));
};
// 默认 get_guid 函数实现static_assert 防止未特化情况导致使用错误
templatetypename T
constexpr GUID get_guid(T*) {static_assert(sizeof(T) 0, GUID not defined for this type!);
}
// 用宏定义类型的 GUID宏展开成 get_guid 函数的特化版本
#define STRUCT_GUID(type, guidString) \struct type; \extern C constexpr GUID get_guid(type*) noexcept { \return str_to_guid(guidString); \}STRUCT_GUID(type, guidString) 会先声明一个 struct type再定义一个针对该类型指针的 get_guid 函数特化返回对应的 GUID。通过 str_to_guid 将字符串形式的 GUID 转换为 GUID 结构。当你调用 __uuidof(type) 时实际是访问 guid_oftype::value编译器会调用对应的 get_guid(type*)返回正确的 GUID。如果没有使用 STRUCT_GUID 定义的类型调用 __uuidof 会触发 static_assert提示没有定义 GUID。
优点
支持 Clang 以及非 VC 编译器。类型安全调用时若未定义 GUID 会报错。不改变结构体大小或内存布局。
你提供的这段代码是对 Clang 下如何在 C 中实现编译期 GUID全局唯一标识符解析 的继续部分。下面是对这段实现的完整讲解
/// 表示一个不带花括号的 GUID 字符串格式为 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
typedef char GuidString[37];
/// 把十六进制的 ASCII 字符转换为无符号整数值0–15
/// H2U[0] 0, H2U[9] 9, H2U[A] 10, H2U[F] 15, H2U[a] 10, H2U[f] 15
const unsigned char H2U[256] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
/// 将格式为 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX 的字符串在编译期转换为 GUID 结构
constexpr GUID str_to_guid(const GuidString g) noexcept {return {// 第1段8位十六进制→ unsigned longstatic_castunsigned long((H2U[g[0]] 28) | (H2U[g[1]] 24) | (H2U[g[2]] 20) |(H2U[g[3]] 16) | (H2U[g[4]] 12) | (H2U[g[5]] 8) |(H2U[g[6]] 4) | H2U[g[7]]),// 第2段4位十六进制→ unsigned shortstatic_castunsigned short((H2U[g[9]] 12) | (H2U[g[10]] 8) |(H2U[g[11]] 4) | H2U[g[12]]),// 第3段4位十六进制→ unsigned shortstatic_castunsigned short((H2U[g[14]] 12) | (H2U[g[15]] 8) |(H2U[g[16]] 4) | H2U[g[17]]),// 第4和第5段4位 12位十六进制→ 8字节 unsigned char[8]{static_castunsigned char((H2U[g[19]] 4) | H2U[g[20]]),static_castunsigned char((H2U[g[21]] 4) | H2U[g[22]]),static_castunsigned char((H2U[g[24]] 4) | H2U[g[25]]),static_castunsigned char((H2U[g[26]] 4) | H2U[g[27]]),static_castunsigned char((H2U[g[28]] 4) | H2U[g[29]]),static_castunsigned char((H2U[g[30]] 4) | H2U[g[31]]),static_castunsigned char((H2U[g[32]] 4) | H2U[g[33]]),static_castunsigned char((H2U[g[34]] 4) | H2U[g[35]])}};
}目的
Clang 不支持 __declspec(uuid(...))所以我们自己构建一种方式在 编译期将字符串形式的 GUID 解析为结构体形式即 GUID{...}以便类型绑定使用。
关键元素解释
1. GuidString 类型定义
typedef char GuidString[37];用于表示标准 GUID 格式的字符串 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX共 36 个字符 终止符。格式示例4D675322-F6F5-4E85-94EF-2927DFAA1409
2. H2U 十六进制字符转换表
const unsigned char H2U[256] { ... };将 0–9、A–F、a–f 转换为 0–15。例如H2U[A] 10H2U[f] 15其它值都默认为 0避免非 hex 字符崩溃。
3. str_to_guid将字符串转为 GUID
constexpr GUID str_to_guid(const GuidString g) noexcept工作流程按 GUID 各字段分段解释
// DWORD Data1 8 hex digits: g[0] - g[7]
(H2U[g[0]] 28) | (H2U[g[1]] 24) | ... | H2U[g[7]]
// WORD Data2 4 hex digits: g[9] - g[12]
(H2U[g[9]] 12) | ... | H2U[g[12]]
// WORD Data3 4 hex digits: g[14] - g[17]
(H2U[g[14]] 12) | ... | H2U[g[17]]
// BYTE Data4[8] 16 hex digits: g[19]–g[36]
(g[19], g[20]), (g[21], g[22]), ... (g[34], g[35])每一对十六进制字符被解析为一个字节。中间的 - 符号被跳过g[8], g[13], g[18], g[23] 是 -。因此整段是一个无分支、常量求值的 GUID 解析器
总结理解
这段代码的目的是为了让你能用字符串定义 GUID但 在编译期就能将字符串转化为真实的 GUID 对象。配合前面提到的 STRUCT_GUID 和 get_guid() 机制就可以对任意类型 T 实现 __uuidof(T) 的能力跨平台兼容。这种实现方式 既不依赖 RTTI也不增加对象大小且可静态验证和优化。
这段内容是 Clang 下使用 __uuidof() 和 GUID 的编译期模拟实现方案的延续解释了 str_to_guid() 机制的工作方式、潜在限制和一些高级用法。下面是逐条解释
理解要点
str_to_guid() 是 constexpr
constexpr GUID str_to_guid(const GuidString g) noexcept;这是一个编译期函数所以只要传入的是字符串常量就能在编译时生成 GUID 实例。可用于 constexpr GUID value str_to_guid(...)不会有运行时代价。
get_guid() 依赖于 ADLArgument-Dependent Lookup
templatetypename T
constexpr GUID get_guid(T*);它通过 实参依赖查找 来解析 get_guid()。所以这个函数必须定义在和类型 T 同一个命名空间下。否则 get_guid(T*) 找不到合适的定义触发 static_assert() 编译错误这个是特意设计的安全机制。
若类型未定义 GUID会报错
templatetypename T
constexpr GUID get_guid(T*) {static_assert(false, Type has no GUID.);
}这是故意设计的“fail early”机制。目的是让开发者在使用 __uuidof(T) 时必须先注册 GUID否则就失败。
限制和已知问题
仅适用于 C11
这套方案依赖 C11 的 constexpr 和模板机制。不兼容更早的标准如 C03。
NDK 链接器问题在 Android 开发中 NDK linker error when __uuidof() is used as a template parameter 具体问题
templatetypename C, const IID* piid __uuidof(C) class QIPtr;在某些平台如 Android NDK__uuidof() 的地址值不能用于模板非类型参数。原因链接器无法静态推导 __uuidof(C) 的地址。
替代方案使用 resolve_guid_ptrC, piid::guid
解决方法
templatetypename C, const IID* piid nullptr
class QIPtr {static constexpr const GUID* guid resolve_guid_ptrC, piid::guid;
};提供一个 resolve_guid_ptr 模板 如果显式提供 piid就用它。如果为 nullptr就自动使用 __uuidof(C)。 这个模式使用了 偏特化/特化技巧 来绕开链接器问题。
总结
这套 Clang 下的 GUID 实现机制的关键点是
特性说明str_to_guid()编译期解析 GUID 字符串为结构体。get_guid() ADL自动查找对应类型的 GUID。如果没有定义则编译失败。Clang 支持跨平台 __uuidof通过 #define __uuidof(type) guid_oftype::value 实现替代。NDK 问题的解决使用 resolve_guid_ptr 延迟和间接获取 GUID 地址绕过链接器限制。
对整套跨平台 GUID 关联机制的总结性说明下面是它的要点解析和中文理解
总结理解
STRUCT_GUID 的作用 STRUCT_GUID 允许在多个平台如 VC 和 Clang上将字符串形式的 GUID 与类型关联。 在 Visual C 下使用 __declspec(uuid(...))。在 Clang 下通过 get_guid() 和 str_to_guid() 编译期计算 GUID。它本质上是一个跨平台的类型→GUID映射工具。
保持与旧代码兼容 可以继续使用 __uuidof()。 __uuidof(T) 在 Visual C 是内建的。在 Clang 中通过 #define __uuidof(type) guid_oftype::value 来兼容。所以老代码无需修改新的平台仍然能运行。
这个技巧还能用于其他自定义类型属性
不局限于 GUID你还可以用类似方式为类型添加其他属性如
类型标签tag分类信息traits序列化 ID 等等
如何实现一个通用机制
定义一个宏比如 STRUCT_GUID来注入属性信息。宏会展开成一个 constexpr 函数返回这个属性值。提供一个访问接口类或模板如 guid_ofT::value。提供一个默认模板函数当类型未定义该属性时触发 static_assert或返回默认值。
中文总结一句话 使用 STRUCT_GUID 技术我们可以为类型编译期地绑定 GUID 或其他自定义属性支持多平台编译器如 VC、Clang且对旧代码兼容良好。这个机制也能扩展到任意类型属性的静态绑定是一种强大且通用的元编程手法。