我已经更新了代码示例并添加了更多详细信息,因为有些人在评论中说该代码不应生成任何导出的符号句点。
我还在这些方法中添加了一些实现,但在使用 MinGW 和 Clang 导出符号方面没有任何改变。 (没更新objdump
虽然输出。本质上他们没有改变太多)。
从可执行文件导出符号时,我遇到了一个非常烦人的问题。
考虑以下代码:
# CMakeLists.txt
add_executable(app main.cpp)
set_target_properties(app PROPERTIES ENABLE_EXPORTS 1)
add_library(lib SHARED main.cpp)
// main.cpp
class __declspec(dllexport) foo
{
public:
int bar()
{
static int val = 3;
return ++val;
}
static int buz()
{
static int val = 814;
return ++val;
}
};
template <typename T>
class __declspec(dllexport) foo_t
{
public:
int bar()
{
static int val = 3;
return ++val;
}
static int buz()
{
static int val = 814;
return ++val;
}
};
template class foo_t<int>;
int main()
{
return 0;
}
MSVC
编译时生成可执行文件和dllxxx.lib
带有可用于链接的导出符号。
// DUMPBIN.exe /EXPORTS app.lib (exact same as for lib.lib)
Dump of file lib.lib
File Type: LIBRARY
Exports
ordinal name
??4?$foo_t@H@@QEAAAEAV0@$$QEAV0@@Z (public: class foo_t<int> & __cdecl foo_t<int>::operator=(class foo_t<int> &&))
??4?$foo_t@H@@QEAAAEAV0@AEBV0@@Z (public: class foo_t<int> & __cdecl foo_t<int>::operator=(class foo_t<int> const &))
??4foo@@QEAAAEAV0@$$QEAV0@@Z (public: class foo & __cdecl foo::operator=(class foo &&))
??4foo@@QEAAAEAV0@AEBV0@@Z (public: class foo & __cdecl foo::operator=(class foo const &))
?bar@?$foo_t@H@@QEAAXXZ (public: void __cdecl foo_t<int>::bar(void))
?bar@foo@@QEAAXXZ (public: void __cdecl foo::bar(void))
?buz@?$foo_t@H@@SAXXZ (public: static void __cdecl foo_t<int>::buz(void))
?buz@foo@@SAXXZ (public: static void __cdecl foo::buz(void))
可以看出,两者foo
和模板foo_t
方法已导出。
Clang
Dump of file libapp.dll.a
File Type: LIBRARY
Exports
ordinal name
_ZN5foo_tIiE3barEv
_ZN5foo_tIiE3buzEv
_ZN5foo_tIiEaSERKS0_
因为只能看到foo_t
方法已导出并且仅由于显式模板实例化。如果删除显式模板实例化,则不会有libapp.dll.a
。不过会有liblib.dll.a
虽然有一个空EXPORTS
部分。
MinGW64
Dump of file liblib.dll.a
File Type: LIBRARY
Exports
ordinal name
main
_ZN5foo_tIiE3buzEv
_ZN5foo_tIiE3barEv
与 Clang 一样,它仅导出显式模板实例化方法。
但与 Clang 和 MSVC 不同没有libapp.dll.a生成可执行文件!
// objdump -t app.exe
File
[116](sec 1)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000000016c0 .text$_ZN5foo_tIiE3barEv
AUX scnlen 0xb nreloc 0 nlnno 0 checksum 0x0 assoc 0 comdat 2
[118](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 1) 0x00000000000016c0 _ZN5foo_tIiE3barEv
AUX tagndx 0 ttlsiz 0x0 lnnos 0 next 0
[120](sec 1)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000000016d0 .text$_ZN5foo_tIiE3buzEv
AUX scnlen 0x7 nreloc 0 nlnno 0 checksum 0x0 assoc 0 comdat 2
[122](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000000016d0 _ZN5foo_tIiE3buzEv
可执行文件本身包含显式实例化模板的符号foo_t
方法。为什么没有为可执行文件生成静态库?
结论
- 在动态库的所有情况下,静态库中都有导出的符号(
libxxx.a
or xxx.lib
) 用于显式类模板实例化。但 MinGW64 不会为可执行文件生成静态库。为什么?
- 仅 MSVC 导出非模板类隐式内联方法的符号。为什么?这是跨平台应用程序可以依赖的预期行为吗?
- 为什么导出模板类和非模板类的隐式内联类内实现方法之间存在不一致(非模板不导出)?跨平台应用程序可以依赖导出的模板类方法作为保证吗?
要深入了解该问题,请参阅https://github.com/skypjack/entt/issues/719