Linux 動態鏈接庫編程基礎

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([email protected]) 周立發 (http://zhoulifa.9999mb.com)
Linux 愛好者 Linux 知識傳播者 SOHO 族 開發者 最擅長 C 語言
*date time: 2006-11-15 21:11:54
*Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
* 但請遵循 GPL
*Hope:希望越來越多的人貢獻自己的力量,爲科學技術發展出力
* 科技站在巨人的肩膀上進步更快!感謝有開源前輩的貢獻!
*感謝 [email protected] 提供原始代碼,

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

然後準備這樣一個生成動態鏈接庫的源文件:

[Copy to clipboard]
CODE:
/************關於本文檔********************************************
*filename:  get.cpp
*purpose: 一個動態鏈接庫源文件示例
*tided by: zhoulifa([email protected]) 周立發 (http://zhoulifa.9999mb.com)
Linux 愛好者 Linux 知識傳播者 SOHO 族 開發者 最擅長 C 語言
*date time:2006-11-15 21:11:54
*Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
* 但請遵循 GPL
*Hope:希望越來越多的人貢獻自己的力量,爲科學技術發展出力
* 科技站在巨人的肩膀上進步更快!感謝有開源前輩的貢獻!
*感謝 [email protected] 提供原始代碼,

我在他的基礎上整理了此文
*********************************************************************/
#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([email protected]) 周立發 (http://zhoulifa.9999mb.com)
Linux 愛好者 Linux 知識傳播者 SOHO 族 開發者 最擅長 C 語言
*date time:2006-11-15 21:11:54
*Note: 任何人可以任意複製代碼並運用這些文檔,當然包括你的商業用途
* 但請遵循 GPL
*Hope:希望越來越多的人貢獻自己的力量,爲科學技術發展出力
* 科技站在巨人的肩膀上進步更快!感謝有開源前輩的貢獻!
*感謝 [email protected] 提供原始代碼,

我在他的基礎上整理了此文
*********************************************************************/
#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 GNU

General Public License, and you are
welcome 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    /* 這是在程序的 main 處設置斷點 */
Breakpoint 1 at 0x804853c: file pk.cpp,line 7.
(gdb) b set      /* 這是在程序的 set 處設置斷點 */
Function "set" not defined.
Make breakpoint pending on future shared

library 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" resolved

Breakpoint 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) n

Breakpoint 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) at

pk.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 GNU

General 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 shared

library load? (y or [n]) y
Breakpoint 2 (set) pending.
(gdb) run  /* 雖然調試操作都一樣,但程序執行失敗 */
Starting program: /data/example/c/app
/data/example/c/app: error while loading

shared 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:
    gcc -shared Lib.o -o Lib.so
    gcc -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)   {     printf("Linux library test!/n");   }  


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


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

先看靜態庫

首先做成靜態庫 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 ?恩,這就是靜態與動態的最大區別,靜
態情況下,他把庫直接加載到程序裏,而在動態鏈接的時候,他只是保留接口,將動態庫與
程序代碼獨立。這樣就可以提高代碼的可複用度,和降低程序的耦合度。

參考:http://www-128.ibm.com/developerworks/cn/linux/sdk/dll/index.html
http://www.oklinux.cn/html/developer/java/20070630/33743.html
http://blog.csdn.net/wenxy1/archive/2005/12/15/553216.aspx

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章