多个动态库代码构建
1、动态库现在在我们的开发中用的原来越多,当然好处不需多说。还是扯扯吧,呵呵!
linux应用层程序开发中系统框架的搭建有两种:
(1)采用多进程的方式,然每个独立的进程干各自的事情,就如同我们日程生活中大家的工作岗位各自分工一样,然后需要打交道的时候用进程间通讯来便可以了,同时还有一个最大的优点,维护的人只需要关注自己的这个进程便可无须去了解其他的进程在干什么,linux有了多进程这个东西所以它才这么火,相信大多数公司都在这样用。来这个公司一开始我就倡导这样来做,可是遗憾的事,我还是打酱油的,所以还是想用单进程干所有的活。那没办法就想出来了另一种程序框架采用动态库的方式。
(2)采用动态库的方式,单进程设计的话必须得采用的方式。动态库就跟每个进程一样称为一个模块,每个动态库模块做各自的事情这样也是有利于代码的维护和开发。相比静态库,动态库直接导入便可,而静态库每次都得重新编译进程因为它是嵌入到进程中了必须绑定,而动态是独立。动态库可以灵活加载,不需要的库你可以不加载进去,比如一个特殊的功能只有一个客户用,那就可以控制加载的时候其它的不加载而这个客户给他做个加载。不加载的好处当然是节约系统内存了。省一点是一点了。
2、如果遇到多个动态库可能有10个8个的话,咋整?怎么样代码才能写的优雅和美观些呢,比如dlsym你要挨着个每个函数都做判断吗?累死人了吧?
所以首先要规划,提出来共性的东西。框架搭建好其它的你写你的函数代码就可以了。
(1)必须得有个库作为入口来动态加载剩余的其它库,所以这个库就所谓的"带头大哥",这个库一般也就是进程一开始必须得先调起来,一般做一些基本的系统初始化之类的工作。可以叫它基础库。
(2)各个动态库的共性:每个动态库模块都有自己的函数名称和对应的模块名称,那是不是可以定义个一个const 的全局结构体变量,把名称和函数指针统一放到这个全局变量里。相当于把函数名称统一管理起来,你后面操作的时候只需要操作这个全局结构体的函数指针就可以了。名称就是每个模块的名称。当然这个定义最适合的位置是在各自的库里面。
(3)基础库加载各个动态库的实现:每个库有了一个全局的变量存放着库的名称和库里面的函数名称,那基础库就可以dlopen每个库然后去dlsym这个全局的变量了,还要判断每个函数吗?别那么麻烦了,直接判断下库的名称就行了,函数等进程调用的时候去判断这个全局变量指向的函数指针是不是空就可以了。不能用就是不能用了。
(4)代码实现思路:根据上面的三点,
要加载的动态库:要定义一个全局的变量,把模块名称和函数指针地址保存起来。
基础库:
dlopen打开需要加载的库,并且dlsym每个库里的这个变量,这里说一下,这个变量可以每个库里面的名称可以都是一样的,因为库是独立的。这样基础库dlsym就可以简单了。
动态库加载后,函数指针是保存在基础库中,所以调用的函数必须得是在基础库中实现,所以基础库中得有结构体指针定义,而这个指针的定义要跟各个动态库的定义一致,因为函数的实现是通过基础库函数指针传递入参的。
3、上代码
Ⅰ 基础库代码实现要统一管理的几个地方:(1)全局变量统一名称,(2)加载库统一用宏来实现,name当做入参便可以统一管理。(3)定义结构体函数指针用来约定结构体指向的函数,原则是要跟子库的定义一致。
base.h
#define LIB_OBJECT(name) const name##_t LibObj //每个库都调用声明一个全局变量包括结构体的名称和函数指针,如下lib1~3的定义,以便统一名称
/*定义结构体指针,定义个函数宏把dlsym获取到的函数指针赋值给各个结构体指针,同时对模块的名称做判断*/
#define LIB_LOADMODULE(name) \
static const name##_t *LibObj= NULL; \
int name##_LoadModule(const void *Object) \ //宏参数的名称作为函数名称
{ \
int iRet; \
\
if (theApiObj) \
iRet= -EEXIST; \
else { \
LibObj= (typeof(LibObj))Object; \
if (!strcmp(LibObj->module, #name)) \ //判断模块的名称和入参的名称是否一致
iRet= 0; \
else { \
LibObj= NULL; \
iRet= -EPERM; \
} \
} \
return iRet; \
}
typedef struct {
const char *module;
int (*LIB1_GetModuleVer)(char *pszVer);
int (*LIB1_Set)(const ST_SET *st_set);
int (*LIB1_Open)(const ST_Open *st_open);
int (*LIB1_Init)(const ST_INIT *st_init) ;
int (*LIB1_Check)(void) ;
int (*LIB1_Connect)(ST_CONNECT *st_connect);
}llib1_t;
typedef struct {
const char *module;
int (*LIB2_GetModuleVer)(char *pszVer);
int (*LIB2_Set)(const ST_SET *st_set);
int (*LIB2_Open)(const ST_Open *st_open);
int (*LIB2_Init)(const ST_INIT *st_init) ;
int (*LIB2_Check)(void) ;
int (*LIB2_Connect)(ST_CONNECT *st_connect);
}llib2_t;
typedef struct {
const char *module;
int (*LIB3_GetModuleVer)(char *pszVer);
int (*LIB3_Set)(const ST_SET *st_set);
int (*LIB3_Open)(const ST_Open *st_open);
int (*LIB3_Init)(const ST_INIT *st_init) ;
int (*LIB3_Check)(void) ;
int (*LIB3_Connect)(ST_CONNECT *st_connect);
}llib3_t;
base.c
/*宏定义方便代码集中管理*/
#define RegisterModule(name) \
{ \
.libname = #name, \
.LoadModule = name##_LoadModule, \
}
static const ModuleTable_t ModuleTable[] = {
RegisterModule(lib1),
RegisterModule(lib2),
RegisterModule(lib3),
};
/*开始加载动态库,并且判断模块名称是否一致*/
handle = dlopen(modulename, RTLD_LAZY | RTLD_GLOBAL);
if (! handle) {
api_debug(DBG_DEBUG, "can't open \"%s\"\n", modulename);
retval = -EPERM;
goto exit_entry;
}
LibObj= dlsym(handle, "LibObj");
if (!LibObj) {
api_debug(DBG_DEBUG, "can't resolve \"%s\"\n", modulename);
retval = -EFAULT;
goto exit_entry;
}
for (i=0; i<sizeof(ModuleTable)/sizeof(ModuleTable[0]); i++) {
api_debug(DBG_DEBUG, "libname = %s, modulename = %s\n", ModuleTable[i].libname, modulename);
if (strstr(modulename,ModuleTable[i].libname)) { //判断是否是基础库里要加载的库名称,目前设计的是base里面固化了要加载什么库,可以设计成灵活些
retval = ModuleTable[i].LoadModule(LibObj); //调用.h文件中的宏定义来判断库里面的内容名称是否和base库要加载的库的名称一致
api_debug(DBG_DEBUG, "retval = %d\n", retval);
goto exit_entry;
}
}
exit_entry:
if (retval && handle) {
dlclose(handle);
if (retval == -EEXIST)
retval = 0;
}
lib1.c 这个.c在基础库中,因为是基础库往外提供函数接口,其它的都被隐藏了。
/*声明结构体变量和动态加载的库函数,下面的函数就是使用的一个情况,判断函数指针指向的函数地址是否为NULL,这样就可以直接操作子库里面的函数了。*/
LIB_LOADMODULE(lib1)
int GetVer(char *pszVer)
{
int retval;
if (LibObj&& LibObj->LIB1_GetModuleVer)
retval = LibObj->LIB1_GetModuleVer(pszVer);
else {
retval = -1;
}
return retval;
}
Ⅱ各个加载库的内部代码实现:(1)定义各个函数的实现(2)定义结构体函数指针指向的函数地址和模块的名称
#define LIBAPI(func) .func = _##func
LIB_OBJECT(lib1) = {
.module = "lib1",
LIBAPI(LIB1_GetModuleVer),
LIBAPI(LIB1_Set),
LIBAPI(LIB1_Open),
LIBAPI(LIB1_Init),
LIBAPI(LIB1_Check),
LIBAPI(LIB1_Connect),
};
4、总结:
基础库提供各个lib定义的函数名称,然后基础类的函数名称根据函数指针地址去挨个调用子类中的函数,其实外面看到的只有基础类的函数,子类的都是不透明的。如果动态库有好多的情况下,这篇文章讲解的实现技巧很好用。