1️⃣ 概述

在 Linux 系统中,C++ 程序可以通过共享库(.so 文件)实现代码复用。
我们可以用 dlopen()dlsym() 等函数在运行时动态加载 .so,实现类似“插件”的机制。


2️⃣ 示例目标

我们将完成:

  • 一个共享库 dbclient_mysql.so

  • 一个可执行文件 utestsym

  • 主程序动态加载 .so 并调用其中的函数


3️⃣ 文件结构

project/
├── dbclient_mysql.cpp   # 共享库源文件
└── utestsym.cpp                   # 主程序源文件

4️⃣ 编写共享库

dbclient_mysql.cpp

// dbclient_mysql.cpp
#include <iostream>
#include <string>

// 为了让函数名在 C++ 下不被 name mangling,必须加 extern "C"
extern "C" {

// 模拟数据库连接函数
int db_connect(const char* host, int port) {
    std::cout << "[dbclient_mysql] Connecting to " << host
              << ":" << port << std::endl;
    return 0; // 0 表示成功
}

// 模拟断开连接函数
void db_disconnect() {
    std::cout << "[dbclient_mysql] Disconnected." << std::endl;
}

// 模拟版本查询函数
const char* db_version() {
    return "mysql client 1.0";
}

} // extern "C"

编译共享库

g++ -std=c++11 -fPIC -shared -g -o dbclient_mysql.so dbclient_mysql.cpp

说明:

  • -fPIC:生成位置无关代码(共享库必须)

  • -shared:输出为 .so 文件


5️⃣ 编写主程序

utestsym.cpp

// utestsym.cpp
#include <iostream>
#include <dlfcn.h> // 动态库操作接口

int main()
{
    /* 1. 加载共享库 
     | 参数          | 符号解析时机     | 优点                  | 缺点                    |
     | -----------   | ----------      | -----------           | ------------            |
     | `RTLD_LAZY`   | 第一次调用时     | 加载快,可延迟错误     | 调用时才报错,调试困难   |
     | `RTLD_NOW`    | dlopen 调用时    | 早期发现错误,调试方便 | 加载慢,可能浪费解析时间 |
    */
    void *handle = dlopen("./dbclient_mysql.so", RTLD_NOW);
    if (!handle)
    {
        std::cerr << "无法加载库: " << dlerror() << std::endl;
        return 1;
    }

    dlerror(); // 清空之前的错误

    // 2. 查找函数符号
    typedef int (*db_connect_func)(const char *, int);
    typedef void (*db_disconnect_func)();
    typedef const char *(*db_version_func)();

    db_connect_func db_connect = (db_connect_func)dlsym(handle, "db_connect");
    db_disconnect_func db_disconnect = (db_disconnect_func)dlsym(handle, "db_disconnect");
    db_version_func db_version = (db_version_func)dlsym(handle, "db_version");

    const char *err = dlerror();
    if (err)
    {
        std::cerr << "查找符号失败: " << err << std::endl;
        dlclose(handle);
        return 1;
    }

    // 3. 调用库中函数
    std::cout << "版本号: " << db_version() << std::endl;
    db_connect("localhost", 3306);
    db_disconnect();

    // 4. 卸载库
    dlclose(handle);
    return 0;
}

编译主程序

g++ -std=c++11 -g -o utestsym utestsym.cpp -ldl

⚠️ 注意:-ldl 必须加,用于链接动态加载库。


6️⃣ 运行程序

./utestsym

输出结果:


7️⃣ 调试与补充

查看 .so 导出的符号

nm -D dbclient_mysql.so | grep db_

结果示例:

如果库不在当前目录

可以设置环境变量:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

✅ 总结

步骤 内容 命令
1️⃣ 编译共享库 g++ -std=c++11 -fPIC -shared -g -o dbclient_mysql.so dbclient_mysql.cpp
2️⃣ 编译主程序 g++ -std=c++11 -g -o utestsym utestsym.cpp -ldl
3️⃣ 运行 ./utestsym

💬 学习要点

  • 共享库函数需使用 extern "C" 导出。

  • dlopen() 加载 .sodlsym() 查找函数;dlclose() 卸载。

  • -ldl 是链接动态加载库的必需参数。

  • 此机制可用于插件系统、数据库驱动加载等模块化场景。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐