动态库/共享库Shared Object

动态库或者称为共享库,是共享代码的另外一种实现方式。
其特征是在程序实际运行时,动态地加载所调用的函数或变量等符号,这样可以减小应用程序文件的尺寸;而且遇到功能升级或者修复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这个结构体中定义自己的各种功能。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

yilonglucky

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

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

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

打赏作者

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

抵扣说明:

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

余额充值