Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
动态库,是得多动态?
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Zhihao Yuan
December 11, 2021
Programming
92
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
动态库,是得多动态?
运行时加载动态库的应用与挑战。
视频:
https://www.bilibili.com/video/BV1LU4y1K7om
Zhihao Yuan
December 11, 2021
More Decks by Zhihao Yuan
See All by Zhihao Yuan
DWARF 眼中的 C++
lichray
0
25
Callable Objects in Java, C#, Rust, and C++
lichray
0
60
非类型参数会梦见 OOP 吗?
lichray
0
160
Dynamically Loaded Libraries Outside the Standard
lichray
0
470
Thinking in Immediate: ImGUI
lichray
0
240
Lambda 和它们的环境
lichray
1
220
The Many Shades of reference_wrapper
lichray
0
250
Are We Macro-free Yet?
lichray
0
180
vvpkg: A cross-platform data deduplication library in C++14
lichray
0
180
Other Decks in Programming
See All in Programming
気圧・高度・GPSを記録&可視化するアプリ「Koudo」を作った話
hjmkth
1
320
Snowflake Summitでの新機能 CoCo / CoWork / snowflake-summit-2026-overall-what-new-coco
tatsuhiro
1
170
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
1
300
AI 時代のソフトウェア設計の学び方
masuda220
PRO
29
13k
Observability in Practice:Grafana 與 Edge Device SRE 的那些事
blueswen
0
170
AIを活用したE2Eテスト実装効率化のあゆみ / ebisu-mobile-14-kotetu
kotetuco
0
130
OSもどきOS
arkw
0
590
Spring Security 実践 ─ GraphQL APIで実務に役立つ 認証・認可 を学ぶ
wagyu
0
260
Vite+ Unified Toolchain for the Web
naokihaba
0
340
JJUG CCC 2026 Spring: JSpecify で実現する Kotlin フレンドリーな Java API 設計
ternbusty
1
190
Claspは野良GASの夢をみるか
takter00
0
210
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
2
670
Featured
See All Featured
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
4 Signs Your Business is Dying
shpigford
187
22k
Building the Perfect Custom Keyboard
takai
2
800
Making Projects Easy
brettharned
120
6.7k
Odyssey Design
rkendrick25
PRO
2
700
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
330
Practical Orchestrator
shlominoach
191
11k
Un-Boring Meetings
codingconduct
0
320
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.2k
How to train your dragon (web standard)
notwaldorf
97
6.7k
Testing 201, or: Great Expectations
jmmastey
46
8.2k
Ruling the World: When Life Gets Gamed
codingconduct
0
260
Transcript
2021 Pure C++ PURECPP 动态库,是得多动态? 2021/12/11 Zhihao Yuan <
[email protected]
>
2021 Pure C++ PURECPP 背景知识
3 2021 Pure C++ | PURECPP 静态链接 app editor.o file.o
line.o search.o source.o
4 2021 Pure C++ | PURECPP 使用静态库 app editor.o file.o
line.o search.o source.o libed.a editor.o file.o line.o libre.a search.o
5 2021 Pure C++ | PURECPP 动态链接 app libed.so =>
libre.so => source.o libed.so editor.o file.o line.o libre.so search.o
6 2021 Pure C++ | PURECPP • 分解应用程序的载体 • 在构建时建立依赖性
• 也是 C++ 标准库的常见载体 依赖库
7 2021 Pure C++ | PURECPP #include "repromath_export.h" namespace repromath
{ REPROMATH_EXPORT auto ddot(int n, double const *x, double const *y) -> double; REPROMATH_EXPORT auto dsum(int n, double const *x) -> double; } 动态链接库的例子 CMake 自动生成的头文件 add_library(repromath SHARED) generate_export_header(repromath)
8 2021 Pure C++ | PURECPP #include <repromath.h> #include <stdio.h>
int main() { double x[] = {1., 2., 3.}; double y[] = {4., -5., 6.}; printf("result = %g\n", repromath::ddot(3, x, y)); } 用户侧的代码和静态链接相同
9 2021 Pure C++ | PURECPP • Level 0: 依赖库
「动态」的不同等级
10 2021 Pure C++ | PURECPP • Level 0: 依赖库
• Level 1: 延迟加载 「动态」的不同等级
11 2021 Pure C++ | PURECPP • 在程序运行过程中第一次用到依赖库导出的名字时再加载它 • 在「时机」上更加动态
• 目的一般是减少程序启动时间 延迟加载
12 2021 Pure C++ | PURECPP • Windows: 辅助库 delayimp.lib
+ linker 选项 /DELAYLOAD:mylib.dll • Solaris: linker 选项 -z lazyload -lmylib • Linux: DIY: Implib.so (类似 DLL 导入库的方案) 延迟加载跨平台吗?
13 2021 Pure C++ | PURECPP printf("address prior to use:
%p\n", repromath::ddot); printf("result = %g\n", repromath::ddot(3, x, y)); printf("address after using: %p\n", repromath::ddot); MSVC 的 /DELAYLOAD 选项
14 2021 Pure C++ | PURECPP • 函数的地址在程序运行过程中可以改变 – 因为槽的地址
≠ 实际函数的地址 • C++ 的对象在内存中不能变更位置 (relocate),但现在函数可以 延迟加载的迷惑行为
15 2021 Pure C++ | PURECPP 有没有可能更动态一点,在程序中直接控制 加载一个库的时机?
16 2021 Pure C++ | PURECPP #include "repromath_export.h" namespace repromath
{ REPROMATH_EXPORT auto ddot(int n, double const *x, double const *y) -> double; REPROMATH_EXPORT auto dsum(int n, double const *x) -> double; } 动态加载库的例子 CMake 自动生成的头文件 add_library(repromath MODULE) generate_export_header(repromath)
17 2021 Pure C++ | PURECPP • LoadLibraryEx("mylib.dll", nullptr, flags)
• LoadLibrary("mylib.dll") • GetProcAddress(handle, "function_or_variable_name") • FreeLibrary(handle) • GetLastError() APIs: Win32
18 2021 Pure C++ | PURECPP • dumpbin /exports repromath.dll
找到动态库导出的名字
19 2021 Pure C++ | PURECPP double x[] = {1.,
2., 3.}; double y[] = {4., -5., 6.}; auto lib = ::LoadLibraryW(L"repromath.dll"); /* ...处理错误 */ typedef auto ddot_t(int, double const *, double const *) -> double; auto ddot = (ddot_t *)::GetProcAddress(lib, "?ddot@repromath@@YANHPEBN0@Z"); printf("result = %g\n", ddot(3, x, y)); ::FreeLibrary(lib); Win32 下的例子
21 2021 Pure C++ | PURECPP • dlopen("mylib.so") • dlsym(handle,
"symbol_name") • dlclose(handle) • dlerror() APIs: POSIX
22 2021 Pure C++ | PURECPP • objdump -T librepromath.so
找到动态库导出的名字
23 2021 Pure C++ | PURECPP double x[] = {1.,
2., 3.}; double y[] = {4., -5., 6.}; auto lib = ::dlopen("./librepromath.so", RTLD_LOCAL | RTLD_NOW); /* ...处理错误 */ typedef auto ddot_t(int, double const *, double const *) -> double; auto ddot = (ddot_t *)::dlsym(lib, "_ZN9repromath4ddotEiPKdS1_"); printf("result = %g\n", ddot(3, x, y)); ::dlclose(lib); POSIX 下的例子
25 2021 Pure C++ | PURECPP • dlopen, LoadLibrary, 和
LoadLibraryEx 增加计数 • dlclose 和 FreeLibrary 减少计数 • GetModuleHandleEx 能增加已经加载了的库的引用计数 平台对载入的库使用了引用计数
26 2021 Pure C++ | PURECPP • Level 0: 依赖库
• Level 1: 延迟加载 • Level 2: 动态实体 「动态」的不同等级
27 2021 Pure C++ | PURECPP void *dlsym(void *handle, char
const *symbol); 近距离观察动态载入 API
28 2021 Pure C++ | PURECPP (ddot_t *)::dlsym(lib, "_ZN9repromath4ddotEiPKdS1_"); 近距离观察动态载入
API 类型为 void * 的指针可以被强制 转换为函数指针吗?
29 2021 Pure C++ | PURECPP • 函数指针和指向对象的指针之间的转换是 conditionally-supported •
不过 POSIX 要求 POSIX 实现支持这个转换 不一定
30 2021 Pure C++ | PURECPP FARPROC GetProcAddress(HMODULE hModule, LPCSTR
lpProcName); Win32 下动态载入 API 的情况 typedef INT_PTR (FAR WINAPI *FARPROC)();
31 2021 Pure C++ | PURECPP • 返回类型是指向函数的指针 • 指向哪个函数的指针?
GetProcAddress
32 2021 Pure C++ | PURECPP 在程序运行过程中,决议符号的结果会指向 • 程序之外 (foreign)
的函数,或者 • 对象模型之外 (foreign) 的对象 动态实体的奇怪之处
33 2021 Pure C++ | PURECPP • 在另一个编程语言中使用可加载模块 • 使用
Foreign Function Interface (FFI) 来访问 外语的「外」
34 2021 Pure C++ | PURECPP from ctypes import CDLL,
c_double x = (c_double * 3)(1., 2., 3.) y = (c_double * 3)(4., -5., 6.) repromath = CDLL('repromath.dll') ddot = repromath['?ddot@repromath@@YANHPEBN0@Z'] ddot.restype = c_double print("result = {}".format(ddot(3, x, y))); ctypes 的例子 遗漏这行,你会得到一堆垃圾
35 2021 Pure C++ | PURECPP • pybind11: 你写的 C++
代码会在 Python 运行时中组建模块 • JNA (Java Native Access): 你可以用 Java 语法声明 C 函数 • pydffi (DragonFFI): 你可以用 C++ 声明 C 或 C++ 函数… 强类型 FFI 是有必要的 import pydffi pydffi.dlopen("/path/to/libarchive.so") CU = pydffi.FFI().cdef("#include <archive.h>") a = funcs.archive_read_new()
36 2021 Pure C++ | PURECPP #include <repromath.h> int main()
{ double x[] = {1., 2., 3.}; double y[] = {4., -5., 6.}; auto lib = dlopen("repromath"); auto ddot = __magic<repromath::ddot>(lib); printf("result = %g\n", ddot(3, x, y)); } 假想的 C++ FFI
37 2021 Pure C++ | PURECPP • 声明决定了一个实体的符号 – 名字
– 类型 – extern "C" – ABI tag 等编译器扩展 用函数、对象声明决议动态实体
38 2021 Pure C++ | PURECPP • Level 0: 依赖库
• Level 1: 延迟加载 • Level 2: 动态实体 • Level 3: 插件系统 「动态」的不同等级
39 2021 Pure C++ | PURECPP • 如果把内存中的整个程序看作是一个「C++ 程序」,那么这是 ODR
violation – 症状各种各样 • 主流平台都有缓和这一风险的机制 – 不过总有「绕过」办法的问题… 载入两个定义了同一个实体的库会发生什么?
40 2021 Pure C++ | PURECPP • OpenSSL + LibreSSL
在同一个进程中能干什么好事? • 「ABI 兼容性」这个术语已经暗示了我们希望两个兼容的东西功能是可替换的 对于动态实体来说,符号冲突是意外 但如果是我们在利用 ABI 相同这一 点,用编程方式从多个载入库中获 得额外功能呢?
41 2021 Pure C++ | PURECPP • GIMP 的图形处理框架 •
拖拽文件就能添加、删除功能 案例:GEGL
42 2021 Pure C++ | PURECPP PLUGINAPP_API LPPLUGINSTRUCT plugin_app_create_plugin(void); PLUGINAPP_API
void plugin_app_destroy_plugin(LPPLUGINSTRUCT); PLUGINAPP_API const gchar* plugin_app_get_plugin_name(void); PLUGINAPP_API const gchar* plugin_app_get_plugin_provider(void); PLUGINAPP_API const gchar* plugin_app_get_menu_name(void); PLUGINAPP_API const gchar* plugin_app_get_menu_category(void); PLUGINAPP_API void plugin_app_run_proc(void); 一个典型的基于 C 的插件系统 该例子修改自 https://www.codeproject.com/Articles/38966 7/Simple-Plug-in-Architecture-in-Plain-C
43 2021 Pure C++ | PURECPP LPPLUGINSTRUCT plugin_app_create_plugin() { g_debug("PluginDialog1::plugin_app_create_plugin");
/* ... */ return PLS; } const gchar* plugin_app_get_plugin_name() { g_debug("PluginDialog1::plugin_app_get_plugin_name"); return "Dialog1 Plugin"; } 插件1 实现的东西
44 2021 Pure C++ | PURECPP LPPLUGINSTRUCT plugin_app_create_plugin() { g_debug("...");
/* ... */ return PLS; } const gchar* plugin_app_get_plugin_name() { g_debug("..."); return "Dialog2 Plugin"; } 插件2 实现的东西
45 2021 Pure C++ | PURECPP LPPLUGINSTRUCT plugin_app_create_plugin() { g_debug("Some
thing"); /* ... */ return PLS; } const gchar* plugin_app_get_plugin_name() { g_debug("..."); return "Dialog1 Plugin"; } 同一实体多个定义 LPPLUGINSTRUCT plugin_app_create_plugin() { g_debug("Some other thing"); /* ... */ return PLS; } const gchar* plugin_app_get_plugin_name() { g_debug("..."); return "Dialog2 Plugin"; }
46 2021 Pure C++ | PURECPP typedef LPPLUGINSTRUCT (*CREATEPROC) (void);
typedef void (*DESTROYPROC) (LPPLUGINSTRUCT); typedef const gchar* (*NAMEPROC) (void); typedef const gchar* (*PROVIDERPROC)(void); typedef const gchar* (*MENUPROC) (void); typedef const gchar* (*MENUCATPROC) (void); typedef void (*RUNPROC) (void); 为了之后使用 GetProcAddress 准备的指针类型
47 2021 Pure C++ | PURECPP void load_all_plugins(GtkWidget *widget, gpointer
user_data) { LPPLUGINSTRUCT pls = NULL; CREATEPROC create = NULL; MENUPROC menuproc = NULL; MENUCATPROC menucatproc = NULL; /* ... */ while (plugin_helper_get_plugin_list()) { create = (CREATEPROC) GetProcAddress(h, "plugin_app_create_plugin"); 用法 struct _PluginStruct { NAMEPROC nameProc; PROVIDERPROC providerProc; MENUPROC menuProc; MENUCATPROC menuCatProc; RUNPROC runProc; DESTROYPROC destProc; };
48 2021 Pure C++ | PURECPP • 给同一组函数提供不同实现 虚表 (Vtable)
49 2021 Pure C++ | PURECPP 接口 whereispython 基于 C++
的插件系统的例子 可执行文件 plugindemo 模块 fullinstaller microsoftstore
50 2021 Pure C++ | PURECPP class installation { public:
virtual auto executable() -> std::filesystem::path = 0; virtual auto windowed_executable() -> std::filesystem::path = 0; virtual ~installation() = default; }; class factory { public: virtual auto lookup(char const *ver) -> std::unique_ptr<installation> = 0; }; namespace whereispython 和同名头文件
51 2021 Pure C++ | PURECPP class fullinstaller : public
installation { std::unique_ptr<HKEY, hkey_deleter> hkey_; public: explicit fullinstaller(char const *version); auto executable() -> std::filesystem::path override { return string_value(L"ExecutablePath"); } auto windowed_executable() -> std::filesystem::path override { return string_value(L"WindowedExecutablePath"); } }; namespace fullinstaller 和同名 dll
52 2021 Pure C++ | PURECPP class fullinstaller_factory : public
factory { public: virtual auto lookup(char const *version) -> std::unique_ptr<installation> { try { return std::make_unique<fullinstaller>(version); } catch (std::exception &) { return nullptr; } } }; namespace fullinstaller 和同名 dll
53 2021 Pure C++ | PURECPP #pragma comment(linker, "/export:instance=?instance@@3Vfullinstaller_factory@whereispython@@A") whereispython::fullinstaller_factory
instance; fullinstaller.dll 的全局命名空间
54 2021 Pure C++ | PURECPP class microsoftstore : public
installation { std::filesystem::path install_location_; public: explicit microsoftstore(char const *version) { auto shell = PowerShell::Create() ->AddCommand("Get-AppxPackage") ... class microsoftstore_factory : public factory { ... namespace microsoftstore 和同名 dll
55 2021 Pure C++ | PURECPP #pragma comment(linker, "/export:instance=?instance@@3Vmicrosoftstore_factory@whereispython@@A") whereispython::microsoftstore_factory
instance; microsoftstore.dll 的全局命名空间 You can also use a DEF file
56 2021 Pure C++ | PURECPP template <class Factory> class
plugin { std::unique_ptr<HMODULE, library_deleter> lib_; Factory *obj_; public: explicit plugin(std::filesystem::path const &dll) : lib_([&] { /* ... */ }()), obj_([this] { if (auto pinst = (Factory *)::GetProcAddress(lib_.get(), "instance")) return pinst; /* ... */ }()) plugin.h 头文件里的 namespace plugindemo
57 2021 Pure C++ | PURECPP plugin<whereispython::factory> (Factory *)::GetProcAddress(lib_.get(), "instance")
whereispython::factory * whereispython::fullinstaller_factory * whereispython::fullinstaller_factory instance;
58 2021 Pure C++ | PURECPP • 载入一个目录下的所有插件 auto openplugins(std::filesystem::path
dir) -> std::vector<plugin<whereispython::factory>>; openplugin.h 头文件里的 namespace plugindemo
60 2021 Pure C++ | PURECPP for (auto &plugin :
plugindemo::openplugins(fs::current_path())) { if (auto python = plugin->lookup(argv[1])) { if (nonempty) std::cout << std::endl; std::cout << python->executable() << '\n'; std::cout << python->windowed_executable() << '\n'; nonempty = true; } } plugindemo 可执行文件的 main 函数
61 2021 Pure C++ | PURECPP • 删掉 fullinstaller.dll 会丢失第一组答案
• 删掉 microsoftstore.dll 会丢失第二组答案 • 拖入文件,插入 (plug-in) 功能 演示 查找版本 "3.7"
62 2021 Pure C++ | PURECPP • 原理上想违反,但不一定得是函数的 ODR •
允许控制以载入为目的导出的别名,这样的功能很有用 插件想要违反 One Definition Rule GCC & Clang int foo asm("myfoo") = 2;
63 2021 Pure C++ | PURECPP • Level 0: 依赖库
• Level 1: 延迟加载 • Level 2: 动态实体 • Level 3: 插件系统 • Level 4: Live update 「动态」的不同等级
64 2021 Pure C++ | PURECPP • 你不想在 GIMP 还在运行的时候就卸载这些
⤵️ 一个典型的插件系统
65 2021 Pure C++ | PURECPP • 调用已经卸载了的库中的函数会导致 access violation
• …函数现在有生命期了 但可能想要卸载旧代码
66 2021 Pure C++ | PURECPP • Apache 和 Nginx
的模块无法卸载 • Python 的模块系统无法卸载 C 和 C++ 扩展 – importlib.reload 也不会重新载入这些扩展 • musl libc 的 dlclose 是 no-op 应用与实现常常避免卸载动态库 重启进程解决一切问题 如果扩展创建的活着的对象不能保持扩展自身不 被回收,不卸载扩展避免了违反 Python 的语义 如果库可以被卸载,线程本地 存储 (TLS) 的实现会很复杂
67 2021 Pure C++ | PURECPP 库创建的对象能比库自身活得更长吗? 嵌套生命期 • 没有问题
✔ 对象可以逃逸 • 给每个对象加一个库的工厂对象的强引用? • 让库跟踪所有的对象? – 可以考虑用 GetModuleHandleEx 让库完全掌控 自身的卸载时机 • 如果库会创建生命期不受库管制的线程, 参考 FreeLibraryAndExitThread • 很难调试
68 2021 Pure C++ | PURECPP • 载入一个库时,初始化命名空间作用域中具静态存储期的对象 • 卸载一个库时,析构具静态存储期的对象
库与 C++ 对象生命期 别忘了,载入的库使用了引用计数
69 2021 Pure C++ | PURECPP • 当新线程开始运行时,初始化命名空间作用域中具线程存储期的对象 • 当一个线程退出时,析构具线程存储期的对象
库的生命期与 thread_local 相互作用 如果库在所有线程退出之 前被卸载了,会发生什么?
70 2021 Pure C++ | PURECPP 如果从进程地址空间中卸载了析构函数代码, 还怎么运行析构函数?
71 2021 Pure C++ | PURECPP class logger { public:
virtual ~logger() = default; }; class singleton { public: virtual auto get() -> logger & = 0; }; 测试一下
72 2021 Pure C++ | PURECPP class memory_logger : public
logger { std::unique_ptr<char[]> buf_ = std::make_unique<char[]>(1024); public: memory_logger() { /* 记录 thread id */ } ~memory_logger() { /* 记录 thread id */ } }; 该 logger 的对象实例将是线程特定的
74 2021 Pure C++ | PURECPP class memory_logger_singleton : public
singleton { public: virtual auto get() -> logger & override { thread_local memory_logger inst; return inst; } }; tslogger::memory_logger_singleton instance; 我们要返回这样一个实例
75 2021 Pure C++ | PURECPP auto load = []
{ return plugin<singleton>(fs::current_path() / libname); }; auto inst = load(); std::thread th[] = { /* 下一张幻灯片 */ }; for (auto &thr : th) thr.join(); 可执行文件的 main 函数
76 2021 Pure C++ | PURECPP 使用该库的线程 th[0] std::thread([&] {
inst->get(); /* th[1] 卸载了库之后 */ }) th[1] std::thread([&] { /* th[0] 初始化了 TLS 之后 */ inst->get(); inst.unload(); })
77 2021 Pure C++ | PURECPP class memory_logger : public
logger { ... memory_logger() { std::cout << " + thread (" << std::this_thread::get_id() << ") attached\n"; } ~memory_logger() { std::cout << " - thread (" << std::this_thread::get_id() << ") detached\n"; } 我们的线程特定 logger 会记录这些东西
78 2021 Pure C++ | PURECPP MSVC on Windows 10
79 2021 Pure C++ | PURECPP GCC + glibc on
Linux 表现好到不真实?
80 2021 Pure C++ | PURECPP class memory_logger_singleton : public
singleton { ... memory_logger_singleton() { std::cout << " + process attached\n"; } ~memory_logger_singleton() { std::cout << " + process detached\n"; } }; 把静态对象的活动也记录下来
81 2021 Pure C++ | PURECPP auto load = []
{ return plugin<singleton>(fs::current_path() / libname); }; auto inst = load(); std::thread th[] = { /* ... */ }; for (auto &thr : th) thr.join(); load(); 然后在所有线程退出之后再载入一次这个库
82 2021 Pure C++ | PURECPP • 虽然泄漏了库的句柄,但语义是对的 MSVC on
Windows 10
83 2021 Pure C++ | PURECPP • …静态对象没被重新初始化? GCC +
glibc on Linux
84 2021 Pure C++ | PURECPP • RTLD_NODELETE – 调用
dlclose() 时不卸载共享库 – 后果是,即便之后用 dlopen() 重新载入该库,其中的静态变量和全局变量也不会重新初始化 • DF_1_NODELETE (elf.h) – 自动给包含 thread_local 对象的共享库打上该标记,直到所有这些对象被销毁 – 此标记被清除之后,再次调用 dlclose() 才能卸载该库 glibc 在销毁 thread_local 对象的过程中调用 dlclose() 是无用操作
85 2021 Pure C++ | PURECPP • 函数可能有生命期了 • 实现需要防止具线程存储期的对象比它们的析构函数活得更长
随卸载而来的问题
2021 Pure C++ PURECPP 结语
87 2021 Pure C++ | PURECPP • 动态载入库「动态」的程度有不同级别,各有用处 结语
88 2021 Pure C++ | PURECPP 越动态,越多挑战 延迟加载 • 函数地址在程序运行过
程中可能会变 动态实体 (显式加载) • 存在程序和对象模型之 外的函数和对象 插件 (加载多个定义) • 违反 ODR Live update (卸载) • 函数可能有生命期 • 析构函数也有生命期给 实现 TLS 带来了挑战
89 2021 Pure C++ | PURECPP • 动态载入库「动态」的程度有不同级别,各有用处 • 在主流平台上用动态载入库创建抽象是可行的
• 一定程度的标准化能给这里的例子增加类型安全和可移植性 结语
2021 Pure C++ PURECPP Questions? @lichray zhihaoy/dl-examples