持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情
前面写有文章探讨动态库环境变量传递问题,本文介绍加载不同版本动态库的函数调用情况。
问题提出
某工程使用到动态库,由于动态库存在不同版本,因此都会加载,后按需调用。为了观察“按需调用”情况,模拟实际使用情况,但作了简化,只观察某一变量的变化情况。
工程代码
设计思路
工程比较简单,分应用程序和动态库,但源码在同一目录,只是通过 Makefile 编译得到不同的目标文件。动态库的不同版本,版本号和累加的变量cnt
不同。每次编译,需将得到的动态库改名,即使用文件文件名区别不同的版本:libfoobar_0.so
、libfoobar_1.so
、libfoobar_2.so
。。
在测试函数中,创建不同线程,根据动态库文件名调用动态库。
实现代码
dl.cpp实现代码:
// 全局变量,库中没有多线程,故不加锁
static int cnt = 0; // 0表示版本0,1表示版本1,等
int GetVersion(char *version)
{
int ver = 2;
sprintf(version, "ver: 0.%d", ver);
return 0;
}
int Foo()
{
printf("Foo...\n");
return 0;
}
int Bar()
{
printf("Bar...\n");
return 0;
}
DL_API_t gAPI = {
.name = "MyLib",
GetVersion,
Foo,
Bar,
};
///////////////
int foo()
{
printf("foo22...\n");
cnt++; // 累加1,下同
return 0;
}
int bar()
{
printf("bar22...\n");
cnt++;
return 0;
}
int getcnt()
{
return cnt;
}
int init()
{
cnt = 100;
return 0;
}
int uninit()
{
cnt = 0;
return 0;
}
测试代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <string.h>
#include <errno.h>
#include <thread>
#include "dl.h"
typedef int (*pfoo_t)();
pfoo_t pfoo;
void* myfunc(int a)
{
char filename[128] = {0};
sprintf(filename, "./libfoobar_%d.so", a);
void* handle = dlopen(filename, RTLD_LAZY); // 必须加路径
if (handle == NULL)
{
printf("open %s failed.\n", filename);
return NULL;
}
printf("load %s handler: %p\n", filename, handle);
char version[64] = {0};
DL_API_t* aLib = (DL_API_t*)dlsym(handle, "gAPI");
if (!aLib)
{
printf("dlsym failed: %s\n", dlerror());
return NULL;
}
aLib->GetVersion(version);
printf("ver: %s\n", version);
#if 0
if (aLib->Foo)
{
aLib->Foo();
}
#endif
#if 0
pfoo = (pfoo_t)dlsym(handle, "init");
if (pfoo)
{
pfoo();
}
#endif
pfoo = (pfoo_t)dlsym(handle, "foo");
if (pfoo)
{
pfoo();
}
// 复用指针
pfoo = (pfoo_t)dlsym(handle, "bar");
if (pfoo)
{
pfoo();
}
pfoo = (pfoo_t)dlsym(handle, "getcnt");
if (pfoo)
{
int ret = pfoo();
printf("cnt: %d\n", ret);
}
dlclose(handle);
return NULL;
}
int main(void)
{
printf("so test...\n");
void* handle = dlopen("./libfoobar.so", RTLD_LAZY); // 必须加路径
if (handle == NULL)
{
printf("open failed.\n");
//return -1;
}
printf("in main handler: %p\n", handle);
#define TEST_THREAD_NUM 3
int threadnum = TEST_THREAD_NUM;
std::thread threads[TEST_THREAD_NUM];
for(int i = 0; i < threadnum; i++)
{
threads[i] = std::thread(myfunc, i);
sleep(2); // 让不同线程错开运行
}
// 线程不一定占满,故要判断再join
for (auto& t: threads) {
if (t.joinable())
t.join();
}
if (handle) dlclose(handle);
return 0;
}
动态库里的接口函数,在形式设计上是相同的:参数为空,返回值为空,因此在测试代码中可以复用函数指针。 对于使用dlopen
打开动态库、用dlsym
获取动态库函数接口的做法,可以不提供头文件,提前是函数封装得比较,比如提供纯粹功能接口,而不对外开放结构体。
以上两点在前面的文章没有提到,此处特意说明。
测试
测试结果:
so test...
in main handler: 0x1292060
load ./libfoobar_0.so handler: 0x7f26d4000910
ver: ver: 0.0
foo22...
bar22...
cnt: 2
load ./libfoobar_1.so handler: 0x7f26d4000910
ver: ver: 0.1
foo22...
bar22...
cnt: 3
load ./libfoobar_2.so handler: 0x7f26d4000910
ver: ver: 0.2
foo22...
bar22...
cnt: 4
根据设计思路,libfoobar_0.so
中变量初始化为0,所以版本号为0.0
,经过2个函数累加,最终值为2
。类似地,libfoobar_1.so
初始化为1,累加后的值为3,libfoobar_2.so
初始化为2,累加后的值为4。
小结
某些应用场合中,会存在“相同”动态库的不同版本,本文对此进行测试。像申请内存、释放,多线程等情况没有深入研究。从输出日志看,加载不同动态库,其返回的句柄值是一样的,但的确能体现出不同的版本,此为何原因,暂时不知道。