GCC, MSVC 实现静态链接、动态链接

来源:互联网 发布:西装 知乎 编辑:程序博客网 时间:2024/05/18 06:19

这两天一直在倒弄两个编译器的链接问题,这篇文章算是一个总结。

静态链接

假设一个程序 P 需要调用一个库 L,如果有着库 L 的源码,可以将 P 与 L 同时编译,生成一个 新的程序 N,其中 N = P + L (这里假设 L 全部被使用)。

由于各种原因,L 可能不开放源码。L 的作者可能会发布一个已经编译好的二进制文件 Lb,编译 P 结束后再将生成的 Pb 与 Lb 进行链接。新生成的程序 N = Pb + Lb。这里的 N 显然和上文的 N 是相近的存在。(与 Lb 的 编译 有关, 因为 L 可能 与 P 不是同样的方式进行编译。)

静态链接类似于,将目标程序分成几大部分来编译,最后进行一个完整的链接。这和直接从源码进行编译(每个文件单独编译,最后进行链接)其实是类似的。

GCC

生成静态链接库

GCC 生成静态链接库,其实是对库中各个源码文件生成的二进制文件进行打包(归档)。

假设库 L 包含以下文件:

a.h b.h c.h a.c b.c c.c

首先使用 GCC 编译生成多个 .o 文件:

gcc -c a.c b.c c.c

获得多个.o文件

a.o b.o c.o

然后将它们进行归档:

ar -r libL.a a.o b.o c.o

就生成了一个静态链接库文件 libL.a

使用静态链接库

静态链接库在链接时使用。

我们有一个程序源码 P,使用了这个库(在源码中#include相关头文件并且使用相关函数)

假设这个程序源码 P 只有一个文件:

main.c

使用 GCC 编译链接(gcc指令会将编译与链接结合)时,需要指定具体的静态库(使用 -l 指令)。指定库时还需要指定查找的路径(使用 -L 指令)。

-l 指令指定具体库名时有特殊方法。一般来说会省略前缀 lib 以及后缀 .a ,如 libL.a 就可以写作 -lL

-L 可以包含当前路径,写作 -L.

gcc -o main -L. -lL main.c

这样就生成了程序 Pb 。

MSVC

这里的 MSVC 指 使用 Visual Studio 这款 IDE。

在项目的常规属性中,配置类型 设置成 静态库(.lib) 即可生成静态链接库。

使用 .lib 静态库时,可以直接将 .lib 文件 加入到项目中(如同源码文件),也可以在 属性页->链接器->附加依赖项 中写明要引用的文件名称,亦或者在源码中使用 #pragma comment(lib, "需要引用的.lib文件") 指令。

请注意,这里的 .lib 文件是静态链接库文件,后面在调用动态链接库时使用的 .lib 文件是导入库文件,二者不能混淆。

动态链接

静态链接是将目标程序的各个部分分别编译,并在最后进行链接,实际上与从源码上编译链接是类似的。而动态链接并不在生成目标程序时链接,而是在程序运行的时候进行链接。

我们手里有一个程序源码 P 和一个库源码 L 。使用动态链接就是,首先将 L 编译为动态链接库 Lb, 然后编译 P 生成 Pb。 P 在生成时,需要提供其调用的 L 的信息(注明 L 是存在于动态链接库中,而不是静态链接库或者其他生成文件)。Pb 和 Lb 的组合,才是在运行时完整的目标程序。

GCC

生成动态链接库

通常情况下,GCC 编译库 动态链接库 L 时需要使用 -fPIC 指令,链接时需要使用 -shared 指令。(根据平台和版本有区别。)

gcc a.c b.c c.c -shared -fPIC -o libL.so

注:使用 MinGW 时,请将 .so 改为 .dll ,否则使用 -l 指令无法查找。(在 MinGW GCC 5.1 下)

使用动态链接库

静态调用

GCC 需要在生成 程序Pb 时提供其所调用的 动态链接库 的信息,因此在生成时需要引用对应的动态链接库(否则链接时会出现查找不到符号的错误)。

引用方法与引用静态链接库相同。

gcc -o main -L. -lL main.c

在运行时,需要将生成的动态链接库放置在能够查找到的位置。

Windows 下放置在程序目录即可。Linux 下通常不包含程序的当前目录,我们需要使用 export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH 指令来在运行前包含当前目录(也可以是别的目录),使用 unset LD_LIBRARY_PATH 来取消设定。在此不赘述。

动态调用

这里所述的,是Linux下动态调用 so 库的方法。在使用 MinGW 于 Windows 环境中 进行动态调用,与 MSVC 方法类似。

假设我们已经有一个库文件 libL.so ,其内有一个函数 int add_int(int, int) 我们想要对其进行调用。

Linux 下需要引用 dlfcn.h 头文件,并且链接 libdl.so 库。

我们可以如下编写:

void *pdlHandle = dlopen("./libL.so", RTLD_LAZY);int (*func)(int, int) = dlsym(pdlHandle, "add_int");

分别进行加载与获取函数,同时要判断是否获取失败。当然释放时使用 dlclose(pdlHandle); 来释放。在此不赘述。

在编译这个文件时,需要链接 libdl.so 库。值得注意的是,使用 gcc 命令会出现指令前后顺序的问题:

gcc -o main main.c -ldl

这样书写是没问题的。但是将 -ldl 提到 main.c 前,就会出现链接问题。

MSVC

MSVC 生成动态链接库与生成静态链接库类似,在设置中调成 生成 .dll 即可。MSVC 在生成 .dll 的同时,还会生成 .lib 导入库文件(并非 静态库文件),用于静态调用 动态链接库 的声明。

MSVC 调用 .dll 分为 静态调用 和 动态调用 两种。

静态调用

MSVC 的 动态链接库 静态调用,与 导入 静态链接库 类似。不同的是,其导入的库为 生成 .dll 时生成的 .lib 导入库,并且程序运行时需要将 .dll 放置在查找环境(程序本目录或者系统库目录或自设目录)中。

步骤如下:

L -(动态库生成)-> L.dll, L.lib
P & L.lib -> P.exe, 运行时需要 L.dll

动态调用

动态调用是通过 Windows API 手动调用 .dll 。

假设 L.dll 里有如下函数:

int add_int(int, int);

使用如下方式就可以加载以及获取对应的函数。

typedef int (*Func) (int, int);HINSTANCE hDLL = LoadLibrary(L"L.dll");Func *func = (Func*)GetProcAddress(hDLL, "add_int");

在结束调用 .dll 后,使用 FreeLibrary(hDLL); 来释放。

加载失败会生成 NULL 指针,使用前需要判断。

需要注意的是,32位程序必须对应32位的动态链接库,64位同理。

MinGW 与 MSVC 的交互

MinGW 生成的 .dll 文件也可以在 MSVC 中进行动态调用。

MinGW 并没有生成 MSVC 所需要的 .lib 导入库文件,因此不能直接进行静态调用。

MinGW 与 MSVC 的文件相互转换可以参考:
http://blog.csdn.net/gavinr/article/details/7383215