在网上找到一篇很棒的文章: http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
关注Linux下动态共享库(Dynamic shared library .so)的开发.

1 简单的so实例

源文件

//test1.c
int  test1(){
    
return   1 ;
}

//test2.c
int  test2(){
    
return 2 ;
}

//mytest.c
#include  < stdio.h >
int  test1();
int  test2();
int  main(){
    printf(
" result of test1:= %d\n " ,test1());
    printf(
" result of test2:= %d\n " ,test2());
}

打包成so文件

在代码的目录下运行如下命令: (如果你不是Ubuntu系统,请将命令的sudo都去掉)

gcc -Wall -fPIC -c *.c
gcc -shared -Wl
, -soname , libctest.so .1  -o libctest.so .1.0    *.o
sudo mv libctest.so
.1.0  /usr/lib
sudo ln -sf /usr/lib/libctest.so
.1.0  /usr/lib/libctest.so
sudo ln -sf /usr/lib/libctest.so
.1.0  /usr/lib/libctest.so .1

参数详解:
  • -Wall: 包含warning信息
  • -fPIC: 编译动态库必须,输出不依赖位置的代码(原文 :Compiler directive to output position independent code)
  • -shared: 编译动态库必需选项
  • -W1: 向链接器(Linker)传递一些参数.在这里传递的参数有 "-soname libctest.so.1"
  • -o: 动态库的名字. 在这个例子里最终生成动态库 libctest.so.1.0
两个符号链接的含义:
  • 第一个:允许应用代码用 -lctest 的语法进行编译.
  • 第二个:允许应用程序在运行时调用动态库.

2 so路径设置

为了使应用程序能够在运行时加载动态库,可以通过3种方式指定动态库的路径(以下例子均假定/opt/lib是动态库所在位置):

用ldconfig指定路径

运行

sudo ldconfig -n /opt/lib

/opt/lib 是动态库所在路径.  这种方式简单快捷,便于程序员开发.缺点是重启后即失效.

修改/etc/ld.so.conf文件

打开/etc/ld.so.confg 文件,并将/opt/lib 添加进去.

( 注: 在Ubuntu系统中, 所有so.conf文件都在/etc/ld.so.conf.d目录. 你可以仿照该目录下的.conf文件写一个libctest.conf并将/opt/lib填入)

用环境变量LD_LIBRARY_PATH指定路径

环境变量的名字一般是LD_LIBRARY_PATH, 但是不同的系统可能有不同名字. 例如

Linux/Solaris: LD_LIBRARY_PATH, SGI: LD_LIBRARYN32_PATH, AIX: LIBPATH, Mac OS X: DYLD_LIBRARY_PATH, HP-UX: SHLIB_PATH) ( 注: 此说法未经验证)

修改~/.bashrc , 增加以下脚本:

Linux下动态共享库 - Jules Allee - Jules Allee
if 
[  -d /opt/lib  ] ;
then
   LD_LIBRARY_PATH
= /opt/lib:$LD_LIBRARY_PATH
fi

Linux下动态共享库 - Jules Allee - Jules Allee

export LD_LIBRARY_PATH

在第一章的简单例子中, /usr/lib 是Ubuntu默认的动态库目录,所以我们不须指定动态库目录也能运行应用程序.

3 简单的动态调用so例子


C调用例子

保留第一章的test1.c和test2.c文件,并增加ctest.h头文件如下:

#ifndef CTEST_H
#define  CTEST_H

#ifdef __cplusplus
extern   " C "  {
#endif

int  test1();
int  test2();

#ifdef __cplusplus
}
#endif

#endif
      

我们继续使用第一章生成的libctest.so,仅需增加一个新的应用程序 prog.c:

//prog.c

#include  < stdio.h >
#include 
< dlfcn.h >
#include 
" ctest.h "

int  main( int  argc,  char   ** argv) 
{
   
void   * lib_handle;
   
int  ( * fn)();
   
char   * error;

   lib_handle 
=  dlopen( " libctest.so " , RTLD_LAZY);
   
if  ( ! lib_handle) 
   {
      fprintf(stderr, 
" %s\n " , dlerror());
      
return   1 ;
   }

   fn 
=  dlsym(lib_handle,  " test1 " );
   
if  ((error  =  dlerror())  !=  NULL)  
   {
      fprintf(stderr, 
" %s\n " , error);
      
return   1 ;
   }

   
int  y = fn();
   printf(
" y=%d\n " ,y);

   dlclose(lib_handle);
   
   
return   0 ;
}
       


然后用如下命令运行(由于没有使用其他库,所以忽略-L等参数):

gcc -Wall prog.c -lctest -o prog -ldl
.
/ progdl

方法简介

dlopen( " libctest.so " , RTLD_LAZY): 加载动态库,如果加载失败返回NULL. 第二个参数可以是:
  • RTLD_LAZY: lazy模式. 直到源码运行到改行才尝试加载.
  • RTLD_NOW: 马上加载.
  • RTLD_GLOBAL: 不解(原文: Make symbol libraries visible.)
dlsym(lib_handle,  " test1 " ): 返回函数地址. 如果查找函数失败则返回NULL.

和微软的动态加载dll技术对比如下:
  • ::LoadLibrary() - dlopen()
  • ::GetProcAddress() - dlsym()
  • ::FreeLibrary() - dlclose()

C++调用例子


增加一个prog2.cpp
#include  < dlfcn.h >
#include 
< iostream >
#include 
" ctest.h "


using   namespace  std;
int  main(){
    
void   * lib_handle;
    
// MyClass* (*create)();
    
// ReturnType (* func_name)();
     int  ( *  func_handle)();  
    
string  nameOfLibToLoad( " libctest.so " );
    lib_handle 
=  dlopen(nameOfLibToLoad.c_str(), RTLD_LAZY);
    
if  ( ! lib_handle) {
        cerr 
<<   " Cannot load library:  "   <<  dlerror()  <<  endl;
    }
    
//  reset errors
    dlerror();
    
//  load the symbols (handle to function "test")
    
// create = (MyClass* (*)())dlsym(handle, "create_object");
    
// destroy = (void (*)(MyClass*))dlsym(handle, "destroy_object");
    func_handle  = ( int ( * )())dlsym(lib_handle,  " test1 " );

    
const   char *  dlsym_error  =  dlerror();
    
if  (dlsym_error) {
        cerr 
<<   " Cannot load symbol test1:  "   <<  dlsym_error  <<  endl;
    }
    
    cout
<< " result:=  " << func_handle() << endl;
    
    dlclose(lib_handle);
    
    
return   0 ;
}




然后用如下命令运行:

g++ -Wall prog2.cpp -lctest -o prog2 -ldl
.
/ prog2

编译命令简介

假设C文件是prog.c, C++调用文件是prog2.cpp,那么编译脚本分别是:

C语言:

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

C++语言:

 g++ -Wall -I/path/to/include-files -L/path/to/libraries prog2.cpp -lctest -ldl -o prog2

参数详解:
  • -I: 指定头文件目录.
     
  • -L: 指定库目录.
  • -lctest: 调用动态库libctest.so.1.0. 如果在打包so时没有创建第一个符号链接,那么这个参数会导致编译不成功.
  • -ldl: C++编译必须
     

相关知识
 


命令 ldd appname 可以查看应用程序所依赖的动态库,运行如下命令:

ldd prog

在我的机器输出:

    linux-gate.so .1   = >  (0xb80d4000)
    libctest.so
.1   = > /usr/lib/libctest.so .1  (0xb80be000)
    libc.so
.6   = > /lib/tls/i686/cmov/libc.so .6  (0xb7f5b000)
    /lib/ld-linux.so
.2  (0xb80d5000)

4 C++开发带class的so

//myclass.h
#ifndef __MYCLASS_H__
#define  __MYCLASS_H__

class  MyClass
{
public :
  MyClass();

  
/*  use virtual otherwise linker will try to perform static linkage  */
  
virtual   void  DoSomething();

private :
  
int  x;
};

#endif

//myclass.cpp
#include  " myclass.h "
#include 
< iostream >

using   namespace  std;

extern   " C "  MyClass *  create_object()
{
  
return   new  MyClass;
}

extern   " C "   void  destroy_object( MyClass *   object  )
{
  delete 
object ;
}

MyClass::MyClass()
{
  x 
=   20 ;
}

void  MyClass::DoSomething()
{
  cout
<< x << endl;
}

//class_user.cpp
#include  < dlfcn.h >
#include 
< iostream >
#include 
" myclass.h "

using   namespace  std;

int  main( int  argc,  char   ** argv)
{
  
/*  on Linux, use "./myclass.so"  */
  
void *  handle  =  dlopen( " ./myclass.so " , RTLD_LAZY);

  MyClass
*  ( * create)();
  
void  ( * destroy)(MyClass * );

  create 
=  (MyClass *  ( * )())dlsym(handle,  " create_object " );
  destroy 
=  ( void  ( * )(MyClass * ))dlsym(handle,  " destroy_object " );

  MyClass
*  myClass  =  (MyClass * )create();
  myClass
-> DoSomething();
  destroy( myClass );
}


编译和运行:

 g++ -fPIC -shared myclass.cpp -o myclass.so
 g++ class_user.cpp -ldl -o class_user
 ./class_user