python扩展文件_1. 使用 C 或 C++ 扩展 Python
1.12.給擴展模塊提供C API?
很多擴展模塊提供了新的函數和類型供Python使用,但有時擴展模塊里的代碼也可以被其他擴展模塊使用。例如,一個擴展模塊可以實現一個類型 "collection" 看起來是沒有順序的。就像是Python列表類型,擁有C API允許擴展模塊來創建和維護列表,這個新的集合類型可以有一堆C函數用于給其他擴展模塊直接使用。
開始看起來很簡單:只需要編寫函數(無需聲明為 static ),提供恰當的頭文件,以及C API的文檔。實際上在所有擴展模塊都是靜態鏈接到Python解釋器時也是可以正常工作的。當模塊以共享庫鏈接時,一個模塊中的符號定義對另一個模塊不可見。可見的細節依賴于操作系統,一些系統的Python解釋器使用全局命名空間(例如Windows),有些則在鏈接時需要一個嚴格的已導入符號列表(一個例子是AIX),或者提供可選的不同策略(如Unix系列)。即便是符號是全局可見的,你要調用的模塊也可能尚未加載。
可移植性需要不能對符號可見性做任何假設。這意味著擴展模塊里的所有符號都應該聲明為 static ,除了模塊的初始化函數,來避免與其他擴展模塊的命名沖突(在段落 模塊方法表和初始化函數 中討論) 。這意味著符號應該 必須 通過其他導出方式來供其他擴展模塊訪問。
Python提供了一個特別的機制來傳遞C級別信息(指針),從一個擴展模塊到另一個:Capsules。一個Capsule是一個Python數據類型,會保存指針( void * )。Capsule只能通過其C API來創建和訪問,但可以像其他Python對象一樣的傳遞。通常,我們可以指定一個擴展模塊命名空間的名字。其他擴展模塊可以導入這個模塊,獲取這個名字的值,然后從Capsule獲取指針。
Capsule可以用多種方式導出C API給擴展模塊。每個函數可以用自己的Capsule,或者所有C API指針可以存儲在一個數組里,數組地址再發布給Capsule。存儲和獲取指針也可以用多種方式,供客戶端模塊使用。
無論你選擇哪個方法,正確地為你的 Capsule 命名都很重要。 函數 PyCapsule_New() 接受一個名稱形參 (const char *);允許你傳入一個 NULL 作為名稱,但我們強烈建議你指定一個名稱。 正確地命名的 Capsule 提供了一定程序的運行時類型安全;沒有可行的方式能區分兩個未命名的 Capsule。
通常來說,Capsule用于暴露C API,其名字應該遵循如下規范:
modulename.attributename
便利函數 PyCapsule_Import() 可以方便的載入通過Capsule提供的C API,僅在Capsule的名字匹配時。這個行為為C API用戶提供了高度的確定性來載入正確的C API。
如下例子展示了將大部分負擔交由導出模塊作者的方法,適用于常用的庫模塊。其會存儲所有C API指針(例子里只有一個)在 void 指針的數組里,并使其值變為Capsule。對應的模塊頭文件提供了宏來管理導入模塊和獲取C API指針;客戶端模塊只需要在訪問C API前調用這個宏即可。
導出的模塊修改自 spam 模塊,來自 一個簡單的例子 段落。函數 spam.system() 不會直接調用C庫函數 system() ,但一個函數 PySpam_System() 會負責調用,當然現實中會更復雜些(例如添加 "spam" 到每個命令)。函數 PySpam_System() 也會導出給其他擴展模塊。
函數 PySpam_System() 是個純C函數,聲明 static 就像其他地方那樣:
static int
PySpam_System(const char *command)
{
return system(command);
}
函數 spam_system() 按照如下方式修改:
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts;
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = PySpam_System(command);
return PyLong_FromLong(sts);
}
在模塊開頭,在此行后:
#include
添加另外兩行:
#define SPAM_MODULE
#include "spammodule.h"
#define 用于告知頭文件需要包含給導出的模塊,而不是客戶端模塊。最終,模塊的初始化函數必須負責初始化C API指針數組:
PyMODINIT_FUNC
PyInit_spam(void)
{
PyObject *m;
static void *PySpam_API[PySpam_API_pointers];
PyObject *c_api_object;
m = PyModule_Create(&spammodule);
if (m == NULL)
return NULL;
/* Initialize the C API pointer array */
PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;
/* Create a Capsule containing the API pointer array's address */
c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL);
if (PyModule_AddObject(m, "_C_API", c_api_object) < 0) {
Py_XDECREF(c_api_object);
Py_DECREF(m);
return NULL;
}
return m;
}
注意 PySpam_API 聲明為 static ;此外指針數組會在 PyInit_spam() 結束后消失!
頭文件 spammodule.h 里的一堆工作,看起來如下所示:
#ifndef Py_SPAMMODULE_H
#define Py_SPAMMODULE_H
#ifdef __cplusplus
extern "C" {
#endif
/* Header file for spammodule */
/* C API functions */
#define PySpam_System_NUM 0
#define PySpam_System_RETURN int
#define PySpam_System_PROTO (const char *command)
/* Total number of C API pointers */
#define PySpam_API_pointers 1
#ifdef SPAM_MODULE
/* This section is used when compiling spammodule.c */
static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;
#else
/* This section is used in modules that use spammodule's API */
static void **PySpam_API;
#define PySpam_System \
(*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])
/* Return -1 on error, 0 on success.
* PyCapsule_Import will set an exception if there's an error.
*/
static int
import_spam(void)
{
PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0);
return (PySpam_API != NULL) ? 0 : -1;
}
#endif
#ifdef __cplusplus
}
#endif
#endif/* !defined(Py_SPAMMODULE_H) */
客戶端模塊必須在其初始化函數里按順序調用函數 import_spam() (或其他宏)才能訪問函數 PySpam_System() 。
PyMODINIT_FUNC
PyInit_client(void)
{
PyObject *m;
m = PyModule_Create(&clientmodule);
if (m == NULL)
return NULL;
if (import_spam() < 0)
return NULL;
/* additional initialization can happen here */
return m;
}
這種方法的主要缺點是,文件 spammodule.h 過于復雜。當然,對每個要導出的函數,基本結構是相似的,所以只需要學習一次。
最后需要提醒的是Capsule提供了額外的功能,用于存儲在Capsule里的指針的內存分配和釋放。細節參考 Python/C API參考手冊的章節 膠囊 和Capsule的實現(在Python源碼發行包的 Include/pycapsule.h 和 Objects/pycapsule.c )。
備注
這個函數的接口已經在標準模塊 os 里了,這里作為一個簡單而直接的例子。
術語"借用"一個引用是不完全正確的:擁有者仍然有引用的拷貝。
檢查引用計數至少為1 沒有用 ,引用計數本身可以在已經釋放的內存里,并有可能被其他對象所用。
當你使用 "舊式" 風格調用約定時,這些保證不成立,盡管這依舊存在于很多舊代碼中。
總結
以上是生活随笔為你收集整理的python扩展文件_1. 使用 C 或 C++ 扩展 Python的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: comcerter无法识别串口_基于FP
- 下一篇: 一个路由器两个网段互通_如何判断两个IP