《扩展和嵌入python解释器》1.12 为扩展模块提供C API

1.12 为扩展模块提供C API(Providing a C API for an Extension Module)

许多扩展模块仅仅提供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).

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值