​​


翻译并根据实际情况进行了小小修改,仅关注Linux下动态共享库(Dynamic shared library .so)的开发.


1 简单的so实例

源文件

//test1.c

int test1(){

return 1;

}


//test2.c

int test2(){

return2;

}


//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 , 增加以下脚本:


Ubuntu下动态共享库(so)开发精悍教程_动态库

if [ -d /opt/lib ];

then

LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH

fi


Ubuntu下动态共享库(so)开发精悍教程_动态库


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