在某些时候,您可能必须在运行时加载库才能使用其功能。 在为程序编写某种插件或模块体系结构时,这种情况最常见。

在Linux C/C++语言中,加载库非常简单,只需调用dlopen,dlsym和dlclose就足够了。

动态装载库API

动态库的运行时加载时通过一系列由动态链接器(dynamic linker)提供的API来实现的,这几个API的实现都是在libdl.so中的,且相关的声明和常量定义在头文件<dlfcn.h>中。

下面列出了这些API:

  • dlopen,打开一个库,并为使用该库做些准备。
void *dlopen(const char *filename, int flag);
/*
 mode:分为这两种
  RTLD_LAZY 暂缓决定,等有需要时再解出符号 
  RTLD_NOW 立即决定,返回前解除所有未决定的符号。
  RTLD_LOCAL
  RTLD_GLOBAL 允许导出符号
  RTLD_GROUP
  RTLD_WORLD 
  */
  • dlsym,在打开的库中查找符号的值。
void *dlsym(void *handle, const char *symbol);
  • dlclose,卸载一个已经加载的模块。
int dlclose(void *handle);
  • dlerror,返回一个描述最后一次调用dlopen、dlsym,或dlclose的错误信息的字符串。
char *dlerror(void);

gcc -fPIC、-shared选项

如果想创建一个动态链接库,可以使用 GCC 的-shared选项。输入文件可以是源文件、汇编文件或者目标文件。

另外还得结合-fPIC选项。-fPIC 选项作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code)。

这样一来,产生的代码中就没有绝对地址了,全部使用相对地址,所以代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的

例如,从源文件生成动态链接库:

 gcc -fPIC -shared libplugin.c -o libplugin.so

实例

使用 gcc 将 libshared.c 、libplugin.c 各编译成共享对象文件libshared.so、libplugin.so

Makefile

executable: main.c libshared.so libplugin.so
	$(CC) -pie -fPIC -L. main.c -lshared -ldl -o executable

libshared.so: libshared.c
	$(CC) -fPIC -shared -rdynamic libshared.c -o libshared.so

libplugin.so: libplugin.c
	$(CC) -fPIC -shared libplugin.c -o libplugin.so

clean:
	rm -f executable libshared.so libplugin.so testcase.zip

run: executable
	./run.sh

zip: executable
	rm -f testcase.zip
	zip testcase.zip executable libshared.so libplugin.so run.sh


.PHONY: run clean zip

libplugin.c它有一个简单的函数调用extern函数libshared_get_value()。它不针对链接libshared.so

//libplugin.c
#include <stdio.h>

extern int libshared_get_value();

void plugin_function() {
	printf("plugin: value = %d\n", libshared_get_value());
}

libshared.c其中包含功能libshared_get_value()。它是作为名为的共享库构建的libshared.so。

int libshared_get_value()
{ 
	return 42; 
}

使用sh run.sh运行可执行文件。

LD_LIBRARY_PATH=. ./executable

main.c与libshared.so和链接libshared_get_value()。

#include <dlfcn.h>
#include <stdio.h>

typedef void (*plugin_function_t)();

extern int libshared_get_value();

int main(int argc, char *argv[]) 
{
	void* plugin_handle = dlopen("./libplugin.so", RTLD_LAZY);
	if (plugin_handle == NULL) {
		printf("dlopen failed: %s\n", dlerror());
		return 1;
	}
	plugin_function_t plugin_function = dlsym(plugin_handle, "plugin_function");
	if (plugin_function == NULL) {
		printf("plugin_function not found\n");
		return 1;
	}

	printf("main: value = %d\n", libshared_get_value());
	plugin_function();

	return 0;
}

在这里插入图片描述

我们使用dlopen打开一个.so,并将其加载到进程的地址空间,完成初始化过程。当库被装入后,可以把 dlopen() 返回的句柄作为给 dlsym() 的第一个参数,以获得符号在库中的地址。使用这个地址,就可以获得库中特定函数的指针,并且调用装载库中的相应函数。

总结

上面我们使用dlopen、dlsym、dlclose加载动态链接库,为了使程序方便扩展,具备通用性,可以采用插件形式,这种情况在开发中最常见。

而Linux 下动态链接库的文件后缀为.so,它是一种特殊的目标文件,可以在程序运行时被加载进来。

使用动态链接库的优点是:程序的可执行文件更小,便于程序的模块化以及更新,同时,有效内存的使用效率更高。

在这里插入图片描述

欢迎关注微信公众号【程序猿编码】,添加本人微信号(17865354792),回复:领取学习资料。或者回复:进入技术交流群。网盘资料有如下:

在这里插入图片描述

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐