動态庫*.so在linux下用c和c++程式設計時經常會碰到,最近在網站找了幾篇文章介紹動态庫的編譯和連結,總算搞懂了這個之前一直不太了解得東東,這裡做個筆記,也為其它正為動态庫連結庫而苦惱的兄弟們提供一點幫助。
1、動态庫的編譯
下面通過一個例子來介紹如何生成一個動态庫。這裡有一個頭檔案:so_test.h,三個.c檔案:test_a.c、test_b.c、test_c.c,我們将這幾個檔案編譯成一個動态庫:libtest.so。
so_test.h:
#include <stdio.h>
#include <stdlib.h>
void test_a();
void test_b();
void test_c();
test_a.c:
#include "so_test.h"
void test_a()
{
printf("this is in test_a.../n");
}
test_b.c:
#include "so_test.h"
void test_b()
{
printf("this is in test_b.../n");
}
test_c.c:
#include "so_test.h"
void test_c()
{
printf("this is in test_c.../n");
}
将這幾個檔案編譯成一個動态庫:libtest.so
$ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so
2、動态庫的連結
在1、中,我們已經成功生成了一個自己的動态連結庫libtest.so,下面我們通過一個程式來調用這個庫裡的函數。程式的源檔案為:test.c。
test.c:
#include "so_test.h"
int main()
{
test_a();
test_b();
test_c();
return 0;
}
l 将test.c與動态庫libtest.so連結生成執行檔案test:
$ gcc test.c -L. -ltest -o test
l 測試是否動态連接配接,如果列出libtest.so,那麼應該是連接配接正常了
$ ldd test
l 執行test,可以看到它是如何調用動态庫中的函數的。
3、編譯參數解析
最主要的是GCC指令行的一個選項:
-shared 該選項指定生成動态連接配接庫(讓連接配接器生成T類型的導出符号表,有時候也生成弱連接配接W類型的導出符号),不用該标志外部程式無法連接配接。相當于一個可執行檔案
l -fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯後的代碼是位置相關的是以動态載入時是通過代碼拷貝的方式來滿足不同程序的需要,而不能達到真正代碼段共享的目的。
l -L.:表示要連接配接的庫在目前目錄中
l -ltest:編譯器查找動态連接配接庫時有隐含的命名規則,即在給出的名字前面加上lib,後面加上.so來确定庫的名稱
l LD_LIBRARY_PATH:這個環境變量訓示動态連接配接器可以裝載動态庫的路徑。
l 當然如果有root權限的話,可以修改/etc/ld.so.conf檔案,然後調用 /sbin/ldconfig來達到同樣的目的,不過如果沒有root權限,那麼隻能采用輸出LD_LIBRARY_PATH的方法了。
4、注意
調用動态庫的時候有幾個問題會經常碰到,有時,明明已經将庫的頭檔案所在目錄 通過 “-I” include進來了,庫所在檔案通過 “-L”參數引導,并指定了“-l”的庫名,但通過ldd指令察看時,就是死活找不到你指定連結的so檔案,這時你要作的就是通過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf檔案來指定動态庫的目錄。通常這樣做就可以解決庫無法連結的問題了。
makefile裡面怎麼正确的編譯和連接配接生成.so庫檔案,然後又是在其他程式的makefile裡面如何編譯和連接配接才能調用這個庫檔案的函數????
答:
你需要告訴動态連結器、加載器ld.so在哪裡才能找到這個共享庫,可以設定環境變量把庫的路徑添加到庫目錄/lib和/usr/lib,LD_LIBRARY_PATH=$(pwd),這種方法采用指令行方法不太友善,一種替代方法
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^注釋^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LD_LIBRARY_PATH可以在 /etc/profile還是 ~/.profile還是 ./bash_profile裡設定,或者.bashrc裡,
改完後運作source /etc/profile或 . /etc/profile
更好的辦法是添入/etc/ld.so.conf, 然後執行 /sbin/ldconfig
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^注釋^^^^^^^^^^^^^^^^^^^^^^^^^^^^
是把庫路徑添加到/etc/ld.so.conf,然後以root身份運作ldconfig
也可以在連接配接的時候指定檔案路徑和名稱 -I -L.
GCC=gcc
CFLAGS=-Wall -ggdb -fPIC
#CFLAGS=
all: libfunc test
libfunc:func.o func1.o
$(GCC) -shared -Wl,-soname,libfunc.so.1 -o libfunc.so.1.1 $<
ln -sf libfunc.so.1.1 libfunc.so.1
ln -sf libfunc.so.1 libfunc.so
***********************************************注釋************************************************
ln -s是用來建立軟連結,也就相當于windows中的快捷方式,在目前目錄中建立上一級目錄中的檔案ttt的命名為ttt2軟連結的指令是ln -s ../ttt ttt2,如果原檔案也就是ttt檔案删除的話,ttt2也變成了空檔案。
ln -d是用來建立硬連結,也就相當于windows中檔案的副本,當原檔案删除的時候,并不影響“副本”的内容。
編譯目标檔案時使用gcc的-fPIC選項,産生與位置無關的代碼并能被加載到任何位址:
gcc –fPIC –g –c liberr.c –o liberr.o
使用gcc的-shared和-soname選項;
使用gcc的-Wl選項把參數傳遞給連接配接器ld;
使用gcc的-l選項顯示的連接配接C庫,以保證可以得到所需的啟動(startup)代碼,進而避免程式在使用不同的,可能不相容版本的C庫的系統上不能啟動執行。
gcc –g –shared –Wl,-soname,liberr.so –o liberr.so.1.0.0 liberr.o –lc
建立相應的符号連接配接:
ln –s liberr.so.1.0.0 liberr.so.1;
ln –s liberr.so.1.0.0 liberr.so;
在MAKEFILE中:
$@
表示規則中的目标檔案集。在模式規則中,如果有多個目标,那麼,"$@"就是比對于目标中模式定義的集合。
$%
僅當目标是函數庫檔案中,表示規則中的目标成員名。例如,如果一個目标是"foo.a(bar.o)",那麼,"$%"就是"bar.o","$@"就是 "foo.a"。如果目标不是函數庫檔案(Unix下是[.a],Windows下是[.lib]),那麼,其值為空。
$<
依賴目标中的第一個目标名字。如果依賴目标是以模式(即"%")定義的,那麼"$<"将是符合模式的一系列的檔案集。注意,其是一個一個取出來的。
$?
所有比目标新的依賴目标的集合。以空格分隔。
$^
所有的依賴目标的集合。以空格分隔。如果在依賴目标中有多個重複的,那個這個變量會去除重複的依賴目标,隻保留一份。
*********************************************注釋***********************************************************************
test: test.o libfunc
$(GCC) -o test test.o -L. -lfunc
%.o:%.c
$(GCC) -c $(CFLAGS) -o $@ $<
clean:
rm -fr *.o
rm -fr *.so*
rm -fr test
要生成.so檔案,cc要帶-shared 參數;要調用.so的檔案,比如libfunc.so,可以在cc指令最後加上-lfunc,還要視情況加上 -L/usr/xxx 指出libfunc.so的路徑;這樣,在你要編譯的源檔案中就可以調用libfunc.so這個庫檔案的函數.
前面的都說的差不多了,最後提醒一下最好提供一個接口頭檔案
動态加載,用dlopen,dlclose,dlsym