# 與外部 C 代碼連接
> 原文: [http://docs.cython.org/en/latest/src/userguide/external_C_code.html](http://docs.cython.org/en/latest/src/userguide/external_C_code.html)
Cython 的主要用途之一是包裝現有的 C 代碼庫。這是通過使用外部聲明來聲明要使用的庫中的 C 函數和變量來實現的。
您還可以使用公共聲明使 Cython 模塊中定義的 C 函數和變量可用于外部 C 代碼。預計對此的需求會減少,但您可能希望這樣做,例如,如果您[將 Python](http://www.freenet.org.nz/python/embeddingpyrex/) 作為腳本語言嵌入到另一個應用程序中。就像 Cython 模塊可以用作允許 Python 代碼調用 C 代碼的橋梁一樣,它也可以用于允許 C 代碼調用 Python 代碼。
## 外部聲明
默認情況下,在模塊級別聲明的 C 函數和變量對于模塊是本地的(即它們具有 C 靜態存儲類)。它們也可以聲明為 extern 以指定它們在別處定義,例如:
```py
cdef extern int spam_counter
cdef extern void order_spam(int tons)
```
### 引用 C 頭文件
當您在上面的示例中單獨使用 extern 定義時,Cython 在生成的 C 文件中包含它的聲明。如果聲明與其他 C 代碼將看到的聲明不完全匹配,則可能會導致問題。例如,如果要包裝現有的 C 庫,則生成的 C 代碼的編譯與庫的其余部分完全相同,這一點很重要。
為了實現這一點,你可以告訴 Cython 聲明是在 C 頭文件中找到的,如下所示:
```py
cdef extern from "spam.h":
int spam_counter
void order_spam(int tons)
```
`cdef extern` from 子句做了三件事:
1. 它指示 Cython 在生成的 C 代碼中為命名頭文件放置`#include`語句。
2. 它可以防止 Cython 為關聯塊中的聲明生成任何 C 代碼。
3. 它將塊中的所有聲明視為以`cdef extern`開頭。
重要的是要了解 Cython 本身不會讀取 C 頭文件,因此您仍需要提供您使用的任何聲明的 Cython 版本。但是,Cython 聲明并不總是必須完全匹配 C 聲明,在某些情況下它們不應該或不能。特別是:
1. 省略對 C 聲明的任何特定于平臺的擴展,例如`__declspec()`。
2. 如果頭文件聲明了一個大結構而你只想使用幾個成員,你只需要聲明你感興趣的成員。剩下的就不會造成任何傷害,因為 C 編譯器會使用完整的頭文件中的定義。
在某些情況下,您可能不需要任何 struct 的成員,在這種情況下,您可以將 pass 放在 struct 聲明的主體中,例如:
```py
cdef extern from "foo.h":
struct spam:
pass
```
注意
你只能在`cdef extern from`塊內執行此操作;其他地方的 struct 聲明必須是非空的。
3. 如果頭文件使用`typedef`等名稱來引用數字類型的平臺相關風格,則需要相應的 [`ctypedef`](language_basics.html#ctypedef) 語句,但不需要匹配完全鍵入,只使用正確的一般類型(int,float 等)。例如,:
```py
ctypedef int word
```
無論`word`的實際大小是什么(如果頭文件正確定義),它都可以正常工作。如果有的話,Python 類型的轉換也將用于此類型。
4. 如果頭文件使用宏來定義常量,則將它們轉換為普通的外部變量聲明。如果它們包含正常的`int`值,您也可以將它們聲明為 [`enum`](language_basics.html#enum) 。請注意,Cython 認為 [`enum`](language_basics.html#enum) 等同于`int`,因此不要對非 int 值執行此操作。
5. 如果頭文件使用宏定義函數,則將其聲明為普通函數,并使用適當的參數和結果類型。
6. 出于陳舊的原因,C 使用關鍵字`void`來聲明不帶參數的函數。在 Cython 中,就像在 Python 中一樣,只需聲明`foo()`等函數。
還有一些技巧和竅門:
* 如果你想要包含一個 C 頭,因為它是另一個頭所需要的,但又不想使用它的任何聲明,請在 extern-from 塊中放入 pass:
```py
cdef extern from "spam.h":
pass
```
* 如果要包含系統標題,請在引號內放置尖括號:
```py
cdef extern from "<sysheader.h>":
...
```
* 如果要包含一些外部聲明,但又不想指定頭文件(因為它已包含在您已包含的其他標頭中),則可以使用`*`代替頭文件名:
```py
cdef extern from *:
...
```
* 如果`cdef extern from "inc.h"`塊不為空且僅包含函數或變量聲明(并且沒有任何類型的聲明),Cython 將在 Cython 生成的所有聲明之后放置`#include "inc.h"`語句。這意味著包含的文件可以訪問由 Cython 聲明的變量,函數,結構......
### 用 C 實現函數
當您想從 Cython 模塊調用 C 代碼時,通常該代碼將位于您鏈接擴展的某個外部庫中。但是,您也可以直接編譯 C(或 C ++)代碼作為 Cython 模塊的一部分。在`.pyx`文件中,您可以輸入以下內容:
```py
cdef extern from "spam.c":
void order_spam(int tons)
```
Cython 將假設函數`order_spam()`在文件`spam.c`中定義。如果您還想從另一個模塊中導入此函數,則必須在`.pxd`文件中聲明(而不是 extern!):
```py
cdef void order_spam(int tons)
```
為此,`spam.c`中`order_spam()`的簽名必須與 Cython 使用的簽名匹配,特別是該函數必須是靜態的:
```py
static void order_spam(int tons)
{
printf("Ordered %i tons of spam!\n", tons);
}
```
### struct,union 和 enum 聲明的樣式
結構,聯合和枚舉有兩種主要方式可以在 C 頭文件中聲明:使用標記名稱或使用 typedef。基于這些的各種組合還存在一些變化。
使 Cython 聲明與頭文件中使用的樣式匹配非常重要,這樣 Cython 就可以對它生成的代碼中的類型發出正確的類型引用。為了實現這一點,Cython 提供了兩種不同的語法來聲明 struct,union 或 enum 類型。上面介紹的樣式對應于標記名稱的使用。要獲得其他樣式,請使用 [`ctypedef`](language_basics.html#ctypedef) 作為聲明的前綴,如下圖所示。
下表顯示了可以在頭文件中找到的各種可能的樣式,以及應該從塊中放入`cdef extern`的相應 Cython 聲明。結構聲明用作示例;這同樣適用于 union 和 enum 聲明。
<colgroup><col width="18%"> <col width="32%"> <col width="50%"></colgroup>
| C 代碼 | 相應的 Cython 代碼的可能性 | 評論 |
| --- | --- | --- |
|
```py
struct Foo {
...
};
```
|
```py
cdef struct Foo:
...
```
| Cython 將在生成的 C 代碼中引用 as `struct Foo`。 |
|
```py
typedef struct {
...
} Foo;
```
|
```py
ctypedef struct Foo:
...
```
| Cython 將生成的 C 代碼中的類型簡稱為`Foo`。 |
|
```py
typedef struct foo {
...
} Foo;
```
|
```py
cdef struct foo:
...
ctypedef foo Foo #optional
```
要么:
```py
ctypedef struct Foo:
...
```
| 如果 C 標頭同時使用帶有 _ 不同 _ 名稱的標簽和 typedef,則可以在 Cython 中使用任一形式的聲明(盡管如果需要轉發引用類型,則必須使用第一個表單)。 |
|
```py
typedef struct Foo {
...
} Foo;
```
|
```py
cdef struct Foo:
...
```
| 如果標題使用標簽和 typedef 的 _ 相同的 _ 名稱,則無法為其包含 [`ctypedef`](language_basics.html#ctypedef) - 但是,這不是必需的。 |
另請參閱 [外部擴展類型](extension_types.html#external-extension-types) 的使用。請注意,在下面的所有情況中,您將 Cython 代碼中的類型簡稱為`Foo`,而不是`struct Foo`。
### 訪問 Python / C API 例程
`cdef extern from`語句的一個特殊用途是獲取對 Python / C API 中的例程的訪問。例如,:
```py
cdef extern from "Python.h":
object PyString_FromStringAndSize(char *s, Py_ssize_t len)
```
將允許您創建包含空字節的 Python 字符串。
### 特殊類型
Cython 預定義名稱`Py_ssize_t`以用于 Python / C API 例程。要使擴展與 64 位系統兼容,應始終在 Python / C API 例程文檔中指定的類型中使用此類型。
### Windows 調用約定
`__stdcall`和`__cdecl`調用約定說明符可以在 Cython 中使用,其語法與 Windows 上的 C 編譯器使用的語法相同,例如:
```py
cdef extern int __stdcall FrobnicateWindow(long handle)
cdef void (__stdcall *callback)(void *)
```
如果使用`__stdcall`,則僅認為該功能與相同簽名的其他`__stdcall`功能兼容。
### 解決命名沖突 - C 名稱規范
每個 Cython 模塊都有一個用于 Python 和 C 名稱的模塊級命名空間。如果要包裝一些外部 C 函數并為 Python 用戶提供相同名稱的 Python 函數,這可能會很不方便。
Cython 提供了幾種解決此問題的方法。最好的方法,特別是如果要包裝許多 C 函數,是將外部 C 函數聲明放入`.pxd`文件,從而使用 [中描述的設備在 Cython 模塊之間共享聲明中使用不同的命名空間](sharing_declarations.html#sharing-declarations) 。將它們寫入`.pxd`文件允許它們跨模塊重用,避免以正常的 Python 方式命名沖突,甚至可以輕松地在 cimport 上重命名它們。例如,如果您的`decl.pxd`文件聲明了 C 函數`eject_tomato`:
```py
cdef extern from "myheader.h":
void eject_tomato(float speed)
```
然后你可以將它導入并將其包裝在`.pyx`文件中,如下所示:
```py
from decl cimport eject_tomato as c_eject_tomato
def eject_tomato(speed):
c_eject_tomato(speed)
```
或者只是簡單地輸入`.pxd`文件并將其用作前綴:
```py
cimport decl
def eject_tomato(speed):
decl.eject_tomato(speed)
```
請注意,這沒有運行時查找開銷,就像在 Python 中一樣。 Cython 在編譯時解析`.pxd`文件中的名稱。
對于導入時命名空間或重命名不夠的特殊情況,例如當 C 中的名稱與 Python 關鍵字沖突時,您可以使用 C 名稱規范在聲明時為 C 函數提供不同的 Cython 和 C 名稱。例如,假設您要包裝一個名為`yield()`的外部 C 函數。如果您將其聲明為:
```py
cdef extern from "myheader.h":
void c_yield "yield" (float speed)
```
那么它的 Cython 可見名稱將是`c_yield`,而它在 C 中的名稱將是`yield`。然后你可以用它包裝它:
```py
def call_yield(speed):
c_yield(speed)
```
對于函數,可以為變量,結構,聯合,枚舉,結構和聯合成員以及枚舉值指定 C 名稱。例如:
```py
cdef extern int one "eins", two "zwei"
cdef extern float three "drei"
cdef struct spam "SPAM":
int i "eye"
cdef enum surprise "inquisition":
first "alpha"
second "beta" = 3
```
請注意,Cython 不會對您提供的字符串進行任何驗證或名稱修改。它會將裸文本注入未經修改的 C 代碼中,因此您完全可以使用此功能。如果你想聲明一個名稱`xyz`并讓 Cython 將文本“讓 C 編譯器在這里失敗”注入它的 C 文件中,你可以使用 C 名聲明來完成。考慮這是一個高級功能,僅適用于其他一切都失敗的罕見情況。
### 包括逐字 C 代碼
對于高級用例,Cython 允許您直接將 C 代碼寫為`cdef extern from`塊的“docstring”:
```py
cdef extern from *:
"""
/* This is C code which will be put
* in the .c file output by Cython */
static long square(long x) {return x * x;}
#define assign(x, y) ((x) = (y))
"""
long square(long x)
void assign(long& x, long y)
```
以上內容基本上等同于將 C 代碼放在文件`header.h`中并進行寫入
```py
cdef extern from "header.h":
long square(long x)
void assign(long& x, long y)
```
也可以組合頭文件和逐字 C 代碼:
```py
cdef extern from "badheader.h":
"""
/* This macro breaks stuff */
#undef int
"""
# Stuff from badheader.h
```
在這種情況下,C 代碼`#undef int`放在 Cython 生成的 C 代碼中的`#include "badheader.h"`之后。
請注意,該字符串的解析方式與 Python 中的任何其他 docstring 一樣。如果要將字符轉義傳遞到 C 代碼文件,請使用原始文檔字符串,即`r""" ... """`。
## 使用 C
Cython 提供了兩種方法,用于從 Cython 模塊生成 C 語句,供外部 C 代碼 - 公共聲明和 C API 聲明使用。
Note
你不需要使用其中任何一個來從一個 Cython 模塊聲明另一個 Cython 模塊 - 你應該使用 [`cimport`](sharing_declarations.html#cimport) 語句。在 Cython 模塊之間共享聲明。
### 公開聲明
您可以通過使用 public 關鍵字聲明它們,使 Cython 模塊中定義的 C 類型,變量和函數可以被 C 代碼訪問,C 代碼與 Cython 生成的 C 文件鏈接在一起:
```py
cdef public struct Bunny: # public type declaration
int vorpalness
cdef public int spam # public variable declaration
cdef public void grail(Bunny *) # public function declaration
```
如果 Cython 模塊中有任何公共聲明,則會生成一個名為`modulename.h`文件的頭文件,其中包含等效的 C 聲明,以包含在其他 C 代碼中。
一個典型的用例是從多個 C 源構建一個擴展模塊,其中一個是 Cython 生成的(即`setup.py`中有`Extension("grail", sources=["grail.pyx", "grail_helper.c"])`的東西。在這種情況下,文件`grail_helper.c`只需要添加`#include "grail.h"` ]為了訪問公共 Cython 變量。
更高級的用例是使用 Cython 在 Python 中嵌入 Python。在這種情況下,請確保調用 Py_Initialize()和 Py_Finalize()。例如,在包含`grail.h`的以下代碼段中:
```py
#include <Python.h>
#include "grail.h"
int main() {
Py_Initialize();
initgrail(); /* Python 2.x only ! */
Bunny b;
grail(b);
Py_Finalize();
}
```
然后,可以在單個程序(或庫)中將此 C 代碼與 Cython 生成的 C 代碼一起構建。
在 Python 3.x 中,應該避免直接調用模塊 init 函數。相反,使用 [inittab 機制](https://docs.python.org/3/c-api/import.html#c._inittab)將 Cython 模塊鏈接到單個共享庫或程序中。
```py
err = PyImport_AppendInittab("grail", PyInit_grail);
Py_Initialize();
grail_module = PyImport_ImportModule("grail");
```
如果 Cython 模塊位于包中,則`.h`文件的名稱由模塊的完整點名稱組成,例如,名為`foo.spam`的模塊將有一個名為`foo.spam.h`的頭文件。
Note
在某些操作系統(如 Linux)上,也可以先按常規方式構建 Cython 擴展,然后像動態庫一樣鏈接到生成的`.so`文件。請注意,這不是便攜式的,所以應該避免。
### C API 聲明
為 C 代碼提供聲明的另一種方法是使用 [`api`](#api) 關鍵字聲明它們。您可以將此關鍵字與 C 函數和擴展類型一起使用。生成一個名為`modulename_api.h`的頭文件,其中包含函數和擴展類型的聲明,以及一個名為`import_modulename()`的函數。
想要使用這些函數或擴展類型的 C 代碼需要包含標題并調用`import_modulename()`函數。然后可以調用其他函數,并像往常一樣使用擴展類型。
如果想要使用這些函數的 C 代碼是多個共享庫或可執行文件的一部分,則需要在使用這些函數的每個共享庫中調用`import_modulename()`函數。如果在調用其中一個 api 調用時遇到分段錯誤(linux 上的 SIGSEGV)崩潰,這可能表明包含生成分段錯誤的 api 調用的共享庫之前沒有調用`import_modulename()`函數崩潰的 api 電話。
當您包含`modulename_api.h`時,Cython 模塊中的任何公共 C 類型或擴展類型聲明也可用。
```py
# delorean.pyx
cdef public struct Vehicle:
int speed
float power
cdef api void activate(Vehicle *v):
if v.speed >= 88 and v.power >= 1.21:
print("Time travel achieved")
```
```py
# marty.c
#include "delorean_api.h"
Vehicle car;
int main(int argc, char *argv[]) {
Py_Initialize();
import_delorean();
car.speed = atoi(argv[1]);
car.power = atof(argv[2]);
activate(&car);
Py_Finalize();
}
```
Note
在 Cython 模塊中定義的任何類型(用作參數或返回類型的導出函數)都需要聲明為 public,否則它們將不會包含在生成的頭文件中,并且當您嘗試編譯時會出現錯誤使用標頭的 C 文件。
使用 [`api`](#api) 方法不需要使用聲明的 C 代碼以任何方式與擴展模塊鏈接,因為 Python 導入機制用于動態建立連接。但是,只能以這種方式訪問??函數,而不是變量。另請注意,要正確設置模塊導入機制,用戶必須調用 Py_Initialize()和 Py_Finalize();如果您在`import_modulename()`調用中遇到分段錯誤,則很可能沒有完成。
您可以在同一功能上同時使用 [`public`](extension_types.html#public) 和 [`api`](#api) ,例如:
```py
cdef public api void belt_and_braces():
...
```
但是,請注意,您應該在給定的 C 文件中包含`modulename.h`或`modulename_api.h`,而不是兩者,否則您可能會遇到沖突的雙重定義。
如果 Cython 模塊位于包中,則:
* 頭文件的名稱包含模塊的完整虛線名稱。
* 導入函數的名稱包含全名,其中圓點由雙下劃線替換。
例如。名為`foo.spam`的模塊將有一個名為`foo.spam_api.h`的 API 頭文件和一個名為`import_foo__spam()`的導入函數。
### 多個公共和 API 聲明
您可以將 [`public`](extension_types.html#public) 和/或 [`api`](#api) 的整個項目一次性地聲明為 [`cdef`](language_basics.html#cdef) 塊,例,:
```py
cdef public api:
void order_spam(int tons)
char *get_lunch(float tomato_size)
```
這在`.pxd`文件中是有用的(參見 [在 Cython 模塊之間共享聲明](sharing_declarations.html#sharing-declarations) ),以通過所有三種方法使模塊的公共接口可用。
### 獲取和釋放 GIL
Cython 提供了獲取和發布[全球解釋器鎖(GIL)](https://docs.python.org/dev/glossary.html#term-global-interpreter-lock)的工具。當從多線程代碼調用可能阻塞的(外部 C)代碼或者想要從(本機)C 線程回調中使用 Python 時,這可能很有用。釋放 GIL 顯然應僅針對線程安全代碼或針對競爭條件和并發問題使用其他保護手段的代碼。
請注意,獲取 GIL 是阻塞線程同步操作,因此可能成本高昂。對于次要計算,可能不值得發布 GIL。通常,并行代碼中的 I / O 操作和實質計算將從中受益。
#### 釋放 GIL
您可以使用`with nogil`語句圍繞一段代碼釋放 GIL:
```py
with nogil:
<code to be executed with the GIL released>
```
with 語句主體中的代碼不得以任何方式引發異常或操縱 Python 對象,并且在不首先重新獲取 GIL 的情況下不得調用任何操作 Python 對象的內容。例如,Cython 在編譯時驗證這些操作,但不能查看外部 C 函數。必須正確聲明它們需要或不需要 GIL(見下文)才能使 Cython 的檢查生效。
#### 獲取 GIL
在沒有 GIL 的情況下執行的 C 代碼回調的 C 函數需要在操作 Python 對象之前獲取 GIL。這可以通過在函數頭中指定`with gil`來完成:
```py
cdef void my_callback(void *data) with gil:
...
```
如果可以從另一個非 Python 線程調用回調,則必須首先通過調用 [PyEval_InitThreads()](https://docs.python.org/dev/c-api/init.html#c.PyEval_InitThreads)來初始化 GIL。如果您已經在模塊中使用 [cython.parallel](parallelism.html#parallel),那么這已經得到了解決。
GIL 也可以通過`with gil`聲明獲得:
```py
with gil:
<execute this block with the GIL acquired>
```
#### 條件獲取/釋放 GIL
有時使用條件來決定是否使用 GIL 運行某段代碼。這個代碼無論如何都會運行,不同之處在于 GIL 是否會被保留或釋放。條件必須是常量(在編譯時)。
這對于分析,調試,性能測試和融合類型非常有用(參見 [條件 GIL 獲取/釋放](fusedtypes.html#fused-gil-conditional) ):
```py
DEF FREE_GIL = True
with nogil(FREE_GIL):
<code to be executed with the GIL released>
with gil(False):
<GIL is still released>
```
### 在沒有 GIL 的情況下將函數聲明為可調用
您可以在 C 函數頭或函數類型中指定 [`nogil`](#nogil) ,以聲明在沒有 GIL 的情況下調用是安全的:
```py
cdef void my_gil_free_func(int spam) nogil:
...
```
在 Cython 中實現這樣的函數時,它不能有任何 Python 參數或 Python 對象返回類型。此外,涉及 Python 對象(包括調用 Python 函數)的任何操作必須首先明確獲取 GIL,例如通過使用`with gil`塊或調用已定義的函數`with gil`。這些限制由 Cython 檢查,如果在`nogil`代碼部分中發現任何 Python 交互,您將收到編譯錯誤。
Note
`nogil`函數注釋聲明在沒有 GIL 的情況下調用函數是安全的。完全允許在持有 GIL 的同時執行它。如果調用者持有 GIL,則該函數本身不釋放 GIL。
聲明函數`with gil`(即在輸入時獲取 GIL)也隱式地使其簽名 [`nogil`](#nogil) 。
- Cython 3.0 中文文檔
- 入門
- Cython - 概述
- 安裝 Cython
- 構建 Cython 代碼
- 通過靜態類型更快的代碼
- Tutorials
- 基礎教程
- 調用 C 函數
- 使用 C 庫
- 擴展類型(又名.cdef 類)
- pxd 文件
- Caveats
- Profiling
- Unicode 和傳遞字符串
- 內存分配
- 純 Python 模式
- 使用 NumPy
- 使用 Python 數組
- 進一步閱讀
- 相關工作
- 附錄:在 Windows 上安裝 MinGW
- 用戶指南
- 語言基礎
- 擴展類型
- 擴展類型的特殊方法
- 在 Cython 模塊之間共享聲明
- 與外部 C 代碼連接
- 源文件和編譯
- 早期綁定速度
- 在 Cython 中使用 C ++
- 融合類型(模板)
- 將 Cython 代碼移植到 PyPy
- Limitations
- Cython 和 Pyrex 之間的區別
- 鍵入的內存視圖
- 實現緩沖協議
- 使用并行性
- 調試你的 Cython 程序
- 用于 NumPy 用戶的 Cython
- Pythran 作為 Numpy 后端