当前位置: 首页 > java >正文

CppCon 2014 学习:Cross platform GUID association with types

类型的 GUID(全局唯一标识符) 是在 COM 编程(Component Object Model) 和某些大型 C++ 架构(如 Office、DirectX、跨 DLL 接口)中关联类型信息实现运行时类型识别与动态接口查询的重要机制。
下面我们分层解释——

类型的 GUID 是什么?

GUID(Globally Unique Identifier):128 位的唯一标识,用来唯一标识某种类型(接口/类)
比如:

__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++ 的原生 typeiddynamic_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_of<type> 来手动绑定 GUID。
  • 通过宏 #define __uuidof(type) guid_of<type>::value,无论在哪个编译器环境,都能统一用 __uuidof(type) 方式获取 GUID。
  • 这样写能实现跨编译器一致访问 GUID,同时保证代码跨平台兼容。

总结一下这个理想方案的问题点:

  • [uuid(“…”)] 标注虽然写起来直观简洁,
  • 但它是 微软专有扩展,不属于标准 C++,只能在 Visual C++ 使用
  • 其实现会在对象实例中增加额外指针,导致对象体积变大
  • 这对性能敏感或跨平台项目是不友好的。
    所以,虽然它“看上去理想”,但实际用时要慎重,最好还是用兼容性更好、对实例大小无影响的传统 __declspec(uuid(...)) + 模板特化方案。

总结一下这个用宏简化 GUID 绑定的关键点:

  • 宏写成 STRUCT_GUID(IWorkerCallback, "4D675322-F6F5-4E85-94EF-2927DFAA1409") 形式,
    宏只负责绑定 GUID,不包含 structclass 关键字,
    这样更利于 IDE 和工具解析代码(语法高亮、跳转等)。
  • structclass 关键字放在宏外写,方便代码风格统一和工具支持。
  • 注意宏绑定的 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 类型就绑定了对应的 GUID,VC++ 编译器会自动生成相关信息。

这段描述是 Clang 下跨平台实现 GUID 关联的思路,主要点如下:

核心思想

  • 不像 VC++ 直接用 __declspec(uuid(...)),Clang 没有类似的内建支持,需要用模板和函数来“模拟” GUID 关联。
  • 定义一个模板结构体 guid_of<T>,它有一个 static constexpr GUID value,存储对应类型的 GUID。
  • __uuidof(type) 宏映射到 guid_of<type>::value,这样用起来语义一致。

关键实现细节

// 默认模板,调用 get_guid 函数
template<typename T>
struct guid_of {static constexpr GUID value = get_guid(static_cast<T*>(nullptr));
};
// 默认 get_guid 函数实现,static_assert 防止未特化情况导致使用错误
template<typename 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_of<type>::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_cast<unsigned 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_cast<unsigned short>((H2U[g[9]] << 12) | (H2U[g[10]] << 8) |(H2U[g[11]] << 4) | H2U[g[12]]),// 第3段(4位十六进制)→ unsigned shortstatic_cast<unsigned 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_cast<unsigned char>((H2U[g[19]] << 4) | H2U[g[20]]),static_cast<unsigned char>((H2U[g[21]] << 4) | H2U[g[22]]),static_cast<unsigned char>((H2U[g[24]] << 4) | H2U[g[25]]),static_cast<unsigned char>((H2U[g[26]] << 4) | H2U[g[27]]),static_cast<unsigned char>((H2U[g[28]] << 4) | H2U[g[29]]),static_cast<unsigned char>((H2U[g[30]] << 4) | H2U[g[31]]),static_cast<unsigned char>((H2U[g[32]] << 4) | H2U[g[33]]),static_cast<unsigned 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' 转换为 015
  • 例如: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_GUIDget_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() 依赖于 ADL(Argument-Dependent Lookup)

template<typename T>
constexpr GUID get_guid(T*);
  • 它通过 实参依赖查找 来解析 get_guid()
  • 所以这个函数必须定义在和类型 T 同一个命名空间下。
  • 否则 get_guid(T*) 找不到合适的定义,触发 static_assert() 编译错误(这个是特意设计的安全机制)。

若类型未定义 GUID,会报错

template<typename T>
constexpr GUID get_guid(T*) {static_assert(false, "Type has no GUID.");
}
  • 这是故意设计的“fail early”机制。
  • 目的是让开发者在使用 __uuidof(T)必须先注册 GUID,否则就失败。

限制和已知问题

仅适用于 C++11

  • 这套方案依赖 C++11 的 constexpr 和模板机制。
  • 不兼容更早的标准(如 C++03)。

NDK 链接器问题(在 Android 开发中)

NDK linker error when __uuidof() is used as a template parameter

具体问题:

template<typename C, const IID* piid = &__uuidof(C)> class QIPtr;
  • 在某些平台(如 Android NDK),__uuidof() 的地址值不能用于模板非类型参数
  • 原因:链接器无法静态推导 &__uuidof(C) 的地址。

替代方案:使用 resolve_guid_ptr<C, piid>::guid

解决方法:

template<typename C, const IID* piid = nullptr>
class QIPtr {static constexpr const GUID* guid = resolve_guid_ptr<C, 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_of<type>::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_of<type>::value 来兼容。
  • 所以老代码无需修改,新的平台仍然能运行。

这个技巧还能用于其他自定义类型属性

不局限于 GUID,你还可以用类似方式为类型添加其他属性,如:

  • 类型标签(tag)
  • 分类信息(traits)
  • 序列化 ID 等等

如何实现一个通用机制

  1. 定义一个宏(比如 STRUCT_GUID)来注入属性信息。
  2. 宏会展开成一个 constexpr 函数,返回这个属性值。
  3. 提供一个访问接口类或模板(如 guid_of<T>::value)。
  4. 提供一个默认模板函数(当类型未定义该属性时触发 static_assert,或返回默认值)。

中文总结一句话:

使用 STRUCT_GUID 技术,我们可以为类型编译期地绑定 GUID 或其他自定义属性,支持多平台编译器(如 VC++、Clang),且对旧代码兼容良好。这个机制也能扩展到任意类型属性的静态绑定,是一种强大且通用的元编程手法。

http://www.xdnf.cn/news/10708.html

相关文章:

  • 蛋白质设计软件LigandMPNN介绍
  • 宇树科技更名“股份有限公司”深度解析:机器人企业IPO前奏与资本化路径
  • R1-Searcher++新突破!强化学习如何赋能大模型动态知识获取?
  • 职坐标IT培训:嵌入式开发C语言/硬件/RTOS路径
  • 时代星光推出战狼W60智能运载无人机,主要性能超市场同类产品一倍!
  • NLP实战(5):基于LSTM的电影评论情感分析模型研究
  • BugKu Web渗透之源代码
  • C++ stl容器之string(字符串类)
  • .NET 原生驾驭 AI 新基建实战系列(一):向量数据库的应用与畅想
  • 利用 Scrapy 构建高效网页爬虫:框架解析与实战流程
  • 2022年 国内税务年鉴PDF电子版Excel
  • centos安装locate(快速查找linux文件)
  • 【QT】QString 与QString区别
  • Qt 仪表盘源码分享
  • docker 中 什么是「卷」?(Volume)
  • 使用Composer创建公共类库
  • 国产高云FPGA实现视频采集转UDP以太网输出,FPGA网络摄像头方案,提供2套Gowin工程源码和技术支持
  • 负载均衡相关基本概念
  • 【Axure高保真原型】交通事故大屏可视化分析案例
  • 【知识点】第4章:程序控制结构
  • 软考 系统架构设计师系列知识点之杂项集萃(79)
  • 博客摘录「 数据库系统概论课后习题答案(第五版 王珊、萨师煊)」2024年10月30日
  • DeepSeek模型边缘计算与端侧部署技术解析
  • 高效DBA的日常运维主题沙龙
  • 无人机巡检智能边缘计算终端技术方案‌‌——基于EFISH-SCB-RK3588工控机/SAIL-RK3588核心板的国产化替代方案‌
  • electron-vite_18桌面共享
  • Starrocks Full GC日志分析
  • Docker容器化技术背后的操作系统原理
  • LINUX63 硬链接、软链接;FTP默认配置
  • 论文阅读:CLIP:Learning Transferable Visual Models From Natural Language Supervision