抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

C与C++的DLL

今天中午跟同事吃饭时,有人分享他在清理项目中的Warming,其中包括C++风格的DLL,我才知道,DLL建议写C风格的

根本原因是,C++功能复杂,更依赖Name Mangling,而三大编译器具体实现不尽相同,使得ABI不确定

DLL

我们都知道,代码到可执行文件,要先代码编译得到目标文件,目标文件链接得到可执行文件。但其实可执行文件在执行时,还会进行装载

DLL的本质其实是目标文件的打包,由目标文件和三张表组成

编译

代码会编译为目标文件.obj,目标文件中包含导出符号表,这些符号会有明确的地址

// test1.cpp
int a; // 在test1.obj文件中a有明确的地址

有的文件中会有只声明,没有实现的符号,放在未解决符号表中。在这些符号没有明确的地址,需要去其他目标文件中寻找

//test2.cpp
extern int a; // 在test2.obj文件中a没有明确的地址

链接与装载

若程序为静态链接,编译器会在链接阶段扫描导出符号表中的符号地址,将计算后的地址写给未解决符号表中的符号,这个过程被称为重定位

也就是说,从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)
SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ../libMath)

ADD_LIBRARY(MathDLL SHARED ${LIB_SRC})
INSTALL(TARGETS MathDLL)

SET_TARGET_PROPERTIES(MathDLL PROPERTIES LINKER_LANGUAGE C
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
OUTPUT_NAME "MathDLL"
PREFIX "")

Math.h

#define DLL_EXPORT extern "C" __declspec(dllexport)

DLL_EXPORT int MathAdd(int a, int b);

Math.cpp

#include "Math.h"

int MathAdd(int a, int b){
return a+b;
}

动态加载DLL

windows下DLL加载:

  1. 分析可执行文件的导入符号表,查找所需要的dll文件
  2. 为dll文件开辟虚拟地址空间
  3. 当dll被真正调用时,将dll文件加载到内存中
#include <iostream>
#include <Windows.h>

typedef int (*pfnAdd)(int, int);

int main() {
HINSTANCE handle = LoadLibrary(TEXT("../lib/libMath/MathDLL.dll"));
pfnAdd pAdd = (pfnAdd) GetProcAddress(handle, "MathAdd");
std::cout << pAdd(1, 2) << std::endl;
FreeLibrary(handle);
return 0;
}

参考

DLL written in C vs the same written in C++

What is an application binary interface (ABI)?

使用CMake生成动态链接库DLL

评论