C与C++的DLL
今天中午跟同事吃饭时,有人分享他在清理项目中的Warming,其中包括C++风格的DLL,我才知道,DLL建议写C风格的
根本原因是,C++功能复杂,更依赖Name Mangling,而三大编译器具体实现不尽相同,使得ABI不确定
DLL
我们都知道,代码到可执行文件,要先代码编译得到目标文件,目标文件链接得到可执行文件。但其实可执行文件在执行时,还会进行装载
DLL的本质其实是目标文件的打包,由目标文件和三张表组成
编译
代码会编译为目标文件.obj
,目标文件中包含导出符号表,这些符号会有明确的地址
// test1.cpp |
有的文件中会有只声明,没有实现的符号,放在未解决符号表中。在这些符号没有明确的地址,需要去其他目标文件中寻找
//test2.cpp |
链接与装载
若程序为静态链接,编译器会在链接阶段扫描导出符号表中的符号地址,将计算后的地址写给未解决符号表中的符号,这个过程被称为重定位
也就是说,从test1.obj
中得到a
的地址,经过计算,写给test2.obj
中的a
,这个过程会记录在地址重定向表中
若程序为动态链接,程序会在装载阶段做地址重定向
ABI
ABI(Application binary interface)
API(Application Programming Interface)
为了保证动态库的版本兼容性,大部分平台会要与DLL函数编写时,要符合ABI
ABI包含了二进制的结构布局、访问方法,应用程序可以通过ABI访问库中的二进制数据,代码编译出的ABI与编程语言、操作系统、编译器有关,通常是不确定的
二进制文件格式
不同操作系统的二进制可执行文件格式不同,因此无法兼容
ELF(Executable and Linkable Format),可执行可链接格式,是Linux系统的二进制可执行文件格式
PE(Portable Executable),可移植可执行格式,是Windows系统的二进制可执行文件格式
Name Mangling
Name Mangling会对名字进行重新编码,以实现名字的唯一性
C++支持函数重载、类、命名空间、模版,一个类中可以有多个重名函数、重名成员变量,这些名称编译后本质是不同的二进制,因此要做更复杂的Name Mangling
很不幸的是,C++没有对Name Mangling做硬性规定,于是三大编译器g++、MSVC、clang的实现不同,相同的代码可能编出不同的ABI,当你使用跨编译器的dll时,很容易出现错误
C的DLL
和C++相比,C就简单得多
大多数语言都提供了一些简单的方法调用C的DLL,而且C++编译器也支持导出C语言的DLL
生成DLL
Windows下生成DLL会得到三个产物:include头文件,dll文件,import library文件
注意import library文件的后缀是
.lib
,但这个文件只是存储了符号表,并不是其他平台的静态库
MT与MD
操作系统将一些高频使用的代码写成DLL,并永远加载在内存中,称为CRT库
在VS生成DLL时,有MT和MD两个选项
MD指动态C运行时,程序执行时依赖于操作系统提供的CRT
MT指静态C运行时,程序会将操作系统的一些CRT静态链接到程序中,运行时就不需要从操作系统那里装载
值得注意的是,如果一个程序混用MT和MD,很容易出现版本不兼容的错误(尤其是在使用编译好的第三方库时)
C++生成DLL
CMakeList.txt
SET(LIB_SRC ./Math.h ./Math.cpp) |
Math.h
|
Math.cpp
|
动态加载DLL
windows下DLL加载:
- 分析可执行文件的导入符号表,查找所需要的dll文件
- 为dll文件开辟虚拟地址空间
- 当dll被真正调用时,将dll文件加载到内存中
|
参考
DLL written in C vs the same written in C++