Linux动态链接库编程入门


动态链接库是一种通用的软件组件技术,是多种操作系统中提供基本服务的方式。比如Win32内核就是3个DLL文件构成。这种技术在Linux操作系统下也有对应的实现,就是Linux标准对象Standard Ojbect,对应的文件扩展名为.so。


  下面通过一个简单的例子开始介绍Linux标准对象。

  我们的标准对象文件含有一个函数,不需要声明export导出符号,只需要编译器设置即可。如下:


#include <stdio.h>
 #include <stdlib.h>

 void show() {
   printf("Standard Object by gashero/n");
 }


  保存为myso.c文件,按照如下编译:


$ gcc -fPIC -shared -o libmyso.so myso.c


  执行生成一个libmyso.so文件,按照Linux标准对象的命名惯例,应该在库名称之前加上"lib"前缀,尽管不是必须的。编译开关-fPIC代表函数符号可以重定向,-shared代表编译结果是一个标准对象。


  不同于Win32DLL,Linux标准对象中的所有函数都是直接导出的,都可以被调用程序所访问。下面我们编写调用程序:


#include <stdio.h>

 int main() {
   printf("Invoke my so/n");
   show();
   return 0;
 }

   保存为invoke.c,按照如下gcc开关编译:

 $ gcc -o test invoke.c ./libmyso.so


  编译生成test可执行文件。如上编译条件的最后一条需要是所调用的标准对象文件名,注意必须含有路径。如果只是使用libmyso.so,则必须确保这个文件在可访问的PATH下面。本例所使用的文件名"./libmyso.so"是当前路径下的,使用了相对路径。


  如下测试结果:


$ ./test
 Invoke my so
 Standard Object by gashero


  希望上文的例子可以对大家有所帮助。



如何在 Linux 下调试动态链接库
大家都知道在 Linux 可以用 gdb 来调试应用程序,当然前提是用 gcc 编译程序时要加上

-g 参数。
我这篇文章里将讨论一下用 gdb 来调试动态链接库的问题。

  首先,假设我们准备这样的一个动态链接库:

QUOTE:
库名称是: ggg
动态链接库文件名是: libggg.so
头文件是: get.h
提供这样两个函数调用接口:
    int get ();
    int set (int a);

要生成这样一个动态链接库,我们首先编写这样一个头文件:

[Copy to clipboard]
CODE:
/************关于本文档********************************************
*filename: get.h
*purpose:  一个动态链接库头文件示例
*tided by: zhoulifa(zhoulifa@163.com) 周立发 (http://zhoulifa.9999mb.com)
Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言
*date time: 2006-11-15 21:11:54
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循 GPL
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*感谢 vcclass@hotmail.com 提供原始代码,

我在他的基础上整理了此文
*********************************************************************/
int get ();
int set (int a);

然后准备这样一个生成动态链接库的源文件:

[Copy to clipboard]
CODE:
/************关于本文档********************************************
*filename:  get.cpp
*purpose: 一个动态链接库源文件示例
*tided by: zhoulifa(zhoulifa@163.com) 周立发 (http://zhoulifa.9999mb.com)
Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言
*date time:2006-11-15 21:11:54
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循 GPL
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*感谢 vcclass@hotmail.com 提供原始代码,

我在他的基础上整理了此文
*********************************************************************/

#include <stdio.h>
#include "get.h"
 
static int x=0;
int get ()
{
        printf ("get x=%d/n", x);
        return x;
}
int set (int a)
{
        printf ("set a=%d/n", a);
        x = a;
        return x;
}

然后我们用 GNU 的 C/C++ 编译器来生成动态链接库,编译命令如下:

QUOTE:
g++ get.cpp -shared -g -DDEBUG -o libggg.so

这样我们就准备好了动态链接库了,下面我们编写一个应用程序来调用此动态链接库,源代码如下:

[Copy to clipboard]
CODE:
/************关于本文档********************************************
*filename: pk.cpp
*purpose: 一个调用动态链接库的示例
*tided by: zhoulifa(zhoulifa@163.com) 周立发 (http://zhoulifa.9999mb.com)
Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言
*date time:2006-11-15 21:11:54
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循 GPL
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*感谢 vcclass@hotmail.com 提供原始代码,

我在他的基础上整理了此文
*********************************************************************/

#include <stdio.h>
#include "get.h"
int main (int argc, char** argv)
{
        int a = 100;
        int b = get ();
        int c = set (a);
        int d = get ();
 
        printf ("a=%d,b=%d,c=%d,d=%d/n",a,b,c,d);
        return 0;
}

编译此程序用下列命令,如果已经把上面生成的 libggg.so 放到了库文件搜索路径指定的文件目录,比如 /lib 或 /usr/lib 之类的,就用下面这条命令:

QUOTE:
g++ pk.cpp -o app -Wall -g -lggg

否则就用下面这条命令:

QUOTE:
g++ pk.cpp -o app -Wall -g -lggg -L`pwd`

下面我们就开始调试上面命令生成的 app 程序吧。如果已经把上面生成的 libggg.so 放到了库文件搜索路径指定的文件目录,比如 /lib或 /usr/lib 之类的,调试就顺利完成,如下

QUOTE:
linux#gdb">zhoulifa@linux#gdb ./app
GNU gdb 6.4-debian
Copyright 2005 Free Software Foundation,Inc.
GDB is free software, covered by the GNUGeneral Public License, and you are
welcome to change it and/or distributecopies of it under certain conditions.
Type "show copying" to see theconditions.There is absolutely no warranty for GDB.
Type "show warranty" for details.This GDB was configured as "i486-linux-
gnu"...Using host libthread_db library"/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) b main    /* 这是在程序的 main 处设置断点 */
Breakpoint 1 at 0x804853c: file pk.cpp,line 7.
(gdb) b set      /* 这是在程序的 set 处设置断点 */
Function "set" not defined.
Make breakpoint pending on future sharedlibrary load? (y or [n]) y /* 这里必须选择 y 调试程序才会跟踪到动态链接库内部去
*/Breakpoint 2 (set) pending.
(gdb) run /* 开始运行我们的程序,直到遇见断点时暂停 */
Starting program: /data/example/c/app
Breakpoint 3 at 0xb7f665f8: file get.cpp,line 11.
Pending breakpoint "set" resolvedBreakpoint 1, main (argc=1,argv=0xbf990504) at pk.cpp:7
7               int a = 100;
(gdb) n     /* 继续执行程序的下一行代码*/
8               int b = get ();
(gdb) n      /* 程序执行到了我们断点所在的动态链接库了 */
get x=0
9               int c = set (a);(gdb) nBreakpoint 3, set (a=100) at get.cpp:11
11              printf ("set a=%d/n", a);(gdb) list   /* 查看当前代码行周围的代码,证明我们已经跟踪到动态链接库的源代码里面了 */
6               printf ("get x=%d/n", x);
7               return x;
8       }
9       int set (int a)
10      {
11              printf ("set a=%d/n", a);
12              x = a;
13              return x;
14      }
(gdb) n
set a=100
12              x = a;(gdb) n
13              return x;(gdb) n
14      }
(gdb) n
main (argc=1, argv=0xbf990504) atpk.cpp:10
10              int d = get ();
(gdb) n
get x=100
11              printf ("a=%d,b=%d,c=%d,d=%d/n",a,b,c,d);
(gdb) n
a=100,b=0,c=100,d=100
12              return 0;
(gdb) c
Continuing.Program exited normally.
(gdb) quit  /* 程序顺利执行结束 */zhoulifa@linux#如果我们没有把动态链接库放到指定目录,比如/lib里面,调试就会失败,过程如下:
QUOTE:
zhoulifa@linux# gdb ./app
GNU gdb 6.4-debian
Copyright 2005 Free Software Foundation,Inc.
GDB is free software, covered by the GNUGeneral Public License, and you arewelcome to change it and/or distribute
copies of it under certain conditions.
Type "show copying" to see theconditions.
There is absolutely no warranty for GDB.Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...Using host libthread_db library
"/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) b main
Breakpoint 1 at 0x804853c: file pk.cpp,line 7.
(gdb) b set
Function "set" not defined.
Make breakpoint pending on future sharedlibrary load? (y or [n]) y
Breakpoint 2 (set) pending.
(gdb) run  /* 虽然调试操作都一样,但程序执行失败 */
Starting program: /data/example/c/app
/data/example/c/app: error while loadingshared libraries: libggg.so: cannot open
shared object file: No such file or
directory
Program exited with code 0177.
(gdb) quit
zhoulifa@linux# 本次实验的环境是: 

CPU:AMD Athlon(tm) 64 Processor 3000+ 

内存:512M 

OS: 
Ubuntu GNU/Linux 6.06 dapper LTS 

gcc:gcc 版本 4.0.3 ( 
Ubuntu 4.0.3-1ubuntu5)



Linux下使用动态链接库
使用动态链接库,我认为,再比较大的程序运行过程中,是一种很有优势的。所以就花了一天时间来学习一下。



使用动态链接库,需要了解一下内容



头文件:


<dlfcn.h> 

函数: 

void *dlopen(const char *filename, int flag); 

const char *dlerror(void); 

void *dlsym(void *handle, char *symbol); 

int dlclose(void *handle);



相关的信息可以通过

man dlopen查询



在编译动生成态链接库的时候,


需要参数 -shared



在使用动态链接库的时候,


需要参数 -ldl



其他相关参数有


-fpic -fPIC  -rdynamic 


如有库函数文件Lib.c, 主函数文件Main.c 

则有如下Makefile 


all: comple link 


comple: 

    gcc -c Lib.c -o Lib.o 

    gcc -c Main.c -o Main.o 


link: 

-shared Lib.o -o Lib.so 

-ldl Main.o -o Main


另外,在C++中使用动态连接库的时候,请注意:


必须用


extern "C" 

{ 

}



将动态苦定义为C的编译连接方式



否则由于C++命名方式于C不同,会造成生成的动态链接库不能使用(无法定位或函数)



文章选取的例子非常简单,上手容易,只是为了讲述静态与动态链接库的生成和链接过

    程,还有他们之间的区别。以下例子在 gcc 4.1.1 下顺利通过。




文件预览 (补充)


文件目录树如下,如你所见,非常简单。

  • libtest/  
  • |-- lt.c  
  • |-- lt.h  
  • `-- test.c  




代码


#lt.c


• /* lt.c
•  *
•  */
•   
• #include <stdio.h>
•   
• void myprint(void)  
• {  
• "Linux library test!/n");  
• }



# lt.h

#test.c

• /* lt.h
•  * 
•  */
•   
• void myprint(void);

• /* test.c
•  *
•  */
•   
• #include "lt.h"
•   
• int main(void)  
• {  
•   myprint();  
• return
• }


先看静态库


首先做成静态库 liblt.a 。

  • $ gcc -c lt.c -o lt.o  
  • $ ar cqs liblt.a lt.o  




再者,链接,这里指定了静态库的位置,注意文件顺序不可乱序。


  • $ gcc test.o liblt.a -o test  



这个时候再来看他的引用库情况。

• $ ldd test  
•         linux-gate.so.1 =>  (0xffffe000)  
•         libc.so.6 => /lib/libc.so.6 (0xb7e29000)  
•         /lib/ld-linux.so.2 (0xb7f6e000)




动态库


做成动态库 liblt.so 。

  • $ gcc -c lt.c -o lt.o  
  • $ gcc -shared -Wall -fPIC lt.o -o liblt.so  



链接方法I,拷贝到系统库里再链接,让gcc自己查找

  • $ sudo cp liblt.so /usr/lib  
  • $ gcc -o test test.o -llt  


这里我们可以看到了 -llt 选项,-l[lib_name] 指定库名,他会主动搜索

lib[lib_name].so 。这个搜索的路径可以通过 gcc --print-search-dirs来查找。


链接方法II,手动指定库路径

  • $ cc -o test test.o -llt -B /path/to/lib


这里的-B 选项就添加 /path/to/lib 到gcc搜索的路径之中。这样链接没有问题但是方法II

中手动链接好的程序在执行时候仍旧需要指定库路径(链接和执行是分开的)。需要添加系

统变量 LD_LIBRARY_PATH :

• $ export LD_LIBRARY_PATH=/path/to/lib



这个时候再来检测一下test程序的库链接状况(方法I情况)

• $ ldd test  
•         linux-gate.so.1 =>  (0xffffe000)  
•         liblt.so => /usr/lib/liblt.so (0xb7f58000)  
•         libc.so.6 => /lib/libc.so.6 (0xb7e28000)  
•         /lib/ld-linux.so.2 (0xb7f6f000)


恩,是不是比静态链接的程序多了一个 liblt.so ?恩,这就是静态与动态的最大区别,静

态情况下,他把库直接加载到程序里,而在动态链接的时候,他只是保留接口,将动态库与

程序代码独立。这样就可以提高代码的可复用度,和降低程序的耦合度。