这篇教程将讨论 Linux 库以及创建和使用 C/C++ 共享组件库和插件库背后的理论和哲学,同样也讨论了他们应用程序中使用的各种不同的技术和方法。这篇教程中所有的库均使用 GNU Linux 编译器创建。

为什么要使用库

这种方式也被称为“共享组件”或“静态库”,将多个编译后的目标代码文件打包成一个单独的文件称之为库。通常来说会将可以被多个应用程序共享的 C 函数或 C++ 类以及方法从源代码中剥离出来,单独编译并打包成库。共享组件的一个示例就是 C 标准库和 C++ STL 库,这些库会被链接到你的代码中。这样做的好处是,连接时不必声明每个目标文件,因为程序员可以单独引用每个库,这使得在应用程序间重用和共享软件组件变得简单。它同样为软件厂商发布应用程序的接口 API 提供了途径。比较大的组件可以创建成动态共享库,因此这些库可以独立于应用程序存在,有助于减少应用程序大小并节省磁盘空间使用,应用程序可以根据需要调用不同的库组件。

Linux 库分类

目前可以创建两种 Linux C/C++ 库:

  1. 静态库(.a)

    会被链接进目标代码,并成为应用程序的一部分

  2. 动态共享链接库(.so):这种库只有一种结构,不过可以通过两种方式使用

    I. 运行时动态链接但是静态依赖,在编译/链接阶段这些库需要存在,共享库不会包含到可执行文件中,但是需要在运行时绑定。

    II. 在执行过程中使用动态加载链接系统函数进行动态加载/卸载和链接(比如:浏览器插件)

库命名规范

库通常使用 'lib' 作为命名前缀,这适用于所有 C 标准库。当链接时,在命令行中引用该库将不需要包含该库的前缀或后缀。

考虑下面的链接命令:

gcc src-file.-lm -lpthread

这个例子中,在链接时需要 math 库和 thread 库,他们保存在在 /usr/lib/libm.a/usr/lib/libpthread.a 中。

注:GNU 编译器现在有命令行选项 -pthread 而较老的编译器使用 -lpthread 来明确指定使用 thread 库。因此,现在你的命令行看上去应该是这样的:

  gcc src-file.-lm -pthread

静态库(.a)

如何创建一个库(目标代码归档文件):

  1. 编译

    cc -Wall -c ctest1.c ctest2.c

    其中选项 -Wall 代表显示警告,请参考 man page 中的说明。

  2. 创建库 libctest.a

    ar -cvq libctest.a ctest1.o ctest2.o
  3. 列出库中的文件

    ar -t libctest.a
  4. 链接库

    cc -o executable-name prog.c libctest.a
    cc -o executable-name prog.-L/path/to/library-directory -lctest

示例文件:

  1. ctest1.c

    void ctest1(int *i)
    {
       *i=5;
    }
  2. ctest2.c

    void ctest2(int *i)
    {
       *i=100;
    }
  3. 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)

创建共享库(动态链接对象库文件)需要以下步骤:

  1. 创建对象代码

  2. 创建库

  3. 可选的:使用符号链接创建默认的版本

示例

gcc -Wall -fPIC -*.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 开发社区并未达成一致。

编译器选项

  1. -Wall:包含警告,请查看 man pages 获得更详细信息。

  2. -fPIC:编译器指令,输出位置无关的代码。共享库所需的特征,参考 -fpic

  3. -shared:生成可以在运行时链接到其他应用程序的共享库

  4. -Wl, options:向链接器传递选项

    在这里我们传递里链接器的选项是 -soname libctest.so.1-o 选项后的名称会传递给 gcc。

  5. -o 选项:输出操作,在这个例子中共享库的名字将保存为 'libctest.so.1.0'

库符号链接

  1. 编译选项 -lcteset 会使用 /opt/lib/libctest.so 符号链接

  2. 运行时绑定会使用 /opt/lib/libctest.so.1,参考下文

编译使用动态链接库的主程序

编译运行时链接动态链接库 libctest.so.1.0

gcc -Wall -I/path/to/include-files -L/path/to/libraries prog.-lctest -o prog

使用:

gcc -Wall -L/opt/lib prog.-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 库时解决最后两个库的依赖关系:

  1. /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)。

    或者

  2. 在编译或链接命令上明确制定库的路径,比如:-lname-of-lib -L/path/to/lib

    或者

  3. 将库的路径加入环境变量来解决运行时依赖问题:

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/lib

运行程序

  • 配置环境变量:export LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH

  • 执行:prog 或者 ./prog

帮助手册

  1. gcc -- GNU C 编译器

  2. ld -- GNU 链接器

  3. ldd -- 列出库依赖信息

  4. ldconfig --配置动态链接运行时绑定(更新缓存 /etc/ld.so.cache

链接

  1. LDP: Shared libraries

库路径

为了让可执行程序在运行时能找到需要链接的库,需要配置系统使得这些库可以被找到,有以下方法(至少需要其中一种方法):

  1. 将包含动态链接库目录的路径加到 /etc/ld.so.conf 文件中,比如:

    /usr/X11R6/lib
    /usr/lib
    ...
    ..
    /usr/lib/sane
    /usr/lib/mysql
    /opt/lib

    将路径加入到这个文件后需要执行 ldconfig 命令(以 root 账号)来配置链接器运行时绑定。如果你为其他操作系统环境开发,你可以使用 -f file-name 来指定不同的配置文件,更多信息参考 ldconfig 手册

    或者

  2. 增加指定目录到库缓存(需要 root 权限):

    ldconfig -/opt/lib

    其中 /opt/lib 是包含 libctest.so 文件的目录。(如果在开发阶段,仅需要将你当前的目录增加到缓存即可 ldconfig -n .

    这种方式不会永久在系统中包含相关目录,在系统重启后配置信息会丢失。

    或者

  3. 配置环境变量 LD_LIBRARY_PATH 执行包含共享库的路径,这可以指定运行时加载器使用该路径解决依赖关系,几种系统的环境变量名称:

    示例(bash shell ~/.bashrc ):

    ...
    if [ -/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 命令手册

列出目标文件、静态库以及动态库的所有符号

  1. 列出目标文件的符号信息

    nm file.o
  2. 列出静态库中的符号信息

    符号信息会根据静态库中包含的源和目标文件层次进行分类:

    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
    ...
    ...
  3. 列出共享库中的符号信息

    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

参考

  1. Man page for nm    table here

  2. Man page for objdump

使用 readelf 列出共享库符号信息

命令:

readelf -/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__
...
...

参考:readelf man page

库版本

如果功能接口将被修改(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
...
..

参考:

  1. Symbol versioning

  2. GNU.org: ld

使用 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.-ldl

说明:

  1. dlopen("/opt/lib/libctest.so", RTLD_LAZY);

    打开共享库 libctest.so ,第二个参数代表绑定,参考头文件 dlfcn.h,如果出现错误会返回 NULL。选项: RTLD_LAZY:如果指定,操作系统在引用共享库前不会关注 'unresolved symbols' 错误。 RTLD_NOW:当调用 dlopen() 方法时解决所有 'unresolved symbols'* RTLD_GLOBAL:使符号库可见

  2. 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)

说明:

  1. --whole-archive:这个链接指令会指示链接器将后面列出的所有库(本例中的 AA_libs)连接到可执行文件中,即使库中的方法没有被使用过。这个选项通常用于指定运行时可加载库需要使用的库。

  2. -no-whole-archive:如果你列出了额外需要的目标文件时需要指定这个选项。gcc/g++ 编译器在链接时会增加他们自己的静态库列表,当然你不希望这些静态库中未使用的目标代码都被连接到应用程序中,它会对这些静态库使用常规链接行为。

参考:

  1. dlopen() -- 以指定模式打开指定的动态链接库文件

  2. dlclose() -- dlclose用于关闭指定句柄的动态链接库

  3. dlsym() -- 根据动态链接库操作句柄与符号,返回符号对应的地址

  4. 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++ 类在调用函数或者库中必须成对使用,

参考

  1. This is so that there is no surprise if one overloads new/delete in one or the other.

  2. dlopen howto

与 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 等同的函数:

  1. ::LoadLibrary() --  dlopen()

  2. ::GetProcAddress() -- dlsym()

  3. ::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

备注

  1. 在其他共享库钱加载指定的共享库

    export LD_PRELOAD=/usr/lib/libXXX.so.x; 
    exec program

    这可以在 /etc/ld.so.preload 配置,并且可以使用环境变量 LD_PRELOAD 扩展。

  2. 开发环境是 RedHat 7.1 (glibc 2.2.2) 但是需要为兼容 RedHat 6.2 进行编译

    参考:RELEASE-NOTES

    export LD_ASSUME_KERNEL=2.2.5
    . /usr/i386-glibc21-linux/bin/i386-glibc21-linux-env.sh
  3. 高亮错误、警告的环境变量

    export CC="colorgcc"

本文链接:http://www.4byte.cn/learning/119989/linux-zhong-de-jing-tai-ku-dong-tai-gong-xiang-ku-he-ke-jia-zai-ku.html

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐