c - 在参数 typedef 更改时重建动态库

标签 c linux shared-libraries

假设,我有一个 C 结构,DynApiArg_t

typedef struct DynApiArg_s {
    uint32_t m1;
    ...
    uint32_t mx;
} DynApiArg_t;

这个结构的指针作为参数传递给函数 say

void DynLibApi(DynApiArg_t *arg)
{
     arg->m1 = 0;
     another_fn_in_the_lib(arg->mold); /* May crash here. (1) */
}

存在于动态库 libdyn.so 中。此 API 通过 dlopen/dlsym 调用过程从可执行文件调用。

如果此动态库更新到版本 2,其中 DynApiArg_t 现在有新成员,比如 m2,如下所示:

typedef struct DynApiArg_s {
    uint32_t m1;
    OldMbr_t *mold;
    ...
    uint32_t mx;
    uint32_t m2;
    NewMbr *mnew;
} DynApiArg_t;

如果没有完全重建通过 dlopen/dlsym 调用此 API 的可执行文件或其他库,每次调用此 API 时,我都会看到进程崩溃,这是由于任何成员的某些取消引用在结构中。我知道访问 m2 可能是个问题。但是可以看到像下面这样访问成员 mold 会导致崩溃。

typedef void (*fnPtr_t)(DynApiArg_t*);


void DynApiCaller(DynApiArg_t *arg)
{
     void *libhdl = dlopen("libdyn.so", RTLD_LAZY | RTLD_GLOBAL);
     fnPtr_t fptr = dlsym(libhdl, "DynLibApi");
     fnptr(arg); /* actual call to the dynamically loaded API (2) */
}

在通过 fnptr 调用 API 时,在标记为 (2) 的行中,当在 (1) 处访问旧的/现有成员(在 lib 的 v1 中,最初编译 DynApiCaller 时)时,它恰好是任何垃圾有时甚至是 NULL

每次更新依赖库时,如果不完全重新编译可执行文件,处理此类更新的正确方法是什么?

我见过用符号符号命名的库,版本号如 libsolid.so.4。有什么与此版本控制系统相关的东西可以帮助我吗?如果有的话,您能指出这些文件的正确文档吗?

最佳答案

有很多方法可以解决这个问题:

  1. 在动态库名称中包含 API 版本。

    您使用 dlopen("libfoo.so.4") 而不是 dlopen("libfoo.so")。库的不同主要版本本质上是独立的,可以在同一系统中共存;因此,该库的包名称将是例如libfoo-4。您可以同时安装 libfoo.so.4libfoo.so.5。次要版本,例如 libfoo-4.2,安装 libfoo.so.4.2,并将 libfoo.so.4 符号链接(symbolic link)到 libfoo.so .4.2.

  2. 最初用零填充定义结构(在早期版本的库中要求为零),并让更高版本重用填充字段,但保持结构大小相同。

    <
  3. 使用版本符号名称。这是一个 Linux 扩展,使用 dlvsym() .单个共享库二进制文件可以实现同一动态符号的多个版本。

  4. 使用 resolver functions在加载时确定符号。这允许例如要在运行时选择的硬件架构优化函数变体,但对于基于 dlopen() 的方法而言用处不大。

  5. 使用结构来描述库 API,并使用版本化函数来获取/初始化该 API。

    例如,您的库的版本 4 可以实现

     struct libfoo_api {
         int   (*func1)(int arg1, int arg2);
         double *data;
         void  (*func2)(void);
         /* ... */
     };
    

    并且只导出一个符号,

     int libfoo_init(struct libfoo_api *const api, const int version);
    

    调用该函数将使用支持的符号初始化 api 结构,假设该结构对应于指定版本。一个共享库可以支持多个版本。如果不支持某个版本,它会返回失败。

    这对于插件类型的接口(interface)特别有用(尽管 _init 函数更可能调用应用程序提供的功能注册函数,而不是填充结构),因为单个文件可以包含针对多个版本的优化功能,针对多个兼容的硬件架构(例如,具有不同 SSE/AVX/AVX2/AVX512 支持的 AMD/Intel 架构)进行了优化。

请注意,上述实现细节可以“隐藏”在头文件中,从而使使用共享库的实际 C 代码更加简单。它还有助于使相同的 API 在多个操作系统上工作,只需更改头文件以使用在该操作系统上最有效的方法,同时保持实际的 C 接口(interface)相同。

关于c - 在参数 typedef 更改时重建动态库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45467653/

相关文章:

c - 将 SA_NOCLDWAIT 设置为 SIGCHLD 后的 Linux system()

linux - 无论如何,是否可以使用 Hudson 将 .war 或 .jar 从一台服务器传输到另一台服务器?

C++:free():下一个大小无效(快速)

Jenkins 共享库 src 类 - 无法解析类

c - 将图像保存在 C 中

c - "%lld\n"是什么意思?

c++ - 在 Linux 上管理共享库的常用方法是什么?

java - 是否可以在不重新启动应用程序的情况下重新加载 WebSphere Application Server (WAS) 中的共享库?

c - 给指针char赋值?

c - Openssl 中 RSA 结构的大小和公钥的大小