你在这里问三个不同的问题。
最初的问题询问是否有任何方法可以让 MSVC 不生成名称,或者是否有可能与其他编译器一起使用,或者,如果失败了,是否有任何方法可以在不破坏任何东西的情况下从生成的 type_info 中删除名称。
然后您想知道是否可以修改 MS ABI(可能不会太激进),以便可以剥离名称。
最后,您想知道是否可以设计一个没有名称的 ABI。
问题 #1 本身就是一个复杂的问题。据我所知,没有办法让 MSVC 不生成名称。大多数其他编译器针对的是专门定义 typeid(foo).name() 必须返回的 ABI,因此它们也不能不生成名称。
更有趣的问题是,如果去掉名字会发生什么。对于 MSVC,我不知道答案。最好的办法可能是尝试一下——进入您的 DLL 并将每个名称的第一个字符更改为 \0 并查看它是否会破坏 dynamic_cast 等(我知道您可以使用 Mac 和 linux x86_64 可执行文件执行此操作由 g++ 4.2 生成并且它可以工作,但我们暂时把它放在一边。)
关于问题 #2,假设空白名称不起作用,将基于名称的系统修改为不再需要名称并不难。一个简单的解决方案是使用名称的散列,甚至是 ROT13 编码的名称(请记住,这里的原始目标是“我不希望普通用户看到我的类的令人尴尬的名称”)。但我不确定这是否适用于您正在寻找的东西。稍微复杂一点的解决方案如下:
- 对于“dllexport”类,生成一个 UUID,将其放入 typeinfo,并将其放入与 DLL 一起生成的 .LIB 导入库中。
- 对于“dllimport”ed 类,从 .LIB 中读取 UUID 并改用它。
因此,如果您设法正确获取 dllexport/dllimport,它将起作用,因为您的 exe 将使用与 dll 相同的 UUID。但如果你不这样做呢?如果您“不小心”在 DLL 和 EXE 中指定了相同的类(例如,具有相同参数的相同模板的实例化),而没有将一个标记为 dllexport 和一个标记为 dllimport,该怎么办?RTTI 不会将它们视为同一类型。
这是一个问题吗?好吧,C++ 标准并没有说它是。也没有任何 MS 文档。事实上,文档明确表示您不允许这样做。您不能在两个不同的模块中使用相同的类或函数,除非您明确地将其从一个模块导出并导入另一个模块。使用类模板很难做到这一点的事实是一个问题,而且这是一个他们没有尝试解决的问题。
让我们举一个现实的例子:创建一个基于节点的linkedlist
类模板,其中包含一个全局静态哨兵,其中每个列表的最后一个节点都指向该哨兵,end() 函数只返回一个指向它的指针。(微软自己的 std::map 实现正是这样做的;我不确定这是否仍然正确。)linkedlist<int>
在你的 exe 中新建 a ,并通过引用你的 dll 中试图从 to 迭代的函数来传递l.begin()
它l.end()
. 它永远不会完成,因为 exe 创建的节点都不会指向 dll 中哨兵的副本。当然,如果你传递l.begin()
并l.end()
进入 DLL,而不是传递l
自身,你就不会有这个问题。您通常可以通过std::string
或各种其他类型的引用,只是因为它们不依赖于任何破坏的东西。但你实际上并没有被允许这样做,你只是走运了。因此,虽然用必须在链接时查找的 UUID 替换名称意味着在链接加载器时无法匹配类型,但在链接加载器时类型已经无法匹配的事实意味着这是无关的。
建立一个没有这些问题的基于名称的系统是可能的。ARM C++ ABI (以及基于它的iOS 和 Android ABI)限制了程序员可以比 MS 少得多的东西,并且对链接加载器如何使其工作有非常具体的要求(3.2.5)。这个不能修改为不基于名称,因为它是设计中的明确选择:
• type_info::operator== 和 type_info::operator!= 比较 type_info::name() 返回的字符串,而不仅仅是指向 RTTI 对象及其名称的指针。
• 不依赖type_info::name() 返回的地址。(也就是说,t1.name() != t2.name() 并不意味着 t1 != t2)。
第一个条件有效地要求这些运算符(和 type_info::before())必须被调用,并且执行环境必须提供它们的适当实现。
但也可以构建一个不存在此问题且不使用名称的 ABI。这很好地延续到#3。
Itanium ABI (除其他外,在 x86_64 和 i386 上被OS X 和最近的 linux 使用)确实保证linkedlist<int>
在一个对象中linkedlist<int>
生成的 a 和从另一个对象中的相同标头生成的 a 可以在运行时链接在一起,并将成为相同类型,这意味着它们必须具有相同的 type_info 对象。从 2.9.1 开始:
当且仅当指针相等时,两个 type_info 指针才指向等价的类型描述。实现必须满足这个约束,例如通过使用符号抢占、COMDAT 部分或其他机制。
编译器、链接器和链接加载器必须协同工作,以确保在可执行文件中创建的 a指向与在共享对象linkedlist<int>
中创建的完全相同的 type_info 对象。linkedlist<int>
所以,如果你只是把所有的名字都拿出来,那根本就没有任何区别。(这很容易测试和验证。)
但是你怎么可能实现这个 ABI 规范呢?j_kubik 有效地辩称这是不可能的,因为您必须在 .so 文件中保留一些链接时间信息。这指向了一个明显的答案:在 .so 文件中保留一些链接时间信息。事实上,您已经必须这样做来处理加载时重定位等问题;这只是扩展了您需要保留的内容。事实上,Apple 和 GNU/linux/g++/ELF 都是这样做的。(这也是几年前每个构建复杂 linux 系统的人都必须了解符号可见性和模糊链接的部分原因。)
有一个更明显的方法来解决这个问题:编写一个基于 C++ 的链接加载器,而不是试图让 C++ 编译器和链接器一起工作来欺骗基于 C 的链接加载器。但据我所知,自从 Be 之后没有人尝试过。