第十八夜:C语言动态链接与静态链接

动态链接与静态链接的区别

链接通俗来说就是C语言程序在编译过程中,编译器将所有的资源“拼接”到二进制文件中,从而形成最终的可执行文件的过程。

链接库文件通常就是一些专门存放一些通用函数的文件,动态和静态库的区别就在于,动态库是随着程序的发布一起发布的,作为独立的模块发布。

程序需要用到其中的函数时,动态地加载这些模块,从而调用其中的功能。

而静态库则是程序在编译的时候,从库中将所需要用到的函数、类等提取出来,复制到自身的可执行文件中。

在Linux中,静态链接库文件的后缀名通常是.a;在Windows系统中,静态链接库文件的后缀名为.lib

在Linux中,动态链接库文件的后缀名通常是.so;在Windows系统中,动态链接库文件的后缀名为.dll

Linux动态链接库和静态链接库

Linux动态链接

一个简单的Linux动态链接创建实例:

源文件:func1.c和func2.c编译生成testlib.so,testlib.h中声明了testlib.so的函数,main.c调用testlib.so中的函数。

func1.c:

1
2
3
4
5
6
#include "testlib.h"

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

func2.c:

1
2
3
4
5
#include "testlib.h"

int sub(int a,int b){
return a - b;
}

testlib.h:

1
2
3
4
5
6
7
#ifndef __TEST_H_
#define __TEST_H_

int add(int a,int b);
int sub(int a,int b);

#endif

编译:

编译生成testlib.so:

1
gcc -fpic -shared func1.c func2.c -o testlib.so

Linux动态链接的隐式调用(静态调用)

Linux动态链接库的隐式调用,就是将生成的so文件直接放到系统的Lib路径中,可执行程序在执行调用之前系统会自动将库文件加载到内存中,而不需要手动去加载。

main.c:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include "testlib.h"

int main(){
int m, n;
printf("Input two numbers: ");
scanf("%d %d", &m, &n);
printf("%d+%d=%d\n", m, n, add(m, n));
printf("%d-%d=%d\n", m, n, sub(m, n));
return 0;
}

编译生成main:

1
gcc main.c testlib.so -o main

生成之后如果直接执行main会报错:

1
./main: error while loading shared libraries: testlib.so: cannot open shared object file: No such file or directory

因为此时系统不知道应该从哪里加载testlib.so:

1
2
3
4
5
6
chorder@debian:~/testlib$ldd main
linux-vdso.so.1 (0x00007fff979c7000)
testlib.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa0e5d8c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa0e5f70000)

只需要将testlib.so拷贝到/usr/lib/即可:

1
2
3
4
5
6
chorder@debian:~/testlib$sudo cp testlib.so /usr/lib/
chorder@debian:~/testlib$./main
Input two numbers: 3
2
3+2=5
3-2=1

Linux动态链接的显式调用(动态调用)

main_d.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <dlfcn.h>
#include <stddef.h>
#include <stdio.h>

int main(){
int m,n;
//打开库文件,需要提供testlib.so的绝对路径,否则会无法加载
void* handler = dlopen("./testlib.so",RTLD_LAZY);
if(dlerror() != NULL){
printf("%s",dlerror());
}

//获取库文件中的 add() 函数
int(*add)(int,int)=dlsym(handler,"add");
if(dlerror()!=NULL){
printf("%s",dlerror());
}

//获取库文件中的 sub() 函数
int(*sub)(int,int)=dlsym(handler,"sub");
if(dlerror()!=NULL){
printf("%s",dlerror());
}

//使用库文件中的函数实现相关功能
printf("Input two numbers: ");
scanf("%d %d", &m, &n);
printf("%d+%d=%d\n", m, n, add(m, n));
printf("%d-%d=%d\n", m, n, sub(m, n));
//关闭库文件
dlclose(handler);
return 0;
}

编译生成main_d:

1
gcc main_d.c -ldl -o main_d

可以直接运行main_d:

1
2
3
4
5
chorder@debian:~/testlib$gcc main_d.c -ldl -o main_d
chorder@debian:~/testlib$./main_d
Input two numbers: 3 2
3+2=5
3-2=1

Linux静态链接

静态链接相对来说比较简单,只需要在编译的时候加上相应的参数即可将函数编译到可执行文件中。

一个简单的Linux下的静态链接库的例子:

add.c和sub.c编译生成libtest.a,libtest.h中声明了相应的函数,main.c调用libtest.a中的函数。

add.c

1
2
3
int add(int a, int b){
return a + b;
}

sub.c

1
2
3
int sub(int a, int b){
return a - b;
}

libtest.h

1
2
3
4
5
6
7
#ifndef __TEST_H_
#define __TEST_H_

int add(int, int);
int sub(int, int);

#endif

main.c

1
2
3
4
5
6
7
#include <stdio.h>
#include "libtest.h"

int main(){
printf("%d, %d\n", add(3, 2), sub(3, 2));
return 0;
}

编译:

编译add.c和sub.c,生成目标文件 add.o sub.o

1
gcc -c add.c sub.c

归档目标文件,生成静态链接库 libtest.a

使用 ar 命令将目标文件压缩到一起,并且对其进行编号和索引,以便于查找和检索

1
ar rcv libtest.a add.o sub.o

编译main.c:

1
gcc main.c -L. -ltest -o main

Windows动态链接库和静态链接库

Windows动态链接

Windows的动态链接库一般是DLL,这里创建一个DLL工程,func1.c和func2.c组合编译成testlib_win.dll,并通过两种方法分别调用testlib_win.dll中的函数。

第一种称之为显式链接,只需提供DLL文件和知晓函数名即可;第二种称之为隐式链接,需要提供lib,头文件和dll。

首先在VS2019中创建DLL项目:

func1.c:

1
2
3
4
5
6
#include "testlib_win.h"

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

func2.c:

1
2
3
4
5
#include "testlib_win.h"

int sub(int a, int b) {
return a - b;
}

testlib_win.h

1
2
3
4
5
6
7
8
#ifndef __TEST_H_
#define __TEST_H_

int add(int a, int b);
int sub(int a, int b);

#endif

直接编译会报错,需要修改一下项目属性,不使用预编译头:

点击生成解决方案,即可编译出DLL:

此时生成的文件中只有dll,并没有lib文件,如果想要生成lib文件,则需要对testlib_win.h文件进行修改:

testlib_win.h

1
2
3
4
5
6
7
#ifndef __TEST_H_
#define __TEST_H_

extern __declspec(dllexport) int add(int a, int b);
extern __declspec(dllexport) int sub(int a, int b);

#endif

此时编译出的文件中,就包含了testlib_win.lib:

Windows DLL显式调用

创建工程,包含testlib_win.h头文件,在其中加载DLL并调用add和sub函数:

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <Windows.h>
#include <stdio.h>

#include "testlib_win.h"

typedef int(*FUNC)(int, int);

int main() {

HINSTANCE hInstLibrary = LoadLibrary(L"testlib_win.dll");
FUNC add,sub;

if (hInstLibrary == NULL) {
printf("无法加载DLL");
}
else {
printf("DLL加载成功 %p\n", hInstLibrary);
}

add = (FUNC)GetProcAddress(hInstLibrary, "add");
sub = (FUNC)GetProcAddress(hInstLibrary, "sub");

if (add == NULL || sub == NULL) {
FreeLibrary(hInstLibrary);
printf("获取函数地址失败\n");
}
else {
printf("函数add地址获取成功 %p!\n", add);
printf("函数sub地址获取成功 %p!\n", sub);
printf("Call add: %d\n", add(1,2) );
printf("Call sub: %d\n", sub(3,2));
}

FreeLibrary(hInstLibrary);
return 0;
}

将testlib_win.h加入到源码目录,将testlib_win.dll加入到编译输出的文件所在目录:

运行即可:

Windows DLL隐式调用

由于不常用,以后再补充。

Windows静态链接

由于不常用,以后再补充。

(待续)

参考资料

GCC使用静态链接库和动态链接库
C/C++动态链接库的显式调用(动态调用)
linux静态链接库
什么是 DLL
windows LoadLibrary使用示例
Visual Studio 2013中.dll文件的显式调用方法
Windows DLL调用实例
VS2017生成一个简单的DLL文件 和 LIB文件——C语言

一个进程的意义 谈副业
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×