http://www.cnitblog.com/yunshichen/archive/2009/08/28/61065.html
在网上找到一篇很棒的文章: 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 , 增加以下脚本:
if
[
-d /opt/lib
]
;
then
LD_LIBRARY_PATH
=
/opt/lib:$LD_LIBRARY_PATH
fi
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