深入浅出:Linux C编程中如何使用动态链接库
在一些大型项目的开发中,代码复用是很常见的情况,各个模块经常会使用一些通用功能。为了提高代码质量,减少代码冗余,一般会引入公共的模块库,将通用函数写入其中。动态链接库就是这样的一个公共的模块库,它不仅可以降低模块之间的耦合,还可以将其发布出去,供他人使用。0x10 动态链接库的生成和使用动态链接库只是一些函数的实现,供第三方调用。从宏观角度上来说,需要以下两个文件头文件,主要是函数的一些声...
在一些大型项目的开发中,代码复用是很常见的情况,各个模块经常会使用一些通用功能。为了提高代码质量,减少代码冗余,一般会引入公共的模块库,将通用函数写入其中。动态链接库就是这样的一个公共的模块库,它不仅可以降低模块之间的耦合,还可以将其发布出去,供他人使用。
0x10 动态链接库的生成和使用
动态链接库只是一些函数的实现,供第三方调用。从宏观角度上来说,需要以下两个文件
- 头文件,主要是函数的一些声明(类似于 Java 中的接口)
- 函数实现代码
0x11 生成动态链接库
头文件需要提供给使用者,因为我们需要知道动态链接库里有哪写函数可供使用。#ifndef 都是一种宏定义判断,作用是防止多重定义,在头文件中也经常使用
/* random.h */
#ifndef _RANDOM_H_
#define _RANDOM_H_
int obtain_random(int x);
#endif
以下是函数实现代码
/* random.c */
#include <time.h>
#include <stdlib.h>
int obtain_random(int max)
{
srand((int)time(0));
return rand() % max;
}
如果你对随机数比较熟悉,可忽略下面的这段话:
标准C库中函数 rand()
可以生成 0~RAND_MAX 之间的一个随机数,其中 RAND_MAX
是stdlib.h
中定义的一个整数,它与系统有关。如果直接使用 rand() 函数,你会发现每次生成的值都是一样的,因为 rand() 函数默认会有一个种子值(默认为1),其根据种子值来生成随机数,种子一样,生成的随机数也一样。为了让程序在每次执行时都能生成一个新序列的随机值,我们通常通过为随机数生成器提供一粒新的随机种子。函数 srand()
(来自stdlib.h
)可以为随机数生成器播散种子。只要种子不同 rand() 函数就会产生不同的随机数序列。srand() 称为随机数生成器的初始化器。
编译生成动态链接库
gcc -shared -fPIC random.c -o librandom.so
- shared 参数,顾名思义就是用来生成动态链接库的
- fPIC 参数,告诉编译器产生与位置无关的代码
这样,就完成了 librandom.so
文件的制作。
0x12 使用动态链接库
编写测试代码,调用动态链接库中的函数
/* test.c */
#include <stdio.h>
#include "random.h"
int main(int argc, char const *argv[])
{
/* generate random number within 100*/
printf("%d\n", obtain_random(100));
return 0;
}
编译和链接
gcc test.c -L ./ -l random -o test
- L 库文件路径
- l 要链接的库,库的名称去头尾
运行结果
这里为什么要加 LD_LIBRARY_PATH
? 刚刚编译的时候不是已经指定动态链接库的路径了吗?不指定文件路径 ,可能会报错 cannot open shared object file: No such file or directory,解决方案详见 Q&A
0x20 使用特定函数加载动态链接库
除了从引用头文件的方式,引入库函数,我们还可以使用特定的函数,来加载动态链接库中的函数。
0x21 使用 dlopen 函数加载动态链接库
Linux 提供了 dlopen
、 dlsym
、 dlerror
和 dlcolose
函数(在头文件 dlfcn.h
中声明)用来获取动态链接库中包含的函数。可用 man
命令查看相应的手册,获得的函数声明如下
#include <dlfcn.h>
void *dlopen(const char *filename, int flag);
char *dlerror(void);
void *dlsym(void *handle, const char *symbol);
int dlclose(void *handle);
dlopen 是一个功能强大的库函数,可以打开一个库,并装入内存。dlopen 用来加载库中的符号,而这些符号在编译的时候,编译器无法知道。dlopen 以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程;dlerror 返回错误状态;dlsym 通过句柄和连接符名称获取函数名或者变量名;dlclose来卸载打开的库。
flags 参数必须包括以下两个值中的一个:
RTLD_LAZY
执行延迟绑定。仅在执行引用它们的代码时解析符号。如果从未引用该符号,则永远不会解析它(只对函数引用执行延迟绑定;在加载共享对象时,对变量的引用总是立即绑定)。自 glibc 2.1.1,此标志被LD_BIND_NOW环境变量的效果覆盖。RTLD_NOW
如果指定了此值,或者环境变量 LD_BIND_NOW 设置为非空字符串,则在dlopen()返回之前,将解析共享对象中的所有未定义符号。如果无法执行此操作,则会返回错误。
我们还以刚刚生成的动态链接库 librandom.so 为例,测试代码如下
/* test_dlopen.c */
#include <stdio.h>
#include <dlfcn.h>
#define DLL_FILE_NAME "librandom.so"
int main(int argc, char const *argv[])
{
void * handle;
int (* func)(int);
char * error;
/* 加载动态链接库 */
handle = dlopen(DLL_FILE_NAME, RTLD_NOW);
if(handle == NULL)
{
printf("Failed to open library %s, error: %s\n", DLL_FILE_NAME, dlerror());
return -1;
}
/* 获取特定的函数 */
func = dlsym(handle, "obtain_random");
printf("%d\n", func(100));
return 0;
}
编译
gcc test_dlopen.c -o test_dlopen -ldl
- dl 显式加载动态库的动态函数库。在 Linux 上,使用动态链接的应用程序需要和库 libdl.so 一起链接,也就是使用选项 -ldl。但是,编译时不需要和动态装载的库一起链接。
结果如下
0x22 使用 python 调用动态链接库
其实 python 也可以调用 C/C++ 编写的动态链接库,只要引入 ctypes
库就可以使用了。
#! /usr/bin/python
#-*- coding=utf-8 -*-
import ctypes
librandom = ctypes.cdll.LoadLibrary( ".//librandom.so" )
print "random: " + str(librandom.obtain_random(100))
0x30 传统编译法
如果你不想使用动态链接库,想直接引用一些开源代码,比如这里,直接将 random.h random.c test.c
一起编译,怎么做呢?也就是说,如果我们不使用动态链接库,怎么链接多个源文件呢?
0x31 传统方法编译和链接文件
生成目标文件
gcc -c random.c -o random.o
gcc -c test.c -o test.o
这里 -c 的作用是,不让 gcc 编译器自动链接,下一步,将目标文件链接在一起,生成可执行程序
gcc random.o test.o -o test
运行结果
0x32 使用 Makefile 文件构建项目
在正式的项目中,通常会使用 Makefile 文件构建和管理工程。简单来说,以上几个命令,也可以用一个 Makefile 文件搞定
CFLAGS = -Wall
test: test.o random.o
test.o: test.c
gcc $(CFLAGS) -c test.c -o test.o
random.o: random.c
gcc $(CFLAGS) -c random.c -o random.o
clean:
rm -rvf *.o
- CFLAGS 用来消除编译中的警告,也可以不加
- clean 用来删除中间文件,也可以不加
结果如下
0x40 总结
在 0x21 节,我们提出,dlopen 用来加载库中的符号,在编译时并不知晓动态链接库中的符号,这种机制使得在系统中添加或者删除一个模块时,都不需要重新编译了。而在 0x10 节,使用头文件的方式引入库函数,如果函数未找到,代码就不能运行。使用头文件的方式,是在链接时定位相关函数,使用 dlopen,则是在程序实际运行时,定位要使用的函数。本篇博客介绍了动态链接库的常见用法,还给出不使用动态链接,直接使用源文件进行编译的一般方法,并且还使用 python 的相关库,调用 C/C++ 编写的动态链接库,相信你读过此文,对编译、动态链接都会有一个清晰的认识,而且可以使用本文的方法来进行实际的 Linux C 动态链接库调用。
0x50 Q&A
运行程序出现错误:cannot open shared object file: No such file or directory,即未找到动态链接库。这是因为默认的链接器只会从 /usr/lib/
寻找库文件。
方法一:在链接语句后面添加如下命令
-Wl,-rpath=路径
方法二:运行程序的时候,直接在前面加上LD_LIBRARY_PATH=
是用来告诉链接程序,从指定的目录下寻找链接库。
更多推荐
所有评论(0)