许多扩展模块仅仅提供Python使用的新函数和类型,但有时扩展模块的代码对其他模块有用。例如,一个扩展模块实现了新类型’collection’,功能象没有顺序的list一样。就象标准的Python list类型有运行扩展模块创建操作list的C API一样,这个新collection类型也应有一个直接从扩展模块进行操作的C函数集。
起初,看起来很容易:仅是写些函数(当然,不能声明它们为static),提供一个合适的头文件,并编写相应的C API文档。事实上,如果所有的扩展模块总是和Python解释器静态连接,这样做有用。然而,当模块使用共享库时,在一个模块中定义的符号也许在另一个模块中不可见。可见性的细节依赖于操作系统,有的系统为Python解释器和所有扩展模块使用全局名字空间(如windows),但是,其他系统(AIX是其中一个例子)要求在模块连接时明确导入符号列表,或提供不同策略的选择(大多数Unices)。即使符号是全局可见的,函数在希望被调用时可能函数的模块还没有加载!
为此,可移植性要求不能假设符号的可见性。这意味着,为了避免和其他扩展模块(正如 1.4中讨论的)发生命名冲突,在扩展模块中的所有符号应该被声明为static,除了模块初始化函数。这意味着应该被从其他扩展模块访问的符号必须以不同的方式导出。
Python提供了一个特别的机制来从一个扩展模块到另一个扩展模块传递C-level信息(指针):CObject。CObject是存储指针(void *)的Python数据类型。 CObject只能经由它们的C API创建和访问,但它们能象其他Python对象到处传递。特别地,它们能够在扩展模块的名字空间中指定一个名字。其他模块导入这个模块,获得这个名称的值,然后从CObject获取指针。
导出C API的扩展模块有多种方式使用CObject。每个名字能够获得它的CObject,或全部C API的指针保存在一个数组中,数组的地址在CObject发布。在提供代码的模块和客户模块间,不同的保存和获取指针的任务可以用不同的方式提供。
下面的例子演示了一种方式,它把大多数工作方在导出模块的作者身上,这对通常使用的库模块是正确的。它存储所有的C API指针(例子中只有一个指针)在一个void类型的指针数组中,这个指针数组成为CObject的一个值。相应模块的头文件提供一个宏,负责导出模块并接受模块C API指针。客户模块只需要在访问C API前调用这个宏。
导出模块是从 1.1节的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 Py_BuildValue("i", sts); }
在模块的开始处,在
#include "Python.h"
行后面必须添加下面两行:
#define SPAM_MODULE #include "spammodule.h"
#define
用来在导出模块中,而不是客户模块中,识别头文件。最后,模块的初始化函数必须处理C API指针数组的初始化。
PyMODINIT_FUNC initspam(void) { PyObject *m; static void *PySpam_API[PySpam_API_pointers]; PyObject *c_api_object; m = Py_InitModule("spam", SpamMethods); /* Initialize the C API pointer array */ PySpam_API[PySpam_System_NUM] = (void *)PySpam_System; /* Create a CObject containing the API pointer array's address */ c_api_object = PyCObject_FromVoidPtr((void *)PySpam_API, NULL); if (c_api_object != NULL) PyModule_AddObject(m, "_C_API", c_api_object); }
注意:PySpam_API
被声明为static,否则指针数组会在initspam()函数终止时消失。
主要工作在头文件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 and set exception on error, 0 on success. */ static int import_spam(void) { PyObject *module = PyImport_ImportModule("spam"); if (module != NULL) { PyObject *c_api_object = PyObject_GetAttrString(module, "_C_API"); if (c_api_object == NULL) return -1; if (PyCObject_Check(c_api_object)) PySpam_API = (void **)PyCObject_AsVoidPtr(c_api_object); Py_DECREF(c_api_object); } return 0; } #endif #ifdef __cplusplus } #endif #endif /* !defined(Py_SPAMMODULE_H) */
所有客户模块为了访问函数PySpam_System(),需要做的是在初始化函数中的调用import_spam()函数(而不是宏):
PyMODINIT_FUNC initclient(void) { PyObject *m; Py_InitModule("client", ClientMethods); if (import_spam() < 0) return; /* additional initialization can happen here */ }
这种方式的主要缺点是文件spammodule.h相当负责。然而,对于每个导出函数基本结构是一样的,所以只需学习一次。
最后应该提到的是CObject提供其他功能,对存储在CObject的指针的内存申请和释放特别有用。在Python/C API Reference Manual的’CObjects’部分有详细描述,并有CObjects的实现(Python 提供的代码中Include/cobject.h ,Objects/cobject.c).