动态库或者称为共享库,是共享代码的另外一种实现方式。
其特征是在程序实际运行时,动态地加载所调用的函数或变量等符号,这样可以减小应用程序文件的尺寸;而且遇到功能升级或者修复bug时只要更新库文件就可以了,不必更新应用程序。
在windows环境中,对应的动态库常以*.dll文件形式存在,意为Dynamic Link Library。
在linux环境下,生成动态库也很简单:添加编译选项,直接将*.c编译成*.so
例如:
gcc -shared s1.c -o so1.so
而使用动态库文件时,调用函数使用dlopen,dlsym等函数动态加载so文件,就能获取其中的符号了。
例一:
我们现在csdn.c文件中定义一个函数fry_it和一个变量data1
int fry_it(int *n)
{
int t;
if(n)
{
t = *n;
t *= 2;
*n = t;
}
return 0;
}
int data1 = 3;
然后我们就可以把这个源文件制作成so,命令如下:
test@test:~$ gcc -shared csdn.c -o libcsdn.so
然后我们再来写一个应用程序源代码main.c,通过访问这个so来使用其中的函数和变量
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main()
{
void *pso = NULL;
int (*pf)(int*);
int *pdata;
printf("%s begin\n", __func__);
pso = dlopen("./libcsdn.so", RTLD_LAZY);
if(!pso)
{
printf("%s\n", dlerror());
return 0;
}
pf = dlsym(pso, "fry_it");
if(pf)
{
printf("fry_it() symbol found\n");
}
else
{
printf("fry_it() symbol not found\n");
goto out;
}
pdata = (int*) dlsym(pso, "data1");
if(!pdata)
{
printf("%s\n", dlerror());
goto out;
}
printf("data=%d\n", *pdata);
(*pf)(pdata);
printf("after fried data=%d\n", *pdata);
out:
if(dlclose(pso))
{
perror("dlclose failed");
}
printf("%s return\n", __func__);
return 0;
}
必要的,我们需要定义一个void*类型的指针pso,用来保存指向打开的so库的入口。
在这里例子中,还定义了函数指针pf和指向变量的指针pdata用来访问so中的符号。
在成功打开我们需要的so后,就可以通过pso来搜索库中我们需要的符号。符号的意思就是说函数名或者变量名。
把应用程序编译出来:
test@test:~$gcc main.c -ldl
由于其中使用了dlopen和dlsym等函数,所以在编译时还需要链接dl这个库。
来看下运行结果:
test@test:~$ ./a.out
main begin
fry_it() symbol found
data=3
after fried data=6
main return
例二:
通过例一我们看到了so的制作,使用等基本用法。但是这样使用so有很致命的缺点:
需要在应用程序中提前预制好药使用的符号。
例一中只用了简单的fry_it这一个函数和data1这一个变量,万一将来我们需要增加几百个函数,或者需要更新fry_it的参数类型怎么办?
万一将来需要增加data2,data3……data100,那么在应用程序中写100个dlsym(pso, “data……吗?
即使允许可以修改主程序,那么每改一个细节都要重新发布一次主程序吗?如果可以那么升级程序前还要卸载旧程序吗?
我们来看例二:
先将so源文件优化一下
test@test:~$ cat csdn0.c
#include <stdlib.h>
#include "csdn.h"
int double_it(int *n)
{
int t;
if(n)
{
t = *n;
t *= 2;
*n = t;
}
return 0;
}
csdn_t csdn = {NULL, double_it, 3};
把csdn_t的定义放在另外一个文件里,便于引用:
test@test:~$ cat *.h
typedef struct csdn_t
{
void *pso;
int (*fry_it)(int *);
int data;
}csdn_t;
制作so:
test@test:~$ gcc csdn0.c -shared -o libcsdn0.so
然后我们来看怎么使用:
test@test:~$ cat main0.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include "csdn.h"
int main()
{
void *pso = NULL;
csdn_t *pc = NULL;
printf("%s begin\n", __func__);
pso = dlopen("./libcsdn0.so", RTLD_LAZY);
if(!pso)
{
printf("%s\n", dlerror());
return 0;
}
pc = (csdn_t *)dlsym(pso, "csdn");
if(pc)
{
printf("csdn symbol found\n");
pc->pso = pso;
}
else
{
printf("csdn symbol not found\n");
goto out;
}
printf("data=%d\n", pc->data);
pc->fry_it( &pc->data);
printf("after fried data=%d\n", pc->data);
out:
if(dlclose(pso))
{
perror("dlclose failed");
}
printf("%s return\n", __func__);
return 0;
}
编译时记得引用dl这个lib:
test@test:~$ gcc main0.c -ldl
例三:
类似的我们来看android源码中HAL是怎么使用so的:
在android源码中,系统进程统通过函数hw_get_module来加载需要的so文件,其中的load函数如下:
58 /**
59 * Load the file defined by the variant and if successful
60 * return the dlopen handle and the hmi.
61 * @return 0 = success, !0 = failure.
62 */
63 static int load(const char *id,
64 const char *path,
65 const struct hw_module_t **pHmi)
66 {
67 int status;
68 void *handle;
69 struct hw_module_t *hmi;
70
71 /*
72 * load the symbols resolving undefined symbols before
73 * dlopen returns. Since RTLD_GLOBAL is not or'd in with
74 * RTLD_NOW the external symbols will not be global
75 */
76 handle = dlopen(path, RTLD_NOW);
77 if (handle == NULL) {
78 char const *err_str = dlerror();
79 ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");
80 status = -EINVAL;
81 goto done;
82 }
83
84 /* Get the address of the struct hal_module_info. */
85 const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
86 hmi = (struct hw_module_t *)dlsym(handle, sym);
87 if (hmi == NULL) {
88 ALOGE("load: couldn't find symbol %s", sym);
89 status = -EINVAL;
90 goto done;
91 }
92
93 /* Check that the id matches */
94 if (strcmp(id, hmi->id) != 0) {
95 ALOGE("load: id=%s != hmi->id=%s", id, hmi->id);
96 status = -EINVAL;
97 goto done;
98 }
99
100 hmi->dso = handle;
101
102 /* success */
103 status = 0;
104
105 done:
106 if (status != 0) {
107 hmi = NULL;
108 if (handle != NULL) {
109 dlclose(handle);
110 handle = NULL;
111 }
112 } else {
113 ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
114 id, path, *pHmi, handle);
115 }
116
117 *pHmi = hmi;
118
119 return status;
120 }
代码中HAL_MODULE_INFO_SYM_AS_STR就永远是HMI这个字符串,每个被调用的模块在HMI这个结构体中定义自己的各种功能。