{"id":28028647,"url":"https://github.com/abin-z/dynamiclibloader","last_synced_at":"2025-05-11T07:20:37.745Z","repository":{"id":273232858,"uuid":"918915227","full_name":"abin-z/DynamicLibLoader","owner":"abin-z","description":"跨平台的动态库显式加载器","archived":false,"fork":false,"pushed_at":"2025-04-16T08:01:12.000Z","size":42,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-16T10:00:30.901Z","etag":null,"topics":["cmake","cpp","dynamic-programming","explicit-loading","library"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/abin-z.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-01-19T07:54:52.000Z","updated_at":"2025-04-16T08:01:01.000Z","dependencies_parsed_at":"2025-01-19T17:25:20.042Z","dependency_job_id":"7d04cbd5-6cf3-4633-85dd-ab977158c3d1","html_url":"https://github.com/abin-z/DynamicLibLoader","commit_stats":null,"previous_names":["abin-z/dynamiclibloading","abin-z/dynamiclibloader"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abin-z%2FDynamicLibLoader","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abin-z%2FDynamicLibLoader/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abin-z%2FDynamicLibLoader/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abin-z%2FDynamicLibLoader/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/abin-z","download_url":"https://codeload.github.com/abin-z/DynamicLibLoader/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253529794,"owners_count":21922745,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cmake","cpp","dynamic-programming","explicit-loading","library"],"created_at":"2025-05-11T07:20:37.331Z","updated_at":"2025-05-11T07:20:37.738Z","avatar_url":"https://github.com/abin-z.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DynamicLibLoader 跨平台动态库加载器\n\n**DynamicLibLoader** 是一个跨平台的动态库加载器，提供了一个统一的 API 来显式加载动态库并调用其中的符号（函数）。支持 Windows 和 POSIX 系统（如 Linux、macOS），自动处理平台差异。\n\n### 核心特性概览:\n\n- ✅ **跨平台支持**: 兼容 Windows 和 POSIX (Linux/macOS).\n\n- ✅ **RAII 资源管理**: 动态库在构造时加载，在析构时卸载，避免资源泄露.\n\n- ✅ **错误处理**:  在加载库或符号失败时，抛出详细的 `std::runtime_error` 异常，并附带平台特定的错误消息.\n\n- ✅ **符号缓存**：`invoke()`支持符号缓存，提高符号加载效率.\n\n- ✅ **线程安全缓存**: `invoke()`成功调用后会缓存已加载的符号指针，提高调用效率.\n\n- ✅ **缓存与非缓存调用接口**: 提供 `invoke()`（自动缓存）和 `invoke_uncached()`（不缓存）两种调用方式.\n\n- ✅ **无依赖**：仅依赖标准库和系统库，不依赖任何第三方库.\n\n- ✅ **支持多种函数类型:**\n\n  | 类型名称             | 定义形式      | 说明                 |\n  | -------------------- | ------------- | -------------------- |\n  | 函数指针类型         | `T (*)(...)`  | 最常用的函数指针形式 |\n  | 引用函数指针类型     | `T (\u0026)(...)`  | 函数的左值引用类型   |\n  | 右值引用函数指针类型 | `T (\u0026\u0026)(...)` | 函数的右值引用类型   |\n  | 函数类型             | `T(...)`      | 直接使用函数类型     |\n\n### 使用方法\n\nHeader-only只需要将[`dynamic_library.hpp`](./application/dynamic_library.hpp)文件拷贝至你的项目引入即可使用:\n\n```cpp\n#include \"dynamic_library.hpp\"\n```\n\n### 基础案例\n\n1. **加载动态库并获取符号**\n\n```cpp\n#include \u003ciostream\u003e\n#include \"dynamic_library.hpp\"\nint main()\n{\n  try\n  {\n    dll::dynamic_library lib(\"path/to/your/library.so\");  // 加载动态库\n    auto func_add = lib.get\u003cint(int, int)\u003e(\"add\");        // 获取符号(函数),此符号必须在库中存在\n    func_add(1, 1);                                       // 调用符号(函数)\n  }\n  catch (const std::runtime_error \u0026e)\n  {\n    std::cerr \u003c\u003c \"Error: \" \u003c\u003c e.what() \u003c\u003c std::endl;\n  }\n}\n```\n\n2. **使用 `try_get()` 尝试获取符号**\n\n```cpp\nint main()\n{\n  dll::dynamic_library lib(\"path/to/your/library.so\");\n  // try_get尝试获取符号，如果符号不存在则返回 nullptr\n  auto func_add = lib.try_get\u003cint(int, int)\u003e(\"add\");\n  if (func_add)\n  {\n    func_add(1, 1);  // 如果符号加载成功，调用符号\n  }\n  else\n  {\n    std::cerr \u003c\u003c \"Failed to load function!\" \u003c\u003c std::endl;\n  }\n}\n```\n\n3. **使用 `invoke()` 简化调用过程**\n\n```cpp\nint main()\n{\n  try\n  {\n    dll::dynamic_library lib(\"path/to/your/library.so\");\n    // 直接调用名为 \"add\" 的函数，该函数签名为 int(int, int)\n    int result = lib.invoke\u003cint(int, int)\u003e(\"add\", 10, 20);\n    std::cout \u003c\u003c \"Result: \" \u003c\u003c result \u003c\u003c std::endl;\n  }\n  catch (const std::runtime_error \u0026e)\n  {\n    std::cerr \u003c\u003c \"Error: \" \u003c\u003c e.what() \u003c\u003c std::endl;\n  }\n}\n```\n\n### 警告: 可能的未定义行为\n\n在获取函数符号时，**一定要确保你传入的函数类型和库中的函数签名完全一致.**\n\n如果提供的**函数签名**与动态库中**实际符号的签名**不一致，将导致**未定义行为**.\n\n假设正确的 `doubleAdd` 函数签名应该是: 是一个接受两个 `double` 参数并返回一个 `double` 的函数指针类型:\n\n```cpp\ndouble(double, double)   // doubleAdd 的函数类型(签名)\n```\n\n```cpp\ntry\n{\n  // 正常调用: symbol 是对的, 函数签名也正常\n  double ret = lib.invoke\u003cdouble(double, double)\u003e(\"doubleAdd\", 1.5, 3.0);\n\n  // 未定义行为: doubleAdd 只接受两个 double 参数，但此处传递了三个 double 参数，这会导致未定义行为\n  double ret2 = lib.invoke\u003cdouble(double, double, double)\u003e(\"doubleAdd\", 1.5, 3.0, 1.0);\n    \n  // 未定义行为: 因为 doubleAdd 符号要求两个 double 类型的参数，而调用时没有提供任何参数\n  double ret3 = lib.invoke\u003cdouble()\u003e(\"doubleAdd\");\n}\ncatch (const std::exception \u0026e)\n{\n  std::cerr \u003c\u003c \"invoke error: \" \u003c\u003c e.what() \u003c\u003c '\\n';\n}\n```\n\n### 仓库目录结构介绍\n\n- [`dynamic`](dynamic/)目录是一个独立动态库模块(目标是生成动态库`.so`或者`.dll`).\n- [`application`](application/)目录是独立程序, 其中会在代码中显式加载`dynamic`动态库.\n\n- 封装了跨平台的动态库显式加载模块: [`application/dynamic_library.hpp`](./application/dynamic_library.hpp)\n- 跨平台的动态库导出头文件: [`dynamic/include/dynamic/dll_export.h`](./dynamic/include/dynamic/dll_export.h)\n- 具体的使用案例请查看:  [`application/main.cpp`](./application/main.cpp)\n\n项目文件目录:\n\n```sh\n.\n├── application                     # 2.使用本库加载dynamic生成的动态库\n│   ├── CMakeLists.txt\n│   ├── dynamic_library.hpp\n│   └── main.cpp\n├── docs\n│   └── 动态库的加载方式介绍.md\n├── dynamic                         # 1.生成dynamic动态库供application调用\n│   ├── CMakeLists.txt\n│   ├── include\n│   │   └── dynamic\n│   │       ├── common.hpp\n│   │       ├── dll_export.h\n│   │       └── dynamic.h\n│   └── src\n│       └── dynamic.cpp\n└── README.md\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003e点击查看完整使用案例\u003c/summary\u003e\n\n\n```cpp\n#include \u003ciostream\u003e\n\n#include \"dynamic_library.hpp\"\n\n/*\n * 为了在没有头文件的情况下调用 libdynamic.so 中的内容，你需要使用 动态链接库的运行时加载机制，\n * 即通过 dlopen、dlsym 等函数（在 POSIX 系统上）或等效的方法（在 Windows 上，如 LoadLibrary 和 GetProcAddress）。\n */\n\n/// =========== 定义动态库中函数指针类型 start ===========\nusing sayHello_func  = void (*)();                // 函数指针类型\nusing intAdd_func    = int (\u0026)(int, int);         // 引用函数指针类型\nusing floatAdd_func  = float (\u0026\u0026)(float, float);  // 右值引用函数指针类型\nusing doubleAdd_func = double(double, double);    // 函数类型\n// 动态库中的struct\nstruct point_t\n{\n  double x;\n  double y;\n  double z;\n};\n\nusing getPoint_func = point_t (*)();\nusing printPoint_func = void (*)(point_t);\n/// =========== 定义动态库中函数指针类型 end ===========\n\nvoid func();\nvoid testNotExistSymbol(const dll::dynamic_library \u0026lib);\nint main()\n{\n  std::cout \u003c\u003c \"====================================================\" \u003c\u003c std::endl;\n  func();\n  std::cout \u003c\u003c \"====================================================\" \u003c\u003c std::endl;\n  return 0;\n}\n\n/// @brief 使用封装的动态库加载流程\nvoid func()\n{\n  try\n  {\n    const std::string libPath =\n#if defined(_WIN32) || defined(_WIN64)\n      \"dynamic.dll\";\n#else\n      \"./bin/libdynamic.so\";\n#endif\n\n    // 加载动态库\n    using dll::dynamic_library;\n    dynamic_library lib0(libPath);\n    dynamic_library lib1(libPath);\n    // dynamic_library lib = lib0; 错误: 禁止拷贝构造\n    // lib0 = lib1;               错误: 禁止拷贝赋值\n\n    lib0 = std::move(lib1);               // 支持移动赋值\n    dynamic_library lib(std::move(lib0));  // 支持移动构造\n\n    if (lib)\n    {\n      std::cout \u003c\u003c \"lib is vaild.\" \u003c\u003c std::endl;\n    }\n\n    // 加载函数符号\n    auto sayHello   = lib.get\u003csayHello_func\u003e(\"sayHello\");\n    auto intAdd     = lib.get\u003cintAdd_func\u003e(\"intAdd\");\n    auto floatAdd   = lib.get\u003cfloatAdd_func\u003e(\"floatAdd\");\n    auto doubleAdd  = lib.get\u003cdoubleAdd_func\u003e(\"doubleAdd\");\n    auto getPoint   = lib.get\u003cgetPoint_func\u003e(\"getPoint\");\n    auto printPoint = lib.get\u003cprintPoint_func\u003e(\"printPoint\");\n\n    // 直接调用函数符号\n    int ret = lib.invoke\u003cint(int, int)\u003e(\"intAdd\", 1, 2);\n    double ret2 = lib.invoke\u003cdouble(double, double)\u003e(\"doubleAdd\", 1.8, 2.5);\n    ret = lib.invoke\u003cint(int, int)\u003e(\"intAdd\", 2, 3);\n    ret = lib.invoke\u003cint(int, int)\u003e(\"intAdd\", 3, 4);\n    ret = lib.invoke\u003cint(*)(int, int)\u003e(\"intAdd\", 4, 5);\n    ret = lib.invoke\u003cint(*)(int, int)\u003e(\"intAdd\", 5, 6);\n    ret = lib.invoke\u003cint(\u0026)(int, int)\u003e(\"intAdd\", 6, 7);\n    ret = lib.invoke\u003cint(\u0026)(int, int)\u003e(\"intAdd\", 7, 8);\n    ret = lib.invoke\u003cint(\u0026\u0026)(int, int)\u003e(\"intAdd\", 8, 9);\n    double ret3 = lib.invoke_uncached\u003cdouble(double, double)\u003e(\"doubleAdd\", 1.8, 2.5);\n    std::cout \u003c\u003c \"invoke: intAdd(8, 9) = \" \u003c\u003c ret \u003c\u003c std::endl;\n    std::cout \u003c\u003c \"invoke: doubleAdd(1.8, 2.5) = \" \u003c\u003c ret2 \u003c\u003c std::endl;\n    std::cout \u003c\u003c \"invoke_uncached: doubleAdd(1.8, 2.5) = \" \u003c\u003c ret3 \u003c\u003c std::endl;\n\n    // 调用函数\n    sayHello();\n\n    int a = 5, b = 3;\n    std::cout \u003c\u003c \"intAdd(\" \u003c\u003c a \u003c\u003c \", \" \u003c\u003c b \u003c\u003c \") = \" \u003c\u003c intAdd(a, b) \u003c\u003c std::endl;\n\n    float fa = 1.5f, fb = 2.3f;\n    std::cout \u003c\u003c \"floatAdd(\" \u003c\u003c fa \u003c\u003c \", \" \u003c\u003c fb \u003c\u003c \") = \" \u003c\u003c floatAdd(fa, fb) \u003c\u003c std::endl;\n\n    double da = 3.14159, db = 2.71828;\n    std::cout \u003c\u003c \"doubleAdd(\" \u003c\u003c da \u003c\u003c \", \" \u003c\u003c db \u003c\u003c \") = \" \u003c\u003c doubleAdd(da, db) \u003c\u003c std::endl;\n\n    point_t p = getPoint();\n    std::cout \u003c\u003c \"getPoint() = {x: \" \u003c\u003c p.x \u003c\u003c \", y: \" \u003c\u003c p.y \u003c\u003c \", z: \" \u003c\u003c p.z \u003c\u003c \"}\" \u003c\u003c std::endl;\n\n    std::cout \u003c\u003c \"printPoint() output: \";\n    printPoint(p);\n    std::cout \u003c\u003c std::endl;\n\n    testNotExistSymbol(lib);\n  }\n  catch (const std::exception \u0026ex)\n  {\n    std::cerr \u003c\u003c \"Error: \" \u003c\u003c ex.what() \u003c\u003c std::endl;\n    return;\n  }\n}\n\n/// @brief 测试符号信息不存在的情况\nvoid testNotExistSymbol(const dll::dynamic_library \u0026lib)\n{\n  std::cout \u003c\u003c \"---------testNotExistSymbol----------\" \u003c\u003c std::endl;\n  // 测试不存在的函数符号加载\n  auto unknownFunc = lib.try_get\u003cprintPoint_func\u003e(\"notExistFunc\");  // 加载失败不抛异常,返回nullptr\n  if (unknownFunc == nullptr)\n  {\n    std::cout \u003c\u003c \"lib.try_get\u003cprintPoint_func\u003e(\\\"notExistFunc\\\"); load failed, return nullptr.\" \u003c\u003c std::endl;\n  }\n  try\n  {\n    auto unknownFunc2 = lib.get\u003cprintPoint_func\u003e(\"notExistFunc\");  // 加载失败抛出异常\n  }\n  catch (const std::exception \u0026e)\n  {\n    std::cerr \u003c\u003c e.what() \u003c\u003c '\\n';\n  }\n\n  try\n  {\n    double ret = lib.invoke\u003cdouble(double, double)\u003e(\"doubleAdd\", 1.5, 3.0);  // 正常调用: symbol是对的, 函数签名也正常\n    std::cout \u003c\u003c \"lib.invoke ret = \" \u003c\u003c ret \u003c\u003c std::endl;\n    double ret2 = lib.invoke\u003cdouble(double, double, double)\u003e(\"doubleAdd\", 1.5, 3.0, 1.0);  // 未定义行为: symbol是对的,但是函数签名不一致\n    std::cout \u003c\u003c \"[UB] lib.invoke ret2 = \" \u003c\u003c ret2 \u003c\u003c std::endl;\n    double ret3 = lib.invoke\u003cdouble()\u003e(\"doubleAdd\");  // 未定义行为: symbol是对的,但是函数签名不一致\n    std::cout \u003c\u003c \"[UB] lib.invoke ret3 = \" \u003c\u003c ret3 \u003c\u003c std::endl;\n  }\n  catch (const std::exception \u0026e)\n  {\n    std::cerr \u003c\u003c \"invoke error: \" \u003c\u003c e.what() \u003c\u003c '\\n';\n  }\n\n  std::cout \u003c\u003c \"---------testNotExistSymbol----------\" \u003c\u003c std::endl;\n}\n```\n\n\u003c/details\u003e\n\n### 点击查看: [动态库加载方式介绍](./docs/动态库的加载方式介绍.md)\n\n### 动态库的加载方式主要有以下两种\n\n1. **显式加载 (Explicit Loading)**\n\n   显式加载动态库是通过在运行时使用特定的函数来加载动态库，通常需要提供库的路径和库的符号名称。这种方法需要程序员在代码中明确调用库加载和符号解析函数。\n\n   - **Windows**：使用 `LoadLibrary` 加载动态库，使用 `GetProcAddress` 获取库中函数的地址。\n   - **Linux/Unix**：使用 `dlopen` 加载动态库，使用 `dlsym` 获取库中函数的地址。\n   - 这种方式通常用于插件系统、动态加载模块等场景。\n\n2. **隐式加载 (Implicit Loading)**\n\n   隐式加载是指在程序启动时，操作系统会根据可执行文件的要求自动加载所依赖的动态库。程序员不需要显式地调用加载函数，只需要在程序的构建过程中指定库的依赖。\n\n   - **Windows**：通过 `dllimport` 声明来隐式加载动态库，操作系统会在启动时根据需要加载动态库。\n   - **Linux/Unix**：通过 `ld` (链接器) 指定库依赖，操作系统会在运行时自动加载。\n\n### 显式加载和隐式加载使用场景\n\n- **隐式加载**适用于：依赖固定且少变、无需动态选择库的场景，简化了开发和维护工作。\n- **显式加载**适用于：需要动态加载库、插件或模块化设计、按需选择库、热更新等场景，提供更大的灵活性和控制力。\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabin-z%2Fdynamiclibloader","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabin-z%2Fdynamiclibloader","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabin-z%2Fdynamiclibloader/lists"}