.netcore在linux下使用P/invoke方式調用linux動態庫


復制代碼
 1 [DllImport(@"libdl.so.2")]
 2         public static extern IntPtr dlopen(string filename, int flags);
 3         [DllImport("libdl.so.2")]
 4         public static extern IntPtr dlsym(IntPtr handle, string symbol);
 5 
 6         [DllImport("libdl.so.2", EntryPoint = "dlopen")]
 7         private static extern IntPtr UnixLoadLibrary(String fileName, int flags);
 8 
 9         [DllImport("libdl.so.2", EntryPoint = "dlclose", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
10         private static extern int UnixFreeLibrary(IntPtr handle);
11 
12         [DllImport("libdl.so.2", EntryPoint = "dlsym", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
13         private static extern IntPtr UnixGetProcAddress(IntPtr handle, String symbol);
14 
15         [DllImport("libdl.so.2", EntryPoint = "dlerror", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
復制代碼

正常情況下,都是可以調用成功的

如果出現調用失敗的情況,可能是so文件缺少了一些依賴文件,可以通過ldd命令進行查看

1
ldd libzmq.so

如果有某些依賴文件找不到,會出現not found的字樣,比如下面這種

/usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by */3rd-party/protobuf-2.4.1/src/.libs/libprotobuf.so.7)
/usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by */3rd-party/protobuf-2.4.1/src/.libs/libprotoc.so.7)

可以使用string命令查找是否確實缺少了依賴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
strings /usr/lib64/libstdc++.so.6 |grep GLIBCXX 得到結果
 
GLIBCXX_3.4
GLIBCXX_3.4.1
GLIBCXX_3.4.2
GLIBCXX_3.4.3
GLIBCXX_3.4.4
GLIBCXX_3.4.5
GLIBCXX_3.4.6
GLIBCXX_3.4.7
GLIBCXX_3.4.8
GLIBCXX_3.4.9
GLIBCXX_3.4.10
GLIBCXX_3.4.11
GLIBCXX_3.4.12
GLIBCXX_3.4.13
GLIBCXX_3.4.14
GLIBCXX_3.4.15
GLIBCXX_3.4.16
GLIBCXX_3.4.17
GLIBCXX_DEBUG_MESSAGE_LENGTH

確實缺少了文件,這種情況下,我們需要使用find命令來查找依賴文件

1
find / -name libstdc++.so.6*

如果能找到依賴的so文件,可以使用cp命令將文件復制到lib64目錄

1
cp /usr/local/lib64/libstdc++.so.6.0.20 /usr/lib64  //復制文件

Centos下系統目錄是/usr/lib64,Suse下可能系統目錄會有不同

如果有舊文件,可以使用rm命令,先刪除舊文件

1
sudo rm -rf /usr/lib64/libstdc++.so.6   //刪除舊文件

最后在使用ln命令,鏈接到新文件

1
sudo ln -s /usr/lib64/libstdc++.so.6.0.20 /usr/lib64/libstdc++.so.6  //鏈接到新版本 (libstdc++.so.6.0.20是復制到linux中的文件的文件名)

這些都做好之后,舊可以測試dlopen命令是否能正常打開文件了,如果可以正常打開,那dllimport方式就可以正常使用

沒有開發dllimport的源碼,很懷疑它內部也是調用了linux下的dlopen命令來調用so文件

除了直接使用dllimport方式來調用,還可以使用委托的方式,來調用so文件

下面是測試代碼,可以比較完整說明.netcore下p/invoke方式調用so文件

 public class SoTester
     {
         private const string LibraryName = "libzmq";
 
         const int RTLD_NOW = 2; // for dlopen's flags
         const int RTLD_GLOBAL = 8;
 
         [DllImport(@"libdl.so.2")]
         public static extern IntPtr dlopen(string filename, int flags);
         [DllImport("libdl.so.2")]
         public static extern IntPtr dlsym(IntPtr handle, string symbol);
 
         [DllImport("libdl.so.2", EntryPoint = "dlopen")]
         private static extern IntPtr UnixLoadLibrary(String fileName, int flags);
 
         [DllImport("libdl.so.2", EntryPoint = "dlclose", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
         private static extern int UnixFreeLibrary(IntPtr handle);
 
         [DllImport("libdl.so.2", EntryPoint = "dlsym", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
         private static extern IntPtr UnixGetProcAddress(IntPtr handle, String symbol);
 
         [DllImport("libdl.so.2", EntryPoint = "dlerror", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
         private static extern IntPtr UnixGetLastError();
 
         public delegate int sumHandler(int a, int b);
         public static sumHandler sumfunc = null;
 
         [DllImport("libNativeLib.so", EntryPoint = "sum", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
         public static extern int Sum(int a, int b);
 
         public void Start()
         {
             IntPtr libPtr = IntPtr.Zero;
 
             string libName = $"{AppContext.BaseDirectory}libNativeLib.so";
 
             libPtr = UnixLoadLibrary(libName, 2 | 8);
 
             //libPtr = dlopen(libName, RTLD_NOW);
 
             if (libPtr != IntPtr.Zero)
                 Console.WriteLine($"調用dlopen打開{libName}成功");
             else
                 Console.WriteLine($"調用dlopen打開{libName}失敗");
 
             var sumPtr = UnixGetProcAddress(libPtr, "sum");
 
             if (sumPtr != IntPtr.Zero)
                 Console.WriteLine($"dlopen調用sum成功");
             else
                 Console.WriteLine($"dlopen調用sum失敗");
 
             sumfunc = Marshal.GetDelegateForFunctionPointer<sumHandler>(sumPtr);
 
             int ret = sumfunc(1, 3);
 
             Console.WriteLine($"調用sum結果:{ret}");
 
             var sumRet = Sum(5, 7);
 
             Console.WriteLine($"DllImport調用sum結果:{sumRet}");
 
             //var libname2 = $"libc.so.6";
             var libname2 = $"{AppContext.BaseDirectory}libzmq.so";
             //var libname2 = $"{AppContext.BaseDirectory}libAdminConsole.so";
             var consolePtr = UnixLoadLibrary(libname2, 2 | 8);
             var erroPtr = UnixGetLastError();
             Console.WriteLine($"錯誤描述:{Marshal.PtrToStringAnsi(erroPtr)}");
 
             if (consolePtr != IntPtr.Zero)
                 Console.WriteLine($"打開{libname2}成功");
             else
                 Console.WriteLine($"打開{libname2}失敗");
         }
   }

 

 

ps:目前測試過CentOS7和suse12 SP3,個人感覺,如果項目中需要使用P/INVOKE最好還是放在suse上跑,centos默認的gcc版本比較低,升級gcc又很麻煩。suse各個組件齊全,只是免費版無法更新組件,p/invoke直接可以使用。其他版本linux沒有使用。

 

 
 __________________________________________________________________________________________________________________________

Linux下gcc編譯生成動態鏈接庫*.so文件並調用它

  https://blog.csdn.net/flyztek/article/details/73612469

 

Linux下gcc編譯生成動態鏈接庫*.so文件並調用它

 

動態庫*.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"
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;
}
將test.c與動態庫libtest.so鏈接生成執行文件test:
$ gcc test.c -L. -ltest -o test
測試是否動態連接,如果列出libtest.so,那么應該是連接正常了
$ ldd test
執行test,可以看到它是如何調用動態庫中的函數的。
3、編譯參數解析
最主要的是GCC命令行的一個選項:
-shared該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當於一個可執行文件

-fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯后的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。

-L.:表示要連接的庫在當前目錄中

-ltest:編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,后面加上.so來確定庫的名稱

LD_LIBRARY_PATH:這個環境變量指示動態連接器可以裝載動態庫的路徑。

當然如果有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文件來指定動態庫的目錄。通常這樣做就可以解決庫無法鏈接的問題了。

在linux下可以用export命令來設置這個值,在linux終端下輸入:
export LD_LIBRARY_PATH=/opt/au1200_rm/build_tools/bin: $LD_LIBRARY_PATH:   
然后再輸入:export   
即會顯示是否設置正確   
export方式在重啟后失效,所以也可以用 vim /etc/bashrc ,修改其中的LD_LIBRARY_PATH變量。   
例如:LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/au1200_rm/build_tools/bin。

 

 

Linux下靜態庫,動態庫,以及arm平台下庫的基本概念

 

一、基本概念

1.1、什么是庫

在 windows 平台和 Linux 平台下都大量存在着庫。

本質上來說庫是 一種可執行代碼的二進制形式,可以被操作系統載入內存執行。

由於 windows 和 linux 的平台不同(主要是編譯器、匯編器和連接器 的不同),因此二者庫的二進制是不兼容的。

本文僅限於介紹 linux 下的庫。

1.2、 庫的種類

linux 下的庫有兩種:靜態庫和共享庫(動態庫)。

二者的不同點在於代碼被載入的時刻不同。

靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積較大。

靜態用.a為后綴, 例如: libhello.a

共享庫(動態庫)的代碼是在可執行程序運行時才載入內存的,在編譯過程中僅簡單的引用,因此代碼體積較小。

動態通常用.so為后綴, 例如:libhello.so

共享庫(動態庫)的好處是,不同的應用程序如果調用相同的庫,那么在內存里只需要有一份該共享庫的實例。

為了在同一系統中使用不同版本的庫,可以在庫文件名后加上版本號為后綴,例如: libhello.so.1.0,由於程序連接默認以.so為文件后綴名。所以為了使用這些庫,通常使用建立符號連接的方式。

ln -s libhello.so.1.0 libhello.so.1 ln -s libhello.so.1 libhello.so

1.3、靜態庫,動態庫文件在linux下是如何生成的:

以下面的代碼為例,生成上面用到的hello庫:

/* hello.c */ 

#include "hello.h" 

void sayhello() 

printf("hello,world "); 

}

首先用gcc編繹該文件,在編繹時可以使用任何合法的編繹參數,例如-g加入調試代碼等:

$gcc -c hello.c -o hello.o

1、生成靜態庫 生成靜態庫使用ar工具,其實ar是archive的意思

$ar cqs libhello.a hello.o

2、生成動態庫 用gcc來完成,由於可能存在多個版本,因此通常指定版本號:

$gcc -shared -o libhello.so.1.0 hello.o

 

1.4、庫文件是如何命名的,有沒有什么規范:

在 linux 下,庫文件一般放在/usr/lib和/lib下,

靜態庫的名字一般為libxxxx.a,其中 xxxx 是該lib的名稱;

動態庫的名字一般為libxxxx.so.major.minor,xxxx 是該lib的名稱,major是主版本號,minor是副版本號

 

1.5、可執行程序在執行的時候如何定位共享庫(動態庫)文件 :

當系統加載可執行代碼(即庫文件)的時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑,此時就需要系統動態載入器(dynamic linker/loader)

對於 elf 格式的可執行程序,是由 ld-linux.so* 來完成的,它先后搜索 elf 文件的 DT_RPATH 段—環境變量LD_LIBRARY_PATH—/etc/ld.so.cache 文件列表— /lib/,/usr/lib 目錄找到庫文件后將其載入內存

如: export LD_LIBRARY_PATH='pwd'

將當前文件目錄添加為共享目錄

 

1.6、使用ldd工具,查看可執行程序依賴那些動態庫或着動態庫依賴於那些動態庫:

ldd 命令可以查看一個可執行程序依賴的共享庫,

例如 # ldd /bin/lnlibc.so.6

=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2

=> /lib/ld- linux.so.2 (0×40000000)

可以看到 ln 命令依賴於 libc 庫和 ld-linux 庫

使用以下的命令查看arm平台的依賴關系

 

注意:arm-linux-readelf -d busybox | grep Shared grep后面可以不要,注意大小Shared第一個字母大寫

   

 

1.7、使用nm工具,查看靜態庫和動態庫中有那些函數名(T類表示函數是當前庫中定義的,U類表示函數是被調用的,在其它庫中定義的,W類是當前庫中定義,被其它庫中的函數覆蓋)。:

有時候可能需要查看一個庫中到底有哪些函數,nm工具可以打印出庫中的涉及到的所有符號,這里的庫既可以是靜態的也可以是動態的。

nm列出的符號有很多, 常見的有三種::

一種是在庫中被調用,但並沒有在庫中定義(表明需要其他庫支持),用U表示;

一種是在庫中定義的函數,用T表示,這是最常見的;

另外一種是所 謂的"弱態"符號,它們雖然在庫中被定義,但是可能被其他庫中的同名符號覆蓋,用W表示。

例如,假設開發者希望知道上文提到的hello庫中是否引用了 printf():

$nm libhello.so | grep printf

發現printf是U類符號,說明printf被引用,但是並沒有在庫中定義。

由此可以推斷,要正常使用hello庫,必須有其它庫支持,使用ldd工具查看hello依賴於哪些庫:

$ldd hello libc.so.6=>/lib/libc.so.6(0x400la000) /lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)

從上面的結果可以繼續查看printf最終在哪里被定義,有興趣可以Go on

 

1.8、使用ar工具,可以生成靜態庫,同時可以查看靜態庫中包含那些.o文件,即有那些源文件構成。

可以使用 ar -t libname.a 來查看一個靜態庫由那些.o文件構成。

可以使用 ar q libname.a xxx1.o xxx2.o xxx3.o ... xxxn.o 生成靜態庫

 

 

Linux下進行程序設計時,關於庫的使用:

 

一、gcc/g++命令中關於庫的參數:

-shared: 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當於一個可執行文件

-fPIC:表示編譯為位置獨立(地址無關)的代碼,不用此選項的話,編譯后的代碼是位置相關的,所以動態載入時,是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。

-L:指定鏈接庫的路徑,-L. 表示要連接的庫在當前目錄中

-ltest:指定鏈接庫的名稱為test,編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,后面加上.so來確定庫的名稱

LD_LIBRARY_PATH:這個環境變量指示動態連接器可以裝載動態庫的路徑。

當然如果有root權限的話,可以修改/etc/ld.so.conf文件,然后調用 /sbin/ldconfig來達到同樣的目的,

不過如果沒有root權限,那么只能采用修改LD_LIBRARY_PATH環境變量的方法了。

調用動態庫的時候,有幾個問題會經常碰到:

1、有時,明明已經將庫的頭文件所在目錄 通過 "-I" include進來了,庫所在文件通過 "-L"參數引導,並指定了"-l"的庫名,但通過ldd命令察看時,就是死活找不到你指定鏈接的so文件,這時你要作的就是通過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態庫的目錄。通常這樣做就可以解決庫無法鏈接的問題了。

 

二、靜態庫鏈接時搜索路徑的順序:

1. ld會去找gcc/g++命令中的參數-L;

2. 再找gcc的環境變量LIBRARY_PATH,它指定程序靜態鏈接庫文件搜索路徑;

export LIBRARY_PATH=$LIBRARY_PATH:data/home/billchen/lib

3. 再找默認庫目錄 /lib /usr/lib /usr/local/lib,這是當初compile gcc時寫在程序內的。

 

三、動態鏈接時、執行時搜索路徑順序:

1. 編譯目標代碼時指定的動態庫搜索路徑;

2. 環境變量LD_LIBRARY_PATH指定動態庫搜索路徑,它指定程序動態鏈接庫文件搜索路徑;

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:data/home/billchen/lib

3. 配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;

4. 默認的動態庫搜索路徑/lib;

5. 默認的動態庫搜索路徑/usr/lib。

 

四、靜態庫和動態鏈接庫同時存在的問題:

當一個庫同時存在靜態庫和動態庫時,比如libmysqlclient.a和libmysqlclient.so同時存在時:

在Linux下,動態庫和靜態庫同事存在時,gcc/g++的鏈接程序,默認鏈接的動態庫。

可以使用下面的方法,給連接器傳遞參數,看是否鏈接動態庫還是靜態庫。

-WI,-Bstatic -llibname //指定讓gcc/g++鏈接靜態庫

使用:

gcc/g++ test.c -o test -WI,-Bstatic -llibname

-WI,-Bdynamic -llibname //指定讓gcc/g++鏈接動態庫

使用:

gcc/g++ test.c -o test -WI,-Bdynamic -llibname

如果要完全靜態加在,使用-static參數,即將所有的庫以靜態的方式鏈入可執行程序,這樣生成的可執行程序,不再依賴任何庫,同事出現的問題是,這樣編譯出來的程序非常大,占用空間。

 

五、有關環境變量:

LIBRARY_PATH環境變量:指定程序靜態鏈接庫文件搜索路徑

LD_LIBRARY_PATH環境變量:指定程序動態鏈接庫文件搜索路徑

 

六、動態庫升級問題:

在動態鏈接庫升級時,

不能使用cp newlib.so oldlib.so,這樣有可能會使程序core掉;

而應該使用:

rm oldlib.so 然后 cp newlib.so oldlib.so

或者

mv oldlib.so oldlib.so_bak 然后 cp newlib.so oldlib.so

為什么不能用cp newlib.so oldlib.so ?

在替換so文件時,如果在不停程序的情況下,直接用 cp new.so old.so 的方式替換程序使用的動態庫文件會導致正在運行中的程序崩潰。

 

解決方法:

解決的辦法是采用"rm+cp" 或"mv+cp" 來替代直接"cp" 的操作方法。

linux系統的動態庫有兩種使用方法:運行時動態鏈接庫,動態加載庫並在程序控制之下使用。

 

1、為什么在不停程序的情況下,直接用 cp 命令替換程序使用的 so 文件,會使程序崩潰?

很多同學在工作中遇到過這樣一個問題,在替換 so 文件時,如果在不停程序的情況下,直接用cp new.so old.so的方式替換程序使用的動態庫文件會導致正在運行中的程序崩潰,退出。

這與 cp 命令的實現有關,cp 並不改變目標文件的 inode,cp 的目標文件會繼承被覆蓋文件的屬性而非源文件。實際上它是這樣實現的:

strace cp libnew.so libold.so 2>&1 |grep open.*lib.*.so

open("libnew.so", O_RDONLY|O_LARGEFILE) = 3

open("libold.so", O_WRONLY|O_TRUNC|O_LARGEFILE) = 4

在 cp 使用"O_WRONLY|O_TRUNC" 打開目標文件時,原 so 文件的鏡像被意外的破壞了。這樣動態鏈接器 ld.so 不能訪問到 so 文件中的函數入口。從而導致 Segmentation fault,程序崩潰。ld.so 加載 so 文件及"再定位"的機制比較復雜。

 

2、怎樣在不停止程序的情況下替換so文件,並且保證程序不會崩潰?

答案是采用"rm+cp" 或"mv+cp" 來替代直接"cp" 的操作方法。

在用新的so文件 libnew.so 替換舊的so文件 libold.so 時,如果采用如下方法:

rm libold.so //如果內核正在使用libold.so,那么inode節點不會立刻別刪除掉。

cp libnew.so libold.so

采用這種方法,目標文件 libold.so 的 inode 其實已經改變了,原來的 libold.so 文件雖然不能用 "ls"查看到,但其 inode 並沒有被真正刪除,直到內核釋放對它的引用。

(即: rm libold.so,此時,如果ld.so正在加在libold.so,內核就在引用libold.so的inode節點,rm libold.so的inode並沒有被真正刪除,當ld.so對libold.so的引用結束,inode才會真正刪除。這樣程序就不會崩潰,因為它還在使用舊的libold.so,當下次再使用libold.so時,已經被替換,就會使用新的libold.so)

同理,mv只是改變了文件名,其 inode 不變,新文件使用了新的 inode。這樣動態鏈接器 ld.so 仍然使用原來文件的 inode 訪問舊的 so 文件。因而程序依然能正常運行。

(即: mv libold.so ***后,如果程序使用動態庫,還是使用舊的inode節點,當下次再使用libold.so時,就會使用新的libold.so)

 

到這里,為什么直接使用"cp new_exec_file old_exec_file"這樣的命令時,系統會禁止這樣的操作,並且給出這樣的提示"cp: cannot create regular file `old': Text file busy"。這時,我們采用的辦法仍然是用"rm+cp"或者"mv+cp"來替代直接"cp",這跟以上提到的so文件的替換有同樣的道理。

 

但是,為什么系統會阻止 cp 覆蓋可執行程序,而不阻止覆蓋 so 文件呢?

這是因為 Linux 有個 Demand Paging 機制,所謂"Demand Paging",簡單的說,就是系統為了節約物理內存開銷,並不會程序運行時就將所有頁(page)都加載到內存中,而只有在系統有訪問需求時才將其加載。"Demand Paging"要求正在運行中的程序鏡像(注意,並非文件本身)不被意外修改,因此內核在啟動程序后會鎖定這個程序鏡像的 inode。

對於 so 文件,它是靠 ld.so 加載的,而ld.so畢竟也是用戶態程序,沒有權利去鎖定inode,也不應與內核的文件系統底層實現耦合。

 

 

gcc指定頭文件路徑及動態鏈接庫路徑

   

本文詳細介紹了linux 下gcc頭文件指定方法,以及搜索路徑順序的問題。另外,還總結了,gcc動態鏈接的方法以及路徑指定,同樣也討論了搜索路徑的順序問題。本文包含了很多的例子,具有很強的操作性,希望讀者自己去走一遍。
一.#include <>與#include ""

#include <>直接到系統指定的某些目錄中去找某些頭文件。
#include ""先到源文件所在文件夾去找,然后再到系統指定的某些目錄中去找某些頭文件。

二.gcc指定頭文件的三種情況:

1.會在默認情況下指定到/usr/include文件夾(更深層次的是一個相對路徑,gcc可執行程序的路徑是/usr/bin/gcc,那么它在實際工作時指定頭文件頭徑是一種相對路徑方法,換算成絕對路徑就是加上/usr/include,如#include 就是包含/usr/include/stdio.h)

2.GCC還使用了-I指定路徑的方式,即
gcc -I 頭文件所在文件夾(絕對路徑或相對路徑均可) 源文件
舉一個例子:
設當前路徑為/root/test,其結構如下:
include_test.c
include/include_test.h
有兩種方法訪問到include_test.h。
1. include_test.c中#include "include/include_test.h"然后gcc include_test.c即可
2. include_test.c中#include 或者#include 然后gcc –I include include_test.c也可

3. 參數:-nostdinc使編譯器不再系統缺省的頭文件目錄里面找頭文件,一般和-I聯合使用,明確限定頭文件的位置。

在編譯驅動模塊時,由於非凡的需求必須強制GCC不搜索系統默認路徑,也就是不搜索/usr/include要用參數-nostdinc,還要自己用-I參數來指定內核頭文件路徑,這個時候必須在Makefile中指定。

頭文件搜索順序:
1.由參數-I指定的路徑(指定路徑有多個路徑時,按指定路徑的順序搜索)

2.然后找gcc的環境變量 C_INCLUDE_PATH, CPLUS_INCLUDE_PATH, OBJC_INCLUDE_PATH

3.再找內定目錄
/usr/include
/usr/local/include
/usr/lib/gcc-lib/i386-linux/2.95.2/include
/usr/lib/gcc-lib/i386-linux/2.95.2/../../../../include/g++-3
/usr/lib/gcc-lib/i386-linux/2.95.2/../../../../i386-linux/include

庫文件,但是如果裝gcc的時候,是有給定的prefix的話,那么就是
/usr/include
prefix/include
prefix/xxx-xxx-xxx-gnulibc/include
prefix/lib/gcc-lib/xxxx-xxx-xxx-gnulibc/2.8.1/include

三.Linux指定動態庫路徑

眾所周知,Linux動態庫的默認搜索路徑是/lib和/usr/lib。動態庫被創建后,一般都復制到這兩個目錄中。當程序執行時需要某動態庫, 並且該動態庫還未加載到內存中,則系統會自動到這兩個默認搜索路徑中去查找相應的動態庫文件,然后加載該文件到內存中,這樣程序就可以使用該動態庫中的函 數,以及該動態庫的其它資源了。在Linux 中,動態庫的搜索路徑除了默認的搜索路徑外,還可以通過以下三種方法來指定。

1.在配置文件/etc/ld.so.conf中指定動態庫搜索路徑。
可以通過編輯配置文件/etc/ld.so.conf來指定動態庫的搜索路徑,該文件中每行為一個動態庫搜索路徑。每次編輯完該文件后,都必須運行命令ldconfig使修改后的配置生效。

舉一個例子:
所有源文件:
源文件1: lib_test.c
#include 
void prt()
{
printf("You found me!!!/n");
}
源文件2: main.c
void prt();
int main()
{
prt();
return 0;
}
操作過程:
我們通過以下命令用源程序lib_test.c來創建動態庫 lib_test.so。
# gcc –o lib_test.o -c lib_test.c
# gcc -shared -fPIC -o lib_test.so lib_test.o
#
或者直接一條指令:
#gcc –shared –fPIC –o lib_test.so lib_test.c
#

注意:
-fPIC參數聲明鏈接庫的代碼段是可以共享的,
-shared參數聲明編譯為共享庫。請注意這次我們編譯的共享庫的名字叫做
lib_test.so,這也是Linux共享庫的一個命名的慣例了:后綴使用so,而名稱使用libxxxx格式。

接着通過以下命令編譯main.c,生成目標程序main.out。
# gcc -o main.out -L. –l_test main.c
#

請注意為什么是-l_test?

然后把庫文件移動到目錄/root/lib中。
# mkdir /root/lib
# mv lib_test.so /root/lib/ lib_test.so
#

最后編輯配置文件/etc/ld.so.conf,在該文件中追加一行/root/lib。

運行程序main.out:
# ./main.out
./main.out: error while loading shared libraries: lib_test.so: cannot open shared object file: No such file or directory
#
出錯了,系統未找到動態庫lib_test.so。找找原因,原來在編輯完配置文件/etc/ld.so.conf后,沒有運行命令ldconfig,所以剛才的修改還未生效。我們運行ldconfig后再試試。

# ldconfig
# ./main.out
You found me!!!
#
程序main.out運行成功,並且打印出正確結果。

2.通過環境變量LD_LIBRARY_PATH指定動態庫搜索路徑。
通過設定環境變量LD_LIBRARY_PATH也可以指定動態庫搜索路徑。當通過該環境變量指定多個動態庫搜索路徑時,路徑之間用冒號":"分隔。下面通過例2來說明本方法。

舉一個例子:
這次我們把上面得到的文件lib_test.so移動到另一個地方去,如/root下面,然后設置環境變量LD_LIBRARY_PATH找到lib_test.so。設置環境變量方法如下:
# export LD_LIBRARY_PATH=/root
#
然后運行:
#./main.out
You found me!!!
#
注意:設置環境變量LD_LIBRARY_PATH=/root是不行的,非得export才行。

3.在編譯目標代碼時指定該程序的動態庫搜索路徑。
還可以在編譯目標代碼時指定程序的動態庫搜索路徑。-Wl,表示后面的參數將傳給link程序ld(因為gcc可能會自動調用ld)。這里通過gcc的參數"-Wl,-rpath,"指定
舉一個例子:
這次我們還把上面得到的文件lib_test.so移動到另一個地方去,如/root/test/lib下面,
因為我們需要在編譯目標代碼時指定可執行文件的動態庫搜索路徑,所以需要用gcc命令重新編譯源程序main.c(見程序2)來生成可執行文件main.out。
# gcc -o main.out -L. –l_test -Wl,-rpath,/root/test/lib main.c
#

運行結果:
# ./main.out
You found me!!!
#

程序./main.out運行成功,輸出的結果正是main.c中的函數prt的運行結果。因此程序main.out搜索到的動態庫是/root/test/lib/lib_test.so。

關於-Wl,rpath的使用方法我再舉一個例子,應該不難從中看出指定多個路徑的方法:
gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/lib test.c

以上介紹了三種指定動態庫搜索路徑的方法,加上默認的動態庫搜索路徑/lib和/usr/lib,共五種動態庫的搜索路徑,那么它們搜索的先后順序是什么呢?讀者可以用下面的方法來試驗一下:
(1) 用前面介紹的方法生成5個lib_test.so放在5個不同的文件夾下面,要求每一個lib_test.so都唯一對應一個搜索路徑,並注意main.out程序輸出的不同。
(2) 運行main.out,即可看出他是那個搜索路徑下的,然后刪除這個路徑下的lib_test.so,然后再運行。依此類推操作,即可推出搜索順序。

可以得出動態庫的搜索路徑搜索的先后順序是:

1.編譯目標代碼時指定的動態庫搜索路徑;

2.環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑;

3.配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;

4.默認的動態庫搜索路徑/lib;

5.默認的動態庫搜索路徑/usr/lib。

在上述1、2、3指定動態庫搜索路徑時,都可指定多個動態庫搜索路徑,其搜索的先后順序是按指定路徑的先后順序搜索的。有興趣的讀者自己驗證。

PS:此文網上搜得,原始出處已經無法訪問,故無法給出原文鏈接。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM