深入浅出:Linux C编程中如何使用动态链接库

在一些大型项目的开发中,代码复用是很常见的情况,各个模块经常会使用一些通用功能。为了提高代码质量,减少代码冗余,一般会引入公共的模块库,将通用函数写入其中。动态链接库就是这样的一个公共的模块库,它不仅可以降低模块之间的耦合,还可以将其发布出去,供他人使用。

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_MAXstdlib.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 提供了 dlopendlsymdlerrordlcolose 函数(在头文件 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= 是用来告诉链接程序,从指定的目录下寻找链接库。

文中是linux下 C++动态库 实现接口提供类导出的一个例子 注意其中使用函数返回基类指针的用法,因为Linux动态链接库不能像MFC中那样直接导出类 一、介绍 如何使用dlopen API动态地加载C++函数和类,是Unix C++程序员经常碰到的问题。 事实上,情况偶尔有些复杂,需要一些解释。这正是写这篇mini HOWTO的缘由。 理解这篇文档的前提是对C/C++语言中dlopen API有基本的了解。 这篇HOWTO的维护链接是: http://www.isotton.com/howtos/C++-dlopen-mini-HOWTO/ 二、问题所在 有时你想在运行时加载一个库(并使用其中的函数),这在你为你的程序写一些插件或模块架构的时候经常发生。 在C语言中,加载一个库轻而易举(调用dlopen、dlsym和dlclose就够了),但对C++来说,情况稍微复杂。 动态加载一个C++库的困难一部分是因为C++的name mangling (译者注:也有人把它翻译为“名字毁坏”,我觉得还是不翻译好), 另一部分是因为dlopen API是用C语言实现的,因而没有提供一个合适的方式来装载类。 在解释如何装载C++库之前,最好再详细了解一下name mangling。 我推荐您了解一下它,即使您对它不感兴趣。因为这有助于您理解问题是如何产生的,如何才能解决它们。 1. Name Mangling 在每个C++程序(或库、目标文件)中, 所有非静态(non-static)函数在二进制文件中都是以“符号(symbol)”形式出现的。 这些符号都是唯一的字符串,从而把各个函数在程序、库、目标文件中区分开来。 在C中,符号名正是函数名:strcpy函数的符号名就是“strcpy”,等等。 这可能是因为两个非静态函数的名字一定各不相同的缘故。 而C++允许重载(不同的函数有相同的名字但不同的参数), 并且有很多C所没有的特性──比如类、成员函数、异常说明──几乎不可能直接用函数名作符号名。 为了解决这个问题,C++采用了所谓的name mangling。它把函数名和一些信息(如参数数量和大小)杂糅在一起, 改造成奇形怪状,只有编译器才懂的符号名。 例如,被mangle后的foo可能看起来像foo@4%6^,或者,符号名里头甚至不包括“foo”。 其中一个问题是,C++标准(目前是[ISO14882])并没有定义名字必须如何被mangle, 所以每个编译器都按自己的方式来进行name mangling。 有些编译器甚至在不同版本间更换mangling算法(尤其是g++ 2.x和3.x)。 即使您搞清楚了您的编译器到底怎么进行mangling的,从而可以用dlsym调用函数了, 但可能仅仅限于您手头的这个编译器而已,而无法在下一版编译器下工作。 三、类 使用dlopen API的另一个问题是,它只支持加载函数。 但在C++中,您可能要用到库中的一个类,而这需要创建该类的一个实例,这不容易做到。 四、解决方案 1. extern "C" C++有个特定的关键字用来声明采用C binding的函数: extern "C" 。 用 extern "C"声明的函数将使用函数名作符号名,就像C函数一样。 因此,只有非成员函数才能被声明为extern "C",并且不能被重载。 尽管限制多多,extern "C"函数还是非常有用,因为它们可以象C函数一样被dlopen动态加载。 冠以extern "C"限定符后,并不意味着函数中无法使用C++代码了, 相反,它仍然是一个完全的C++函数,可以使用任何C++特性和各种类型的参数。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江下枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值