系统之间差异
在ubuntu或mac编译好的动态库,放到windows上编译出现了大量“无法解析的外部符号”错误,对Windows编译不太了解的同学一定感觉到特别困惑,这主要是不同系统关于动态库定义之间的差异。
Windows系统
使用msvc生成的dll动态库,必须使用__declspec(dllexport)
导出,否则使用方不能正常访问。与动态库配套出现的是导入库,导入库里包含了动态库导出类、函数相关信息。其他项目引用动态库时,直接引用导入库即可,导入库相关的信息会被编译到引用方产物中,但最终的打包产物中只有动态库,而没有导入库。导入库并不是必须的,在没有导入库的情况下,可以使用动态加载的方式来加载动态库,通过指定的函数名来引用函数。
hInstance = LoadLibrary("library.dll");
fun = (FUN)GetProcAddress(hInstance, "add")
k = fun(1, 2);
Unix系统
mac、linux、android等都是unix。
动态库里定义的类、类中的public方法默认都是可以被引入动态库的项目访问,如果想像Windows系统一样只暴露部分API给使用方的话,可以使用visibility("default")
方法来标记类或方法,在编译时添加-fvisibility=hidden
,修改默认访问权限为隐藏,只有使用visibility(“default”)的相关类和方法可以被其他使用动态库的项目访问到。
MinGW编译
mingw可以使用gcc或llvm对源码进行编译,这两种编译器在编译dll动态库时,可以像Unix系统一样,使用或不使用visibility("default")
导出函数,当然也可以使用dllexport来导出函数。
- visibility(“default”) (类Unix导出方法)
- __declspec(dllexport)(Windows导出方式)
导出方法命名规则
使用__declspec(dllexport)
编译生成的dll,.c文件生成的函数名保持不变
_Z7testOnev
_ZN3One3addEii
_ZN3One4multEii
_ZN3One8toStringEv
sayHello
使用__attribute__((visibility("default")))
编译.cpp文件生成的dll。.c文件生成的函数名保持不变
_Z7testOnev
_ZN3One3addEii
_ZN3One4multEii
_ZN3One8toStringEv
_ZNSt3__111char_traitsIcE6lengthEPKc
_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEC2B7v160006IDnEEPKc
_ZNSt3__116__non_trivial_ifILb1ENS_9allocatorIcEEEC2B7v160006Ev
_ZNSt3__117__compressed_pairINS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE5__repES5_EC2B7v160006INS_18__default_init_tagESA_EEOT_OT0_
_ZNSt3__118__constexpr_strlenB7v160006EPKc
_ZNSt3__119__debug_db_insert_cB7v160006INS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEEEvPT_
_ZNSt3__122__compressed_pair_elemINS_12basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE5__repELi0ELb0EEC2B7v160006ENS_18__default_init_tagE
_ZNSt3__122__compressed_pair_elemINS_9allocatorIcEELi1ELb1EEC2B7v160006ENS_18__default_init_tagE
_ZNSt3__19allocatorIcEC2B7v160006Ev
sayHello
编译出来的.c文件,函数名保持原样
glfwCreateCursor
glfwCreateStandardCursor
glfwCreateWindow
glfwCreateWindowSurface
glfwDefaultWindowHints
glfwDestroyCursor
导出方式混乱带来的问题
MinGW编译dll时,可以使用dllexport或visibility(“default”)两种方式进行方法导出,因依赖的库内置的导出方式不同,就会产生混乱使用的情况。动态库引用动态库,无论两个动态如何配置,没有问题;但动态库引入静态库场景,可能会产生问题
- 静态库使用dllexport导出,动态库使用visibility(“default”)导出
编译出的动态库只有静态库中使用dllexport导出的相关函数和类。在引用动态库编译可执行文件时,会提示动态库中被引用的方法未实现 - 静态库使用visibility(“default”)导出,动态库使用dllexport导出
动态库中使用dllexport的函数及类全部导出,静态库中的方法全部不能导出 - 同一动态库中即使用dllexport又使用visibility(“default”),则只有dllexport标记的函数和类被导出
总结:两者不要混合使用。在混合使用时,dllexport的优先级更高,visibility("default")相关标记会被忽略
。
llvm-mingw-20230614-msvcrt-x86_64\bin/clang++ -target x86_64-pc-windows-gnu -rtlib=compiler-rt -stdlib=libc++ -lunwind -lpthread -Qunused-arguments -fuse-ld=lld -o ./test.exe @./test.exe.rsp
ld.lld: error: undefined symbol: Two::toString()