28
02/12
Linux动态共享库
前几周甘雨童鞋讨论分享了linux连接器的知识,做为linux菜鸟都算不上的我来说压力很真有点大,尤其是后来调汇编各种跳,作为汇编和组成原理接近满分的男人有点无地自容呀,阳春白雪,曲高和寡呀。但是有几点还是留下了比较深刻的影响的,所以向甘雨童鞋借了书进行学习学习。这里要说的不是连接器的知识,刚开始看,看的差不多了,写篇水文。今天是要说的是当时顺带提到的动态共享库的东西,这里不会去扣原理和内部实现,比较肤浅的说一下使用。
学过win32编程或者MFC之类的都知道windows平台有dll,这个东西,中文叫动态链接库,使用起来相当方便,差不多一年没写win32程序了,依稀还记得LoadLibrary,GetProcAddress,FreeLibrary,使用起来很傻瓜,微软之后在基础上搞出了COM组件原理,这就是后话了。这里说要的是Linux的动态共享库,可以理解为和dll类似的东西,但是有所不同。那天分享上说linux下的动态共享库不能在不同进程间共享数据,比如A对某个数据+1,对B进程无效。但是我依稀记得Windows下的dll是可以实现共享的,并且这是一种进程间通信的手段?这个问题不在此探讨了,不是今天的重点
==========================================正文出现====================================================
这是一篇linux动态动态共享库的入门文章,因项目中使用到所以进行了一下简单的学习,顺带写了一下代码,先给出代码然后做解释吧,其实没有特别高深的东西。
dll1.c
#ifndef __DLL1__
#define __DLL1__
#include <stdio.h>
__attribute((constructor))
void dll1_init(void) {
printf("dll1_init\n");
}
__attribute((destructor))
void dll1_finit(void) {
printf("dll1_finit\n");
}
void whats_up() {
printf("whats_up\n");
}
int add(int a, int b) {
return a - b;
}
#endif
gcc dll1.c -fPIC -shared -Wall -g -o libdll1.so
将dll.c生成动态贡献库dll.so,简单解释一下参数,-fPIC表示生成和位置无关代码,-shared表示生成共享库。
下面说一下代码,其实一般的c代码没啥不同哈,但是你会先多了2个方法,其实没有这2个方法,也是没有问题的哈,这2个方法的作用是加载前调用,释放时调用,比如你加载前要预处理一些东西呀,释放时处理一些东西呀,哈哈,是不是想到了windows的DllMain是的作用是类似的,Linux动态贡献库没有DllMain这种东西,动态加载其实提供了2个方法_init()和_finit(),悲剧的是这2个被gcc占用了,所以要实现相同的功能只能绕道一下,使用gcc的东西了。
dll2.c
#ifndef __DLL2__
#define __DLL2__
#include <stdio.h>
__attribute((constructor))
void dll1_init(void) {
printf("dll1_init\n");
}
__attribute((destructor))
void dll1_finit(void) {
printf("dll1_finit\n");
}
void whats_up();
void nothing() {
printf("nothing, but test\n");
}
void ans_whats_up() {
whats_up();
printf("haha...you guess\n");
}
int sub(int a, int b) {
return a - b;
}
#endif
gcc dll2.c -fPIC -shared -Wall -g -o libdll2.so
看上面这段代码其实和前面没啥区别 不同的是声明了一个方法whats_up() 嗯,就是dll1里面的,下面会用它来说说明一些事情。
testmain.c
#ifndef __TEST_MAIN__
#define __TEST_MAIN__
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#ifndef CMDLINE
typedef int (*add_func)(int a, int b);
typedef int (*sub_func)(int a, int b);
typedef void (*ans_whats_up_func)();
void test_dlopen(int dlopen_flag) {
char* err_info = NULL;
void* handler_dll1 = NULL;
void* handler_dll2 = NULL;
{
handler_dll1 = dlopen("libdll1.so", dlopen_flag);
if (handler_dll1 == NULL) {
fprintf(stderr, "handler_dll1 line %d:\n%s\n", __LINE__, dlerror());
exit(1);
}
dlerror();
add_func add = dlsym(handler_dll1, "add");
if ((err_info = dlerror()) != NULL) {
fprintf(stderr, "handler_dll1->dlsym line %d:\n%s\n", __LINE__, dlerror());
exit(1);
}
int add_ret = add(10, 20);
printf("add_ret: 10 + 20 = %d\n", add_ret);
}
{
handler_dll2 = dlopen("libdll2.so", RTLD_NOW|RTLD_GLOBAL);
if (handler_dll2 == NULL) {
fprintf(stderr, "handler_dll2 line %d:\n%s\n", __LINE__, dlerror());
exit(1);
}
dlerror();
sub_func sub = dlsym(handler_dll2, "sub");
if ((err_info = dlerror()) != NULL) {
fprintf(stderr, "handler_dll2->dlsym line %d:\n%s\n", __LINE__, dlerror());
exit(1);
}
int sub_ret = sub(10, 20);
printf("sub_ret: 10 - 20 = %d\n", sub_ret);
}
{
dlerror();
ans_whats_up_func ans_whats_up = dlsym(handler_dll2, "ans_whats_up");
if ((err_info = dlerror()) != NULL) {
fprintf(stderr, "ans_whats_up line %d:\n%s\n", __LINE__, dlerror());
exit(1);
}
ans_whats_up();
}
dlclose(handler_dll2);
dlclose(handler_dll1);
printf("over\n");
}
#ifdef DLOPEN_GLOBAL
void test1_dlopen_global() {
test_dlopen(RTLD_NOW|RTLD_GLOBAL);
}
#else
void test1_dlopen_local() {
test_dlopen(RTLD_NOW|RTLD_LOCAL);
}
#endif
#else
void test2_in_cmdline() {
whats_up();
nothing();
ans_whats_up();
}
#endif
int main(int argc, char* argv[]) {
#ifndef CMDLINE
#ifdef DLOPEN_GLOBAL
test1_dlopen_global();
#else
test1_dlopen_local();
#endif
#else
test2_in_cmdline();
#endif
return 0;
}
#endif
gcc testmain.c -L. -ldll1 -ldll2 -D CMDLINE -Wall -g -o testmain1.out
gcc testmain.c -rdynamic -ldl -Wall -g -o testmain2.out
gcc testmain.c -rdynamic -ldl -D DLOPEN_GLOBAL -Wall -g -o testmain3.out
这代码就有点乱了哈 主要是使用了编译宏,把种情况全部写在一个代码里面了,做一下简单的解释,testmain1.out是使用编译时把动态库指定链接,下面2种是类似LoadLibrary的方式进行,对应的方法是dlopen,dlsym,dlclose另外还有一个输出错误信息和清除错误信息作用的dlerror,下面2种情况的区别是dlopen的参数的不同,dlopen的RTLD_NOW立即加载所有的描述符号等,对应的是RTLD_LAZY,RTLD_GLOBAL表示该加载可以被之后加载的动态共享库使用,RTLD_LOCAL与之相反,具体可以man文档,testmain2.out是使用RTLD_LOCAL发现ans_whats_up调用whats_up时,找不到描述符号,而testmain3.out一切正常。另外运行3个程序还会发现调用test_init和test_finit的不同时间哦 testmain1.out在main之前被调用和main退出时被调用了,而其他2个则是在调用dlopen时被调用和dlclose时被调用
