久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

用 Cython 包裝靜態(tài)庫和動態(tài)庫

 古明地覺O_o 2022-12-08 發(fā)布于北京

楔子


引入 C 源文件我們已經(jīng)知道該怎么做了,,但如果引入的不是源文件,而是已經(jīng)存在的靜態(tài)庫或者動態(tài)庫該怎么辦呢,?C 語言發(fā)展到現(xiàn)在已經(jīng)擁有非常多成熟的庫了,,我們可以直接拿來用,這些庫可以是靜態(tài)庫,、也可以是動態(tài)庫,,這個時候 Cython 要如何和它們勾搭在一起呢?

要搞清楚這一點,,我們需要先了解靜態(tài)庫和動態(tài)庫,。并且一個 C 源文件可以被編譯成一個可執(zhí)行文件,那么我們還要先搞清楚在將 C 源文件編譯成可執(zhí)行文件的時候,,靜態(tài)庫和動態(tài)庫是如何起作用的,。所以暫時不提 Cython,先來說一下靜態(tài)庫和動態(tài)庫,。


C 的編譯過程


假設(shè)有一個 C 源文件 main.c,,只需要通過 gcc main.c -o main.exe 即可編譯生成可執(zhí)行文件( 如果只寫 gcc main.c,那么 Windows 上會默認(rèn)生成 a.exe,、Linux 上會默認(rèn)生成 a.out ),,但是這一步可以拆解成如下步驟:

  • 預(yù)處理:gcc -E main.c -o main.i,根據(jù) C 源文件得到預(yù)處理之后的文件,,這一步只是對 main.c 進(jìn)行了預(yù)處理:比如宏定義展開,、頭文件展開、條件編譯等等,,同時將代碼中的注釋刪除,,注意:這里并不會檢查語法;

  • 編譯:gcc -S main.i -o main.s,,將預(yù)處理后的文件進(jìn)行編譯,、生成匯編文件,這一步會進(jìn)行語法檢測,、變量的內(nèi)存分配等等,;

  • 匯編:gcc -c main.s -o main.o,,根據(jù)匯編文件生成目標(biāo)文件,當(dāng)然我們也可以通過 gcc -c main.c -o main.o 直接通過 C 源文件得到目標(biāo)文件;

  • 鏈接:gcc main.o -o main.exe,程序是需要依賴各種庫的,,可以是靜態(tài)庫也可以是動態(tài)庫,因此需要將目標(biāo)文件和其引用的庫鏈接在一起,,最終才能構(gòu)成可執(zhí)行的二進(jìn)制文件;

從 C 源文件到可執(zhí)行文件會經(jīng)歷以上幾步,,不過我們一般都會將這幾步組合起來,,整體稱之為編譯。比如我們常說,,將某個源文件編譯成可執(zhí)行文件,。

而靜態(tài)庫和動態(tài)庫是在鏈接這一步發(fā)生的,比如我們在 main.c 中引入了 stdio.h 這個頭文件,,里面的函數(shù)( 比如 printf )不是我們自己實現(xiàn)的,,所以在編譯成可執(zhí)行文件的時候還需要將其鏈接進(jìn)去。

所以靜態(tài)庫和動態(tài)庫的作用都是一樣的,,都是和匯編生成的目標(biāo)文件( .o 文件)攪和在一起,,共同組合生成可執(zhí)行文件。那么它們之間有什么區(qū)別呢,?下面就來介紹一下,。

在 Windows 上靜態(tài)庫是以 .lib 結(jié)尾、動態(tài)庫是以 .dll 結(jié)尾,;在 Linux 上靜態(tài)庫則以 .a 結(jié)尾,、動態(tài)庫以 .so 結(jié)尾。而動態(tài)庫的生成,,兩個系統(tǒng)沒太大區(qū)別,,但生成靜態(tài)庫,Windows 系統(tǒng)要麻煩一些,。考慮到生產(chǎn)上大部分都是 Linux 系統(tǒng),,并且動態(tài)庫的使用頻率更高,,所以這里只以 Linux 系統(tǒng)為例。


靜態(tài)庫


一個靜態(tài)庫可以簡單看成是一組目標(biāo)文件的集合,,也就是多個目標(biāo)文件經(jīng)過壓縮打包之后形成的文件,。而靜態(tài)庫最大的特點就是一旦鏈接成功,那么就可以刪掉了,,因為它已經(jīng)鏈接到生成的可執(zhí)行文件中了,。所以從側(cè)面也可以看出使用靜態(tài)庫會比較浪費空間和資源,,說白了就是生成的可執(zhí)行文件會比較大,因為里面還包含了靜態(tài)庫,。

而在 Linux 中靜態(tài)庫是有命名規(guī)范的,,必須以 lib 開頭、.a 結(jié)尾,。假設(shè)你想生成一個名為 hello 的靜態(tài)庫,,那么它的文件名就必須是 libhello.a,這是一個規(guī)范,。

在 Linux 中生成靜態(tài)庫的方式如下:

  • 先得到目標(biāo)文件:gcc -c 源文件 -o 目標(biāo)文件,,比如 gcc -c test.c -o test.o。這里要指定 -c 參數(shù),,否則生成的就是可執(zhí)行文件,;

  • 通過 ar 工具基于目標(biāo)文件構(gòu)建靜態(tài)庫:ar rcs libtest.a test.o,此時就得到了靜態(tài)庫 ,。但我們說在 Linux 中靜態(tài)庫是有格式要求的,,必須以 lib 開頭、.a 結(jié)尾,,所以是 libtest.a,;

我們來做一個測試,首先是編寫一個 C 文件 test.c,,里面內(nèi)容如下:

// 計算 start 到 end 之間所有整數(shù)的和
int sum(int start, int end) {
 int res = 0;
 for (; start <= end; start++) {
  res += start;
 }
 return res;
}

執(zhí)行命令:

[root@satori ~]# gcc -c test.c -o test.o
[root@satori ~]# ar rcs libtest.a test.o
[root@satori ~]# ls | grep test.
libtest.a
test.c
test.o

此時 libtest.a 就成功生成了,,然后我們再來編寫一個 main.c 直接調(diào)用:

#include <stdio.h>

int sum(intint);

int main() {
 printf("%d\n", sum(1100));
}

我們看到這里只是聲明了 sum,但是具體實現(xiàn)則沒有編寫,,因為它已經(jīng)在 libtest.a 中實現(xiàn)了,,我們只需要在使用 gcc 編譯的時候指定即可。

[root@satori ~]# gcc main.c -L . -l test -o main
[root@satori ~]# ./main 
5050

可以看到執(zhí)行成功了,,打印結(jié)果也是正確的,,但這里需要解釋一下里面的參數(shù)。首先 gcc main.c 無需解釋,,表示對 main.c 文件進(jìn)行編譯,。而結(jié)尾的 -o main 也無需解釋,表示生成的可執(zhí)行文件的文件名叫 main,。

中間的 -L . 表示追加庫文件的搜索路徑,,因為 gcc 在尋找?guī)斓臅r候,只會從標(biāo)準(zhǔn)位置進(jìn)行查找,。而當(dāng)前所在目錄明顯不屬于標(biāo)準(zhǔn)位置,,因此需要通過 -L 參數(shù)將寫好的靜態(tài)庫所在的路徑追加進(jìn)去,libtest.a 位于當(dāng)前目錄,,所以是 -L .,。

然后是 -l test,,首先 -l 表示要鏈接的靜態(tài)庫(也可以是動態(tài)庫,后面會說,,目前就只看靜態(tài)庫即可),,因為當(dāng)前的靜態(tài)庫名字叫做 libtest.a,那么把開頭的 lib 和結(jié)尾的 .a 去掉再和 -l 進(jìn)行組合即可,。

如果我們將靜態(tài)庫改名為 libxxx.a 的話,,那么就需要指定 -l xxx;同理,,要是我們指定的是 -l foo,,那么在鏈接的時候會自動尋找 libfoo.a。所以從這里也能看出,,在 Linux 中創(chuàng)建靜態(tài)庫的時候一定要遵循命名規(guī)范,,以 lib 開頭、.a 結(jié)尾,,否則鏈接是會失敗的,。當(dāng)然追加搜索路徑、鏈接靜態(tài)庫的數(shù)量是沒有限制的,,比如除了 libtest.a 之外還想鏈接 libfoo.a,,那么就指定 -l test -l foo 即可。

注:-l test 也可以寫成 -ltest,,即中間沒有空格,,這種寫法更為常見。但這里我為了清晰,,之間加了一個空格,,對于編譯而言是沒有影響的。

同理還有頭文件,,雖然這里沒有涉及到,,但還是需要說一說,因為導(dǎo)入頭文件更常見,。如果想導(dǎo)入的頭文件不在搜索路徑中,,我們在編譯的時候也是需要指定的。假設(shè) main.c 還引入了一個自定義的頭文件,,其位于當(dāng)前目錄下的 header 目錄里,,那么編譯的時候為了讓編譯器能夠找得到,我們需要通過 -I 來追加相應(yīng)的頭文件的搜索路徑:

注:追加頭文件的搜索路徑使用的是大寫的字母 i,,當(dāng)前文章的字體讓人很容易和小寫的字母 l 搞混,因此這里專門畫張圖,,并用一個區(qū)分度比較明顯的字體,。

對于頭文件搜索路徑,、庫文件搜索路徑、引入的靜態(tài)庫的數(shù)量,,都是沒有限制的,,可以指定任意個:-I、-L,、-l,。


動態(tài)庫


通過靜態(tài)庫,我們算是實現(xiàn)了代碼復(fù)用,,而且靜態(tài)庫的使用也比較方便,。那么問題來了,既然有了靜態(tài)庫,,為什么我們還要使用動態(tài)庫呢,?

首先是資源浪費,假設(shè)有一個靜態(tài)庫大小是 1M,,而它被 1000 個可執(zhí)行程序依賴,,那么這個靜態(tài)庫就相當(dāng)于被拷貝了 1000 份,因為靜態(tài)庫是需要被鏈接到可執(zhí)行文件當(dāng)中的,。然后是靜態(tài)庫的更新和部署會帶來麻煩,,假設(shè)靜態(tài)庫更新了,那么所有使用它的應(yīng)用程序都必須重新編譯,、然后發(fā)布給用戶,。即使只改動了一小部分,也要重新編譯生成可執(zhí)行文件,,因為要重新鏈接靜態(tài)庫,。

而動態(tài)庫則不同,動態(tài)庫在鏈接的時候不會將自身的內(nèi)容包含在可執(zhí)行文件中,,而是在程序運行的時候動態(tài)加載,。相當(dāng)于只是告訴可執(zhí)行文件:"你的內(nèi)部會依賴我,但由于我是動態(tài)庫,,因此我不會像靜態(tài)庫一樣被包含在你的內(nèi)部,,而是需要你運行的時候再去查找、加載",。所以多個可執(zhí)行文件可以共享同一個動態(tài)庫,,因此也就避免了空間浪費的問題,并且動態(tài)庫是程序運行時動態(tài)加載的,,我們對動態(tài)庫做一些更新之后可以不用重新編譯生成可執(zhí)行文件,。

有優(yōu)點就自然有缺點,相信都看出來了,,既然是動態(tài)加載,,就意味著即使在編譯成可執(zhí)行文件之后,,依賴的動態(tài)庫也不能丟。和靜態(tài)庫不同,,靜態(tài)庫和最終的可執(zhí)行文件是完全獨立的,,因為在編譯成可執(zhí)行文件的時候靜態(tài)庫的內(nèi)容就已經(jīng)被鏈接在里面了;而動態(tài)庫是要被動態(tài)加載的,,因此它是被可執(zhí)行文件所依賴的,,所以不能丟。

然后我們來生成一下動態(tài)庫,,生成動態(tài)庫要比生成靜態(tài)庫簡單許多:gcc 源文件 -shared -o 動態(tài)庫文件,,還是以之前的 test.c 為例:

gcc test.c -shared -o libtest.so

在 Linux 中,動態(tài)庫也具有相同的命名規(guī)范,,只不過它是以 .so 結(jié)尾,。但是你真的不按照這個格式命名也是可以的,只不過在使用 gcc 的時候會找不到相應(yīng)的庫,。因為編譯的時候會按照指定格式去查找?guī)煳募?,所以我們在生成庫文件的時候也要按照相同的格式起名字。

Windows 上生成動態(tài)庫的方式與 Linux 相同,,只需把動態(tài)庫的后綴 .so 換成 .dll 即可,。

然后使用 gcc 對之前的 test.c 源文件進(jìn)行編譯:

[root@satori ~]# gcc test.c -shared -o libtest.so
[root@satori ~]# ls libtest.so 
libtest.so
[root@satori ~]# gcc main.c -L . -l test -o main1

我們看到可執(zhí)行文件成功生成了,這里起名為 main1,。引入動態(tài)庫和引入靜態(tài)庫的方式是一樣的,,因為 -l 既可以鏈接靜態(tài)庫、也可以鏈接動態(tài)庫(要是靜態(tài)庫和動態(tài)庫都有怎么辦,?別急,,后面說,目前只考慮動態(tài)庫),。

[root@satori ~]# ./main1
./main1: error while loading shared libraries: libtest.so: 
cannot open shared object file: No such file or directory

但是問題來了,,雖然編譯成功了,但是執(zhí)行的時候卻報錯了,,說找不到這個 libtest.so,,盡管它就在當(dāng)前可執(zhí)行文件所在的目錄下。

原因是可執(zhí)行文件在查找動態(tài)庫的時候也是會從指定的位置進(jìn)行查找的,,而我們當(dāng)前目錄不在搜索范圍內(nèi),。這時候可能有人會好奇,我們不是在編譯的時候通過 -L 參數(shù)將當(dāng)前路徑追加進(jìn)去了嗎,?

答案是動態(tài)庫和靜態(tài)庫不同,,動態(tài)庫在鏈接的時候自身不會被包含在可執(zhí)行文件當(dāng)中,我們指定的 -L . -l test 相當(dāng)于只是在鏈接的時候告訴即將生成的可執(zhí)行文件:"在當(dāng)前目錄下有一個 libtest.so,它將來會是你的依賴,,你趕緊記錄一下",。我們可以通過 ldd 命令查看可執(zhí)行文件依賴的動態(tài)庫:

[root@satori ~]# ldd main1
 linux-vdso.so.1 =>  (0x00007ffe67379000)
 libtest.so => not found
 libc.so.6 => /lib64/libc.so.6 (0x00007f8d89bf9000)
 /lib64/ld-linux-x86-64.so.2 (0x00007f8d89fc6000)
[root@satori ~]# 

我們看到 libtest.so 已經(jīng)被記錄下來了,所以鏈接動態(tài)庫時只是記錄了動態(tài)庫的信息,,當(dāng)程序執(zhí)行時再去動態(tài)加載,因此它們會有一個指向,。但我們發(fā)現(xiàn) libtest.so 指向的是 not found,,這是因為動態(tài)庫 libtest.so 不在動態(tài)庫查找路徑中,所以會指向 not found,。

因此我們還需要將當(dāng)前目錄加入到動態(tài)庫查找路徑中,,vim /etc/ld.so.conf,將當(dāng)前目錄( 我這里是 /root )寫在里面,?;蛘咧苯?echo "/root" >> /etc/ld.so.conf,然后執(zhí)行 /sbin/ldconfig 使得修改生效,。

最后再來重新執(zhí)行一下 main1,,看看結(jié)果如何:

[root@satori ~]# echo "/root" >> /etc/ld.so.conf
[root@satori ~]# /sbin/ldconfig
[root@satori ~]# 
[root@satori ~]# ./main1
5050

可以看到此時成功執(zhí)行了,因此使用動態(tài)庫實際上會比靜態(tài)庫要麻煩一些,,因為靜態(tài)庫在編譯的時候就通過 -L 和 -l 參數(shù)直接把自身鏈接到可執(zhí)行文件中了,。而動態(tài)庫則不是這樣,用大白話來說就是,,它在鏈接的時候并沒有把自身內(nèi)容加入到可執(zhí)行文件中,,而是告訴可執(zhí)行文件自己的信息、然后讓其執(zhí)行時再動態(tài)加載,。但是加載的時候,,為了讓可執(zhí)行文件能加載的到,我們還需要將動態(tài)庫的路徑配置到 /etc/ld.so.conf 中,。

[root@satori ~]# ldd main1
 linux-vdso.so.1 =>  (0x00007ffdbc324000)
 libtest.so => /root/libtest.so (0x00007fccdf5db000)
 libc.so.6 => /lib64/libc.so.6 (0x00007fccdf20e000)
 /lib64/ld-linux-x86-64.so.2 (0x00007fccdf7dd000

此時 libtest.so 就指向 /root/libtest.so 了,,而不是 not found。雖然麻煩,,但是它更省空間,,因為此時只需要有一份動態(tài)庫,如果可執(zhí)行文件想用的話直接動態(tài)加載即可,。除此之外,,我們說修改了動態(tài)庫之后,原來的可執(zhí)行文件不需要重新編譯:

int sum(int start, int end) {
 int res = 0;
 for (; start <= end; start++) {
  res += start;
 }
 return res + 1;
}

這里我們將返回的 res 加上一個 1,,然后重新生成動態(tài)庫:

[root@satori ~]# ./main1
5050
[root@satori ~]# gcc test.c -shared -o libtest.so
[root@satori ~]# ./main1
5051

結(jié)果變成了 5051,,并且我們沒有對可執(zhí)行文件做修改,因為動態(tài)庫的內(nèi)容不是嵌入在可執(zhí)行文件中的,而是可執(zhí)行文件執(zhí)行時動態(tài)加載的,。如果是靜態(tài)庫的話,,那么就需要重新編譯生成可執(zhí)行文件了。


同時指定靜態(tài)庫和動態(tài)庫


無論是靜態(tài)庫 libtest.a 還是動態(tài)庫 libtest.so,,在編譯時都是通過 -l test 進(jìn)行鏈接的,。那如果內(nèi)部同時存在 libtest.a 和 libtest.so,-l test 是會去鏈接 libtest.a 還是會去鏈接 libtest.so 呢,?這里可以猜一下,,首先我們上面所有的操作都是在 /root 目錄下進(jìn)行的,而且文件都沒有刪除,。

[root@satori ~]# ls | grep test.
libtest.a
libtest.so
test.c
test.o

相信結(jié)果很好猜,,我們介紹靜態(tài)庫的時候已經(jīng)生成了 libtest.a,然后 -l test 找到了 libtest.a 這沒有任何問題,。然后介紹動態(tài)庫的時候又生成了 libtest.so,,但是并沒有刪除當(dāng)前目錄下的 libtest.a,而 -l test 依然會去找 libtest.so,,說明了 -l 會優(yōu)先鏈接動態(tài)庫,。如果當(dāng)前目錄不存在相應(yīng)的動態(tài)庫,才會去尋找靜態(tài)庫,。

// 修改配置,,將當(dāng)前目錄給去掉
[root@satori ~]# vim /etc/ld.so.conf
[root@satori ~]# /sbin/ldconfig
[root@satori ~]# 
[root@satori ~]# gcc main.c -L . -l test -o main2
[root@satori ~]# ./main2
./main2: error while loading shared libraries: libtest.so: 
cannot open shared object file: No such file or directory

我們在 /etc/ld.so.conf 中將當(dāng)前目錄給刪掉了,所以編譯成可執(zhí)行文件之后再執(zhí)行就報錯了,,因為找不到 libtest.so,,證明默認(rèn)加載的確實是動態(tài)庫。

但是問題來了,,如果同時存在靜態(tài)庫和動態(tài)庫,,而我就想鏈接靜態(tài)庫的話該怎么做呢?

[root@satori ~]# gcc main.c -L . -static -l test -o main2
[root@satori ~]# ./main2
5050

通過 -static,,強(qiáng)制讓 gcc 鏈接靜態(tài)庫,。另外,如果執(zhí)行上面的命令報錯了,,提示 /usr/bin/ld: cannot find -lc,,那么執(zhí)行 yum install glibc-static 即可。因為高版本的 Linux 系統(tǒng)下安裝 glibc-devel, glibc 和 gcc-c++ 時不會安裝 libc.a,,而是只安裝libc.so,。所以當(dāng)使用 -static 時,libc.a 不能使用,,因此報錯 "找不到 lc",。

我們執(zhí)行一下:

[root@satori ~]# gcc main.c -L . -static -l test -o main2
[root@satori ~]# ./main2
5050
//刪除靜態(tài)庫,,依舊不影響執(zhí)行
//因為它已經(jīng)鏈接在可執(zhí)行文件中了

[root@satori ~]# rm -rf libtest.a
[root@satori ~]# ./main2
5050

這里再提一個問題:鏈接 libtest.a 生成的可執(zhí)行文件鏈接 libtest.so 生成的可執(zhí)行文件 哪一個占用的空間更大呢?好吧,,這個問題問的有點幼稚了,,很明顯前者更大,但是究竟大多少呢,?我們來比較一下吧,。

//鏈接 libtest.a
[root@satori ~]# size main2
   text data  bss  dec  hex filename
 770336 6228 8640  785204   bfb34 main2

[root@satori ~]# gcc main.c -L . -l test -o main2
//鏈接 libtest.so
[root@satori ~]# size main2
   text data  bss  dec  hex filename
   1479  564 4 2047  7ff main2

我們看到大小確實差的不是一點半點,再加上靜態(tài)庫是每一個可執(zhí)行文件內(nèi)部都要包含一份,,可想而知空間占用量是多么恐怖??,,所以才要有動態(tài)庫。因此靜態(tài)庫和動態(tài)庫各有優(yōu)缺點,,具體使用哪一種完全由你自己決定,就我個人而言更喜歡靜態(tài)庫,,因為生成可執(zhí)行文件之后就不用再管了(盡管對空間占用有點不負(fù)責(zé)任),。


Cython 和靜態(tài)庫結(jié)合


然后回到我們的主題,我們的重點是 Cython 和它們的結(jié)合,,當(dāng)然先對靜態(tài)庫和動態(tài)庫有一定的了解是必要的,。下面來看看 Cython 要如何引入靜態(tài)庫,這里我們編寫斐波那契數(shù)列,,然后生成靜態(tài)庫,。當(dāng)然為了追求刺激,這里采用 CGO 進(jìn)行編寫,。

// 文件名:go_fib.go
package main

import "C"
import "fmt"

//export go_fib
func go_fib(n 
C.intC.double {
 var i 
C.int 0
 var a, b 
C.double 0.01.0
 for ; i < n; i++ {
  a, b = a + b, a
 }
 fmt.Println("斐波那契計算完畢,,我是 Go 語言")
 return a
}

func main() {}

關(guān)于 CGO 這里不做過多介紹,你也可以使用 C 來編寫,,效果是一樣的,。然后我們來使用 go build 根據(jù) go 源文件生成靜態(tài)庫:

go build -buildmode=c-archive -o 靜態(tài)庫文件 go源文件

[root@satori ~]# go build -buildmode=c-archive -o libfib.a go_fib.go

然后還需要一個頭文件,這里定義為 go_fib.h:

double go_fib(int);

里面只需要放入一個函數(shù)聲明即可,,具體實現(xiàn)在 libfib.a 中,,然后編寫 Cython 源文件,文件名為 wrapper_gofib.pyx:

cdef extern from "go_fib.h":
 
double go_fib(int)

def fib_with_go(n):
 """
 調(diào)用 Go 編寫的斐波那契數(shù)列
 以靜態(tài)庫形式存在
 """

 return go_fib(n)

函數(shù)的具體實現(xiàn)邏輯是以源文件形式存在,、還是以靜態(tài)庫形式存在,,實際上并不關(guān)心。然后是編譯腳本 setup.py:

from distutils.core import setup, Extension
from Cython.Build import cythonize

# 我們不能在 sources 里面寫上 ["wrapper_gofib.pyx", "libfib.a"]
# 這是不合法的,,因為 sources 里面需要放入源文件
# 靜態(tài)庫和動態(tài)庫需要通過其它參數(shù)指定
ext = Extension(name="wrapper_gofib",
 sources=["wrapper_gofib.pyx"],
 # 相當(dāng)于 -L 參數(shù),,路徑可以指定多個
 library_dirs=["."],
 # 相當(dāng)于 -l 參數(shù),鏈接的庫可以指定多個
 # 注意:不能寫 libfib.a,,直接寫 fib 就行
 # 所以命名還是需要遵循規(guī)范,,要以 lib 開頭,、.a 結(jié)尾,
 libraries=["fib"],
 # 相當(dāng)于 -I 參數(shù)
 include_dirs=["."])

setup(ext_modules=cythonize(ext, language_level=3))

然后我們執(zhí)行 python3 setup.py build,,因為我現(xiàn)在使用的是 Linux,,所以需要輸入 python3,要是輸入 python 會指向 python2,。

執(zhí)行成功之后,,會生成一個 build 目錄,我們將里面的擴(kuò)展模塊移動到當(dāng)前目錄,,然后進(jìn)入交互式 Python 中導(dǎo)入它,,看看會有什么結(jié)果。

此時我們就將 Cython, Go, C, Python 給結(jié)合在一起了,,當(dāng)然你可以再加入 C 源文件,、或者 C 生成的庫文件,怎么樣,,是不是很好玩呢,。如果用 Go 寫了一個程序,那么就可以通過編譯成靜態(tài)庫的方式,,嵌入到 Cython 中,,然后再生成擴(kuò)展模塊交給 Python 調(diào)用。之前我本人也將 Python 和 Go 結(jié)合起來使用過,,只不過當(dāng)時是編譯成的動態(tài)庫,,然后通過 Python 的 ctypes 模塊調(diào)用的。

注意:無論是這里的靜態(tài)庫還是一會要說的動態(tài)庫,,我們舉的例子都會比較簡單,。但實際上我們使用 CGO 的話,內(nèi)部是可以編寫非常復(fù)雜的邏輯的,,因此我們需要注意 Go 和 C 之間內(nèi)存模型的差異,。因為 Python 和 Go 之間是無法直接結(jié)合的,但是它們都可以和 C 勾搭上,,所以需要 C 在這兩者之間搭一座橋,。

但是不同語言的內(nèi)存模型是不同的,因此當(dāng)跨語言操作同一塊內(nèi)存時需要格外小心,,比如 Go 的導(dǎo)出函數(shù)不能返回 Go 的指針等等,。所以里面的細(xì)節(jié)還是比較多的,當(dāng)然我們這里的主角是 Cython,,因此 Go 就不做過多介紹了,。

如果大家對 Python 和 Go 聯(lián)合編程感興趣,可以后臺私信我一下,,人多的話我就寫幾篇這樣的文章,。


Cython 和動態(tài)庫結(jié)合


然后是 Cython 和 動態(tài)庫結(jié)合,,我們還用剛才的 go_fib.go,而 Go 生成動態(tài)庫的命令如下:

go build -buildmode=c-shared -o 動態(tài)庫文件 go源文件

[root@satori ~]# go build -buildmode=c-shared -o libfib.so go_fib.go

動態(tài)庫的話我們只需要生成 libfib.so 即可,,然后其它地方不做任何改動,,直接執(zhí)行 python3 setup.py build 生成擴(kuò)展模塊,因為加載動態(tài)庫和加載靜態(tài)庫的邏輯是一樣的,。而我們的動態(tài)庫和剛才的靜態(tài)庫的名字也保持一致,,所以整體不需要做任何改動。

整體效果和 C 使用動態(tài)庫的表現(xiàn)是一致的,,仍然優(yōu)先尋找動態(tài)庫,,并且還要將動態(tài)庫所在路徑加入到 ld.so.conf 中。如果在動態(tài)庫和靜態(tài)庫同時存在的情況下,,想使用靜態(tài)庫的話,,那么可以這么做:

ext = Extension(
 name="wrapper_gofib",
 sources=["wrapper_gofib.pyx"],
 # 庫的搜索路徑
 library_dirs=["."],
 # libraries 參數(shù)可以同時指定動態(tài)庫和靜態(tài)庫
 # 但優(yōu)先尋找動態(tài)庫,動態(tài)庫不存在則找靜態(tài)庫
 # 如果就想鏈接靜態(tài)庫,,那么可以使用 extra_objects 參數(shù)
 # 該參數(shù)可以鏈接任意的對象,,我們只需要將路徑寫上去即可
 # 注意:此時是文件路徑,需要寫 libfib.a,,不能只寫 fib
 extra_objects=["./libfib.a"],
 include_dirs=["."])

當(dāng)然我們這里使用 Go 來生成庫文件實際上有點刻意了,因為主要是想展現(xiàn) Cython 的強(qiáng)大之處,。但其實使用 C 來生成庫文件也是一樣的,,因為我們使用 Go 本質(zhì)上也是將 Go 的代碼轉(zhuǎn)成 C 的代碼(因此叫 CGO),只不過用 Go 寫代碼肯定比用 C 寫代碼舒服,,畢竟 Go 是一門帶垃圾回收的高級語言,。

至于 Go 和 C 之間怎么轉(zhuǎn),那就不需要我們來操心了,,Go 編譯器會為我們處理好一切,。正如我們此刻學(xué)習(xí) Cython 一樣,用 Cython 寫擴(kuò)展肯定比用 C 寫擴(kuò)展舒服,,但 Cython 代碼同樣也是要轉(zhuǎn)成 C 的代碼,,至于怎么轉(zhuǎn),也不需要我們來操心,,Cython 編譯器會為我們處理好一切,。

以上就是 Cython 和庫文件(靜態(tài)庫、動態(tài)庫)之間的結(jié)合,,注:Cython 引入庫文件的相關(guān)操作都是基于 Linux,,至于 Windows 如何引入庫文件可以自己試一下。


小結(jié)


在該系列的最開始我們就說過,,其實可以將 Cython 當(dāng)成兩個身份來看待:如果是編譯成 C,,那么可以看作是 Cython 的 '陰',;如果作為膠水連接 C 或者 C++,那么可以看作是 Cython 的 '陽',。

但其實兩者之間并沒有嚴(yán)格區(qū)分,,一旦在 cdef extern from 塊中聲明了 C 函數(shù),就可以像 Cython 本身定義的常規(guī) cdef 函數(shù)一樣使用,。并且對外而言,,在使用 Python 調(diào)用時,沒有人知道里面的方法是我們自己辛辛苦苦編寫的,,還是調(diào)用了其它已經(jīng)存在的,。

這次我們介紹了 Cython 的一些接口特性和使用方法,感受一下它包裝 C 函數(shù)是多么的方便,。而 C 已經(jīng)存在很多年了,,擁有大量經(jīng)典的庫,通過 Cython 我們可以很輕松地調(diào)用它們,。

當(dāng)然不只是 C,,Cython 還可以調(diào)用同樣被廣泛使用的 C++ 中的庫函數(shù),但由于我本人不擅長 C++,,因此有興趣可以自己了解一下,。

E N D

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多