Linux中的静态库、动态共享库和可加载库
这篇教程将讨论 Linux 库以及创建和使用 C/C++ 共享组件库和插件库背后的理论和哲学,同样也讨论了他们应用程序中使用的各种不同的技术和方法。这篇教程中所有的库均使用 GNU Linux 编译器创建。
为什么要使用库
这种方式也被称为“共享组件”或“静态库”,将多个编译后的目标代码文件打包成一个单独的文件称之为库。通常来说会将可以被多个应用程序共享的 C 函数或 C++ 类以及方法从源代码中剥离出来,单独编译并打包成库。共享组件的一个示例就是 C 标准库和 C++ STL 库,这些库会被链接到你的代码中。这样做的好处是,连接时不必声明每个目标文件,因为程序员可以单独引用每个库,这使得在应用程序间重用和共享软件组件变得简单。它同样为软件厂商发布应用程序的接口 API 提供了途径。比较大的组件可以创建成动态共享库,因此这些库可以独立于应用程序存在,有助于减少应用程序大小并节省磁盘空间使用,应用程序可以根据需要调用不同的库组件。
Linux 库分类
目前可以创建两种 Linux C/C++ 库:
-
静态库(.a)
会被链接进目标代码,并成为应用程序的一部分
-
动态共享链接库(.so):这种库只有一种结构,不过可以通过两种方式使用
I. 运行时动态链接但是静态依赖,在编译/链接阶段这些库需要存在,共享库不会包含到可执行文件中,但是需要在运行时绑定。
II. 在执行过程中使用动态加载链接系统函数进行动态加载/卸载和链接(比如:浏览器插件)
库命名规范
库通常使用 'lib' 作为命名前缀,这适用于所有 C 标准库。当链接时,在命令行中引用该库将不需要包含该库的前缀或后缀。
考虑下面的链接命令:
gcc src-file.c -lm -lpthread
这个例子中,在链接时需要 math 库和 thread 库,他们保存在在 /usr/lib/libm.a
和 /usr/lib/libpthread.a
中。
注:GNU 编译器现在有命令行选项
-pthread
而较老的编译器使用-lpthread
来明确指定使用 thread 库。因此,现在你的命令行看上去应该是这样的:gcc src-file.c -lm -pthread
静态库(.a)
如何创建一个库(目标代码归档文件):
-
编译
cc -Wall -c ctest1.c ctest2.c
其中选项
-Wall
代表显示警告,请参考 man page 中的说明。 -
创建库
libctest.a
ar -cvq libctest.a ctest1.o ctest2.o
-
列出库中的文件
ar -t libctest.a
-
链接库
cc -o executable-name prog.c libctest.a cc -o executable-name prog.c -L/path/to/library-directory -lctest
示例文件:
-
ctest1.c
void ctest1(int *i) { *i=5; }
-
ctest2.c
void ctest2(int *i) { *i=100; }
-
prog.c
#include <stdio.h> void ctest1(int *); void ctest2(int *); int main() { int x; ctest1(&x); printf("Valx=%d\n",x); return 0; }
关于历史:以前需要在创建完库后执行命令
ranlib ctest.a
在库文件中生成符号表。不过现在ranlib
命令已经集成到ar
命令中了。
动态链接“共享”库(.so)
创建共享库(动态链接对象库文件)需要以下步骤:
-
创建对象代码
-
创建库
-
可选的:使用符号链接创建默认的版本
示例
gcc -Wall -fPIC -c *.c gcc -shared -Wl,-soname,libctest.so.1 -o libctest.so.1.0 *.o mv libctest.so.1.0 /opt/lib ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1 ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so
上面命令会创建库 libctest.so.1.0
并且生成两个符号链接。
下面的级联链接是允许的:
ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1 ln -sf /opt/lib/libctest.so.1 /opt/lib/libctest.so
如果你看看 /lib
以及 /usr/lib
你会发现里面同时会使用以上两种符号链接方式,对于符号链接是否应该链接到真实的文件,Linux 开发社区并未达成一致。
编译器选项
-
-Wall
:包含警告,请查看 man pages 获得更详细信息。 -
-fPIC
:编译器指令,输出位置无关的代码。共享库所需的特征,参考-fpic
-
-shared
:生成可以在运行时链接到其他应用程序的共享库 -
-Wl, options
:向链接器传递选项在这里我们传递里链接器的选项是
-soname libctest.so.1
。-o
选项后的名称会传递给 gcc。 -
-o
选项:输出操作,在这个例子中共享库的名字将保存为 'libctest.so.1.0'
库符号链接:
-
编译选项
-lcteset
会使用/opt/lib/libctest.so
符号链接 -
运行时绑定会使用
/opt/lib/libctest.so.1
,参考下文
编译使用动态链接库的主程序
编译运行时链接动态链接库 libctest.so.1.0
gcc -Wall -I/path/to/include-files -L/path/to/libraries prog.c -lctest -o prog
使用:
gcc -Wall -L/opt/lib prog.c -lctest -o prog
这里使用的库为 libctest.so
(这就是为什么你需要创建符号链接的原因,否则你会得到错误 '/usr/bin/ld: cannot find -lctest')
应用程序不会包含库文件,库文件会在应用程序执行时进行动态链接。
列出依赖信息:
可以使用命令 ldd name-of executable
列出应用程序依赖的共享库信息,比如:
ldd prog libctest.so.1 => /opt/lib/libctest.so.1 (0x00002aaaaaaac000) libc.so.6 => /lib64/tls/libc.so.6 (0x0000003aa4e00000) /lib64/ld-linux-x86-64.so.2 (0x0000003aa4c00000)
潜在的陷阱:当库被加载后共享库的 Unresolved errors 可能会导致一个错误,比如:
运行时错误信息:
ERROR: unable to load libname-of-lib.so ERROR: unable to get function address
详细错误信息:
[prompt]$ ldd libname-of-lib.so libglut.so.3 => /usr/lib64/libglut.so.3 (0x00007fb582b74000) libGL.so.1 => /usr/lib64/libGL.so.1 (0x00007fb582857000) libX11.so.6 => /usr/lib64/libX11.so.6 (0x00007fb582518000) libIL.so.1 (0x00007fa0f2c0f000) libcudart.so.4 => not found
前三个库使用路径解析,最后两个是出问题的库。
解决方法是链接 libname-of-lib.so
库时解决最后两个库的依赖关系:
-
在
/etc/ld.so.conf.d/name-of-lib-x86_64.conf
以及/或者/etc/ld.so.conf.d/name-of-lib-i686.conf
目录中增加未找到库的路径,然后使用命令sudo ldconfig
或重新加载库缓存(/etc/ld.so.cache
)。或者
-
在编译或链接命令上明确制定库的路径,比如:
-lname-of-lib -L/path/to/lib
或者
-
将库的路径加入环境变量来解决运行时依赖问题:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/lib
运行程序
-
配置环境变量:
export LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH
-
执行:
prog
或者./prog
帮助手册
链接
库路径
为了让可执行程序在运行时能找到需要链接的库,需要配置系统使得这些库可以被找到,有以下方法(至少需要其中一种方法):
-
将包含动态链接库目录的路径加到
/etc/ld.so.conf
文件中,比如:/usr/X11R6/lib /usr/lib ... .. /usr/lib/sane /usr/lib/mysql /opt/lib
将路径加入到这个文件后需要执行
ldconfig
命令(以 root 账号)来配置链接器运行时绑定。如果你为其他操作系统环境开发,你可以使用-f file-name
来指定不同的配置文件,更多信息参考 ldconfig 手册或者
-
增加指定目录到库缓存(需要 root 权限):
ldconfig -n /opt/lib
其中
/opt/lib
是包含libctest.so
文件的目录。(如果在开发阶段,仅需要将你当前的目录增加到缓存即可ldconfig -n .
这种方式不会永久在系统中包含相关目录,在系统重启后配置信息会丢失。
或者
-
配置环境变量
LD_LIBRARY_PATH
执行包含共享库的路径,这可以指定运行时加载器使用该路径解决依赖关系,几种系统的环境变量名称:示例(bash shell
~/.bashrc
):... if [ -d /opt/lib ]; then LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH fi ... export LD_LIBRARY_PATH
上面配置会指示运行时加载器在解决共享库依赖时查看环境变量
LD_LIBRARY_PAT
中配置的路径,当前配置的路径包含/opt/lib
-
Linux/Solaris:
LD_LIBRARY_PATH
-
SGI:
LD_LIBRARYN32_PATH
-
AIX:
LIBPATH
-
Mac OS X:
DYLD_LIBRARY_PATH
-
HP-UX:
SHLIB_PATH
-
库路径需要按照 "Linux Standard Base" 制定目录结构。
库信息
列出库中的目标文件
可以使用 ar
命令列出库中的目标文件,如下:
ar tf /usr/lib/x86_64-linux-gnu/libjpeg.a
上面命令会列出库中所有目标文件信息:
jlibinit.o jcapimin.o jcapistd.o jccoefct.o jccolor.o jcdctmgr.o jchuff.o jcinit.o ... ...
可以参考 ar 命令手册
列出目标文件、静态库以及动态库的所有符号
-
列出目标文件的符号信息
nm file.o
-
列出静态库中的符号信息
符号信息会根据静态库中包含的源和目标文件层次进行分类:
nm /usr/lib/x86_64-linux-gnu/libjpeg.a
输出:
jlibinit.o: 0000000000000000 B auxv U fclose U fopen U fread U getpagesize 0000000000000000 T libjpeg_general_init U malloc U perror jcapimin.o: U jinit_marker_writer U jinit_memory_mgr 0000000000000000 T jpeg_CreateCompress U jpeg_abort 0000000000000240 T jpeg_abort_compress U jpeg_destroy 0000000000000230 T jpeg_destroy_compress 00000000000002a0 T jpeg_finish_compress U jpeg_natural_order ... ...
-
列出共享库中的符号信息
nm -D libctest.so.1.0 #or nm --dynamic libctest.so.1.0)
输出:
0000000000100988 A __bss_start 000000000000068c T ctest1 00000000000006a0 T ctest2 w __cxa_finalize 00000000001007b0 A _DYNAMIC 0000000000100988 A _edata 0000000000100990 A _end 00000000000006f8 T _fini 0000000000100958 A _GLOBAL_OFFSET_TABLE_ w __gmon_start__ 00000000000005b0 T _init w _Jv_RegisterClasses
参考
-
Man page for nm table here
使用 readelf 列出共享库符号信息
命令:
readelf -s /usr/lib64/libjpeg.so
输出:
Symbol table '.dynsym' contains 144 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000003b30 0 SECTION LOCAL DEFAULT 10 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getenv@GLIBC_2.2.5 (4) 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free@GLIBC_2.2.5 (4) 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND ferror@GLIBC_2.2.5 (4) 5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fread@GLIBC_2.2.5 (4) 6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fclose@GLIBC_2.2.5 (4) 7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@GLIBC_2.4 (5) 8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memset@GLIBC_2.2.5 (4) 9: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ ... ...
库版本
如果功能接口将被修改(C++ 类的 public/protected 定义),库中需要增加新的函数,库中的函数需要调整,函数原型需要调整(返回值类型、参数或者函数体等),这时需要为库指定新版本号。
库版本号可以在创建库的时候制定,如果要更新库那么需要指定新的版本号。这对于共享库来说更加重要,因为这些库是动态链接的。这可以避免由于系统升级而修改了标准库,使得依赖于较老版本标准库的应用程序崩溃的这种库依赖问题(微软的 "DLL 地狱" )。
GNU C/C++ 库同样也是版本画的,这通常使得使用某一版本 GNU 工具编译的应用程序与其他版本 GNU 工具编译的应用程序不兼容除非相应版本的 GNU 工具也安装在系统上。同一个库的多个版本可以在系统中共存,库的版本信息中包含了符号名,因此链接器知道需要链接哪个版本的库。
使用下面命令,我们可以看一下所使用的符号版本
nm csub1.o
输出:
00000000 T ctest1
默认情况下目标代码是没有任何版本信息的。
参考:ld 和目标文件布局
GNU C/C++ 编译器有一个选项可以用来明确生成符号版本。可以在编译器使用 --version-script=your-version-script-file
指定生成版本的脚本名称。
注:这仅适用于创建共享库的场景,它默认开发人员在进行静态链接时知道所需链接的静态库卡版本。
运行时链接机制允许系统中存在多个不兼容版本的动态库。在 GNU/Linux 环境下生成版本的脚本示例可以参考:sysdeps/unix/sysv/linux/Versions
。
有些符号也可以从汇编代码获取版本字符串,参考 glibc 的头文件 include/libc-symbols.h
。
示例:
nm /lib/libc.so.6 | more
输出:
00000000 A GCC_3.0 00000000 A GLIBC_2.0 00000000 A GLIBC_2.1 00000000 A GLIBC_2.1.1 00000000 A GLIBC_2.1.2 00000000 A GLIBC_2.1.3 00000000 A GLIBC_2.2 00000000 A GLIBC_2.2.1 00000000 A GLIBC_2.2.2 00000000 A GLIBC_2.2.3 00000000 A GLIBC_2.2.4 ... ..
注意版本脚本的使用。
依赖某个版本动态库的动态库
nm /lib/libutil-2.2.5.so
输出:
.. ... U strcpy@@GLIBC_2.0 U strncmp@@GLIBC_2.0 U strncpy@@GLIBC_2.0 ... ..
参考:
使用 libdl 动态加载和卸载共享库
那些会在执行过程中动态加载和卸载的库,通常可用于创建插件架构。
'ctest.h' 头文件:
#ifndef CTEST_H #define CTEST_H #ifdef __cplusplus extern "C" { #endif void ctest1(int *); void ctest2(int *); #ifdef __cplusplus } #endif #endif
如果需要在 C 和 C++ 中使用库需要使用符号 extern "C"
。这个语句可以避免 C++ 的 name mangling(C++ 编译代码会修改方法名),这样在链接时就不会发生 'unresolved symbols' 错误了。
动态加载和卸载上面创建的 libctest.so
:
#include <stdio.h> #include <dlfcn.h> #include "ctest.h" int main(int argc, char **argv) { void *lib_handle; double (*fn)(int *); int x; char *error; lib_handle = dlopen("/opt/lib/libctest.so", RTLD_LAZY); if (!lib_handle) { fprintf(stderr, "%s\n", dlerror()); exit(1); } fn = dlsym(lib_handle, "ctest1"); if ((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); exit(1); } (*fn)(&x); printf("Valx=%d\n",x); dlclose(lib_handle); return 0; }
使用下面命令进行编译:
gcc -rdynamic -o progdl progdl.c -ldl
说明:
-
dlopen("/opt/lib/libctest.so", RTLD_LAZY);
打开共享库
libctest.so
,第二个参数代表绑定,参考头文件dlfcn.h
,如果出现错误会返回 NULL。选项:RTLD_LAZY
:如果指定,操作系统在引用共享库前不会关注 'unresolved symbols' 错误。RTLD_NOW
:当调用dlopen()
方法时解决所有 'unresolved symbols'*RTLD_GLOBAL
:使符号库可见 -
dlsym(lib_handle, "ctest1")
获取已加载的动态库地址,如果出错会返回 NULL。
注:如果使用 C++ 函数,首先需要使用
nm
来查找到 'mangled(修饰)' 的符号,或者使用extern "C"
来避免命名修饰(name mangling)问题。比如:extern "C" void function-name()
。
目标代码位置
静态库目标代码(Object code archive libraries)可以位于任何可执行文件或者可加载库中,每一种方式都不能够重复使用目标代码。尤其对于使用静态变量的代码,比如单例类。静态变量是全局的,因此只能声明一次。包含静态库两次将会产生无法预期的结果。程序员可以通过编译器来传递特定的目标代码给链接器将目标代码链接到可执行文件中。
在 gcc/g++ 编译器中需要使用 -Wl
标识将命令行参数传递给 GNU ld
链接器。
示例 makefile 语句:
g++ -rdynamic -o appexe $(OBJ) $(LINKFLAGS) -Wl,--whole-archive -L{AA_libs} -laa -Wl,--no-whole-archive $(LIBS)
说明:
-
--whole-archive
:这个链接指令会指示链接器将后面列出的所有库(本例中的 AA_libs)连接到可执行文件中,即使库中的方法没有被使用过。这个选项通常用于指定运行时可加载库需要使用的库。 -
-no-whole-archive
:如果你列出了额外需要的目标文件时需要指定这个选项。gcc/g++ 编译器在链接时会增加他们自己的静态库列表,当然你不希望这些静态库中未使用的目标代码都被连接到应用程序中,它会对这些静态库使用常规链接行为。
参考:
-
dlopen()
-- 以指定模式打开指定的动态链接库文件 -
dlclose()
-- dlclose用于关闭指定句柄的动态链接库 -
dlsym()
-- 根据动态链接库操作句柄与符号,返回符号对应的地址 -
dlerror()
-- 当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。
动态加载C++ 类对象
C++ Name Mangling
当使用 C++ 编译器运行上面例子时会发现 C++ 函数名称被修饰了,因此无法工作,除非使用 extern "C" {}
进行保护。
注意,下面的两个方法声明不相同:
//function A extern "C" { int functionx(); } //function B extern "C" int functionx();
下面的两个方法声明是相同的
//function A extern "C" { extern int functionx(); } //function B extern "C" int functionx();
动态加载 C++ 类
动态库加载程序允许程序员加载 C 函数。对于 C++ 库我们可能希望加载类成员函数。事实上,我们有时候希望能够加载库中的整个类,希望可以访问整个对象以及对象的所有成员函数。我们可以使用 C 实现的类工厂来构造 C++ 类实例,比如下面代码:
类的头文件(.h 文件)
class Abc { ... ... }; // Class factory "C" functions typedef Abc* create_t; typedef void destroy_t(Abc*);
类的实现文件(.cpp)文件
Abc::Abc() { ... } extern "C" { // These two "C" functions manage the creation and destruction of the class Abc Abc* create() { return new Abc; } void destroy(Abc* p) { delete p; // Can use a base class or derived class pointer here } }
上面代码是库的源文件,使用 C 函数来实例化(创建)以及销毁定义在动态库中的 Abc
C++ 类。
调用函数如下:
// load the symbols create_t* create_abc = (create_t*) dlsym(lib_handle, "create"); ... ... destroy_t* destroy_abc = (destroy_t*) dlsym(lib_handle, "destroy"); ... ...
陷阱
创建和销毁 C++ 类在调用函数或者库中必须成对使用,
参考
与 Microsoft DLL 对比
在 Microsoft Windows 中与 Linux/Unix 共享对象(.so)相同的概念是 .dll。Microsoft Windows DLL 文件通常使用 .dll
作为后缀名,不过可能会使用 .ocx
后缀。在 16位 windows 操作系统中,动态链接库也会使用 .exe
后缀,"执行"动态链接库时会将库加载进内存。
Vistual C++ .NET 集成开发环境可以通过 GUI 创建 DLL 框架,并且生成 .def
文件,这个文件称之为 "模块定义文件(module definition file)" 它会列出动态链接库所有输出的函数(或功能)。当输出 C++ 函数时,可以使用 C++ 修饰后的名称,Vistual C++ 编译器会生成 .map
文件,这个文件可以帮助你找到 .def
文件中被修饰的 C++ 名称。.def
文件中的 SECTIONS
标签用来定义 "共享" 部分。不幸的是生成的 DLL 文件是与 Microsoft IDE 紧密捆绑的,因此我不建议你尝试不使用 Microsoft IDE 创建 DLL。
下面的函数是 Microsoft C++ 中与 libdl
等同的函数:
-
::LoadLibrary() -- dlopen()
-
::GetProcAddress() -- dlsym()
-
::FreeLibrary() -- dlclose()
可能的陷阱Microsoft Visual C++ .NET 编译器不允许像 GNU linker ld
那样控制链接器的行为(比如:--whole-archive
, -no-whole-archive
)。VC++ 编译器需要逐个解析可执行文件和动态链接库中所有符号信息,因此在库加载时可能引起库的重复。当使用静态库时这是非常不好的(比如:应用了单例模式)因为对于同一个静态变量可能有两个内存地址,一个是动态链接库中的静态变量内存地址,另一个是可执行程序中相同变量的内存地址,这破坏了整个静态变量概念和Singleton模式。因此,你不能定义可以同时在动态链接库和应用程序中使用的静态变量,因为在运行时他们是唯一的以及不同的(内存地址不一样)。要使用唯一的静态变量,你需要传递静态变量的指针到另一个模块,这样每个模块(应用程序和DLL)就可以使用同一个静态变量了。在 MS/Windows 中你可以使用共享内存或者内存映射文件,这样主应用程序和动态链接库(DLL)就可以共享指向相同地址的指针供双方使用了。
跨平台(Linux 和 MS/Windows)C++ 代码片段:
头文件(.h 或 .hpp)
class Abc{ public: static Abc* Instance(); // Function declaration. Could also be used as a public class member function. private: static Abc *mInstance; // Singleton. Use this declaration in C++ class member variable declaration. ... }
C/C++ 函数代码(.cpp):
/// Singleton instantiation Abc* Abc::mInstance = 0; // Use this declaration for C++ class member variable // (Defined outside of class definition in ".cpp" file) // Return unique pointer to instance of Abc or create it if it does not exist. // (Unique to both exe and dll) static Abc* Abc::Instance() // Singleton { #ifdef WIN32 // If pointer to instance of Abc exists (true) then return instance pointer else look for // instance pointer in memory mapped pointer. If the instance pointer does not exist in // memory mapped pointer, return a newly created pointer to an instance of Abc. return mInstance ? mInstance : (mInstance = (Abc*) MemoryMappedPointers::getPointer("Abc")) ? mInstance : (mInstance = (Abc*) MemoryMappedPointers::createEntry("Abc",(void*)new Abc)); #else // If pointer to instance of Abc exists (true) then return instance pointer // else return a newly created pointer to an instance of Abc. return mInstance ? mInstance : (mInstance = new Abc); #endif }
Windows 链接器会生成静态对象的两个实例,一个在可执行文件中另一个在动态链接库中。可以使用内存映射指针指向一个或所有变量,这样可执行文件和动态链接库就可以使用相同的变量或对象了。
更多单例模式的例子请参考:C++ 单例模式教程
跨平台加载动态库
#ifndef USE_PRECOMPILED_HEADERS #ifdef WIN32 #include <direct.h> #include <windows.h> #else #include <sys/types.h> #include <dlfcn.h> #endif #include <iostream> #endif using namespace std; #ifdef WIN32 HINSTANCE lib_handle; #else void *lib_handle; #endif // Where retType is the pointer to a return type of the function // This return type can be int, float, double, etc or a struct or class. typedef retType* func_t; // load the library ------------------------------------------------- #ifdef WIN32 string nameOfLibToLoad("C:\opt\lib\libctest.dll"); lib_handle = LoadLibrary(TEXT(nameOfLibToLoad.c_str())); if (!lib_handle) { cerr << "Cannot load library: " << TEXT(nameOfDllToLoad.c_str()) << endl; } #else string nameOfLibToLoad("/opt/lib/libctest.so"); lib_handle = dlopen(nameOfLibToLoad.c_str(), RTLD_LAZY); if (!lib_handle) { cerr << "Cannot load library: " << dlerror() << endl; } #endif ... ... ... // load the symbols ------------------------------------------------- #ifdef WIN32 func_t* fn_handle = (func_t*) GetProcAddress(lib_handle, "superfunctionx"); if (!fn_handle) { cerr << "Cannot load symbol superfunctionx: " << GetLastError() << endl; } #else // reset errors dlerror(); // load the symbols (handle to function "superfunctionx") func_t* fn_handle= (func_t*) dlsym(lib_handle, "superfunctionx"); const char* dlsym_error = dlerror(); if (dlsym_error) { cerr << "Cannot load symbol superfunctionx: " << dlsym_error << endl; } #endif ... ... ... // unload the library ----------------------------------------------- #ifdef WIN32 FreeLibrary(lib_handle); #else dlclose(lib_handle); #endif
备注
更多推荐
所有评论(0)