多版本动态库的变量测试

79 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情

前面写有文章探讨动态库环境变量传递问题,本文介绍加载不同版本动态库的函数调用情况。

问题提出

某工程使用到动态库,由于动态库存在不同版本,因此都会加载,后按需调用。为了观察“按需调用”情况,模拟实际使用情况,但作了简化,只观察某一变量的变化情况。

工程代码

设计思路

工程比较简单,分应用程序和动态库,但源码在同一目录,只是通过 Makefile 编译得到不同的目标文件。动态库的不同版本,版本号和累加的变量cnt不同。每次编译,需将得到的动态库改名,即使用文件文件名区别不同的版本:libfoobar_0.solibfoobar_1.solibfoobar_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。

小结

某些应用场合中,会存在“相同”动态库的不同版本,本文对此进行测试。像申请内存、释放,多线程等情况没有深入研究。从输出日志看,加载不同动态库,其返回的句柄值是一样的,但的确能体现出不同的版本,此为何原因,暂时不知道。