Skip to content

Commit a98124d

Browse files
committed
15.5小节完成
1 parent 547da4a commit a98124d

File tree

2 files changed

+341
-334
lines changed

2 files changed

+341
-334
lines changed

source/c15/p05_define_and_export_c_api_from_extension_modules.rst

Lines changed: 163 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
----------
1515
解决方案
1616
----------
17-
本节聚焦在处理Point对象(在15.4小节已经讲过)。仔细回一下,在C代码中包含了如下这些工具函数:
17+
本节主要问题是如何处理15.4小节中提到的Point对象。仔细回一下,在C代码中包含了如下这些工具函数:
1818

1919
::
2020

@@ -72,172 +72,171 @@
7272
}
7373
#endif
7474

75-
The most important feature here is the _PointAPIMethods table of function pointers. It
76-
will be initialized in the exporting module and found by importing modules.
77-
Change the original extension module to populate the table and export it as follows:
78-
79-
/* pysample.c */
80-
81-
#include "Python.h"
82-
#define PYSAMPLE_MODULE
83-
#include "pysample.h"
84-
85-
...
86-
/* Destructor function for points */
87-
static void del_Point(PyObject *obj) {
88-
printf("Deleting point\n");
89-
free(PyCapsule_GetPointer(obj,"Point"));
90-
}
91-
92-
/* Utility functions */
93-
static Point *PyPoint_AsPoint(PyObject *obj) {
94-
return (Point *) PyCapsule_GetPointer(obj, "Point");
95-
}
96-
97-
static PyObject *PyPoint_FromPoint(Point *p, int free) {
98-
return PyCapsule_New(p, "Point", free ? del_Point : NULL);
99-
}
100-
101-
static _PointAPIMethods _point_api = {
102-
PyPoint_AsPoint,
103-
PyPoint_FromPoint
104-
};
105-
...
106-
107-
/* Module initialization function */
108-
PyMODINIT_FUNC
109-
PyInit_sample(void) {
110-
PyObject *m;
111-
PyObject *py_point_api;
112-
113-
m = PyModule_Create(&samplemodule);
114-
if (m == NULL)
115-
return NULL;
116-
117-
/* Add the Point C API functions */
118-
py_point_api = PyCapsule_New((void *) &_point_api, "sample._point_api", NULL);
119-
if (py_point_api) {
120-
PyModule_AddObject(m, "_point_api", py_point_api);
121-
}
122-
return m;
123-
}
124-
125-
Finally, here is an example of a new extension module that loads and uses these API
126-
functions:
127-
128-
/* ptexample.c */
129-
130-
/* Include the header associated with the other module */
131-
#include "pysample.h"
132-
133-
/* An extension function that uses the exported API */
134-
static PyObject *print_point(PyObject *self, PyObject *args) {
135-
PyObject *obj;
136-
Point *p;
137-
if (!PyArg_ParseTuple(args,"O", &obj)) {
138-
return NULL;
139-
}
140-
141-
/* Note: This is defined in a different module */
142-
p = PyPoint_AsPoint(obj);
143-
if (!p) {
144-
return NULL;
145-
}
146-
printf("%f %f\n", p->x, p->y);
147-
return Py_BuildValue("");
148-
}
149-
150-
static PyMethodDef PtExampleMethods[] = {
151-
{"print_point", print_point, METH_VARARGS, "output a point"},
152-
{ NULL, NULL, 0, NULL}
153-
};
154-
155-
static struct PyModuleDef ptexamplemodule = {
156-
PyModuleDef_HEAD_INIT,
157-
"ptexample", /* name of module */
158-
"A module that imports an API", /* Doc string (may be NULL) */
159-
-1, /* Size of per-interpreter state or -1 */
160-
PtExampleMethods /* Method table */
161-
};
162-
163-
/* Module initialization function */
164-
PyMODINIT_FUNC
165-
PyInit_ptexample(void) {
166-
PyObject *m;
167-
168-
m = PyModule_Create(&ptexamplemodule);
169-
if (m == NULL)
170-
return NULL;
171-
172-
/* Import sample, loading its API functions */
173-
if (!import_sample()) {
174-
return NULL;
175-
}
176-
177-
return m;
178-
}
179-
180-
When compiling this new module, you don’t even need to bother to link against any of
181-
the libraries or code from the other module. For example, you can just make a simple
182-
setup.py file like this:
183-
184-
# setup.py
185-
from distutils.core import setup, Extension
186-
187-
setup(name='ptexample',
188-
ext_modules=[
189-
Extension('ptexample',
190-
['ptexample.c'],
191-
include_dirs = [], # May need pysample.h directory
192-
)
193-
]
194-
)
195-
196-
If it all works, you’ll find that your new extension function works perfectly with the C
197-
API functions defined in the other module:
198-
199-
>>> import sample
200-
>>> p1 = sample.Point(2,3)
201-
>>> p1
202-
<capsule object "Point *" at 0x1004ea330>
203-
>>> import ptexample
204-
>>> ptexample.print_point(p1)
205-
2.000000 3.000000
206-
>>>
75+
这里最重要的部分是函数指针表 ``_PointAPIMethods`` .
76+
它会在导出模块时被初始化,然后导入模块时被查找到。
77+
修改原始的扩展模块来填充表格并将它像下面这样导出:
78+
79+
80+
::
81+
82+
/* pysample.c */
83+
84+
#include "Python.h"
85+
#define PYSAMPLE_MODULE
86+
#include "pysample.h"
87+
88+
...
89+
/* Destructor function for points */
90+
static void del_Point(PyObject *obj) {
91+
printf("Deleting point\n");
92+
free(PyCapsule_GetPointer(obj,"Point"));
93+
}
94+
95+
/* Utility functions */
96+
static Point *PyPoint_AsPoint(PyObject *obj) {
97+
return (Point *) PyCapsule_GetPointer(obj, "Point");
98+
}
99+
100+
static PyObject *PyPoint_FromPoint(Point *p, int free) {
101+
return PyCapsule_New(p, "Point", free ? del_Point : NULL);
102+
}
103+
104+
static _PointAPIMethods _point_api = {
105+
PyPoint_AsPoint,
106+
PyPoint_FromPoint
107+
};
108+
...
109+
110+
/* Module initialization function */
111+
PyMODINIT_FUNC
112+
PyInit_sample(void) {
113+
PyObject *m;
114+
PyObject *py_point_api;
115+
116+
m = PyModule_Create(&samplemodule);
117+
if (m == NULL)
118+
return NULL;
119+
120+
/* Add the Point C API functions */
121+
py_point_api = PyCapsule_New((void *) &_point_api, "sample._point_api", NULL);
122+
if (py_point_api) {
123+
PyModule_AddObject(m, "_point_api", py_point_api);
124+
}
125+
return m;
126+
}
127+
128+
最后,下面是一个新的扩展模块例子,用来加载并使用这些API函数:
129+
130+
::
131+
132+
/* ptexample.c */
133+
134+
/* Include the header associated with the other module */
135+
#include "pysample.h"
136+
137+
/* An extension function that uses the exported API */
138+
static PyObject *print_point(PyObject *self, PyObject *args) {
139+
PyObject *obj;
140+
Point *p;
141+
if (!PyArg_ParseTuple(args,"O", &obj)) {
142+
return NULL;
143+
}
144+
145+
/* Note: This is defined in a different module */
146+
p = PyPoint_AsPoint(obj);
147+
if (!p) {
148+
return NULL;
149+
}
150+
printf("%f %f\n", p->x, p->y);
151+
return Py_BuildValue("");
152+
}
153+
154+
static PyMethodDef PtExampleMethods[] = {
155+
{"print_point", print_point, METH_VARARGS, "output a point"},
156+
{ NULL, NULL, 0, NULL}
157+
};
158+
159+
static struct PyModuleDef ptexamplemodule = {
160+
PyModuleDef_HEAD_INIT,
161+
"ptexample", /* name of module */
162+
"A module that imports an API", /* Doc string (may be NULL) */
163+
-1, /* Size of per-interpreter state or -1 */
164+
PtExampleMethods /* Method table */
165+
};
166+
167+
/* Module initialization function */
168+
PyMODINIT_FUNC
169+
PyInit_ptexample(void) {
170+
PyObject *m;
171+
172+
m = PyModule_Create(&ptexamplemodule);
173+
if (m == NULL)
174+
return NULL;
175+
176+
/* Import sample, loading its API functions */
177+
if (!import_sample()) {
178+
return NULL;
179+
}
180+
181+
return m;
182+
}
183+
184+
编译这个新模块时,你甚至不需要去考虑怎样将函数库或代码跟其他模块链接起来。
185+
例如,你可以像下面这样创建一个简单的 ``setup.py`` 文件:
186+
187+
::
188+
189+
# setup.py
190+
from distutils.core import setup, Extension
191+
192+
setup(name='ptexample',
193+
ext_modules=[
194+
Extension('ptexample',
195+
['ptexample.c'],
196+
include_dirs = [], # May need pysample.h directory
197+
)
198+
]
199+
)
200+
201+
如果一切正常,你会发现你的新扩展函数能和定义在其他模块中的C API函数一起运行的很好。
202+
203+
::
204+
205+
>>> import sample
206+
>>> p1 = sample.Point(2,3)
207+
>>> p1
208+
<capsule object "Point *" at 0x1004ea330>
209+
>>> import ptexample
210+
>>> ptexample.print_point(p1)
211+
2.000000 3.000000
212+
>>>
207213

208214
|
209215
210216
----------
211217
讨论
212218
----------
213-
This recipe relies on the fact that capsule objects can hold a pointer to anything you
214-
wish. In this case, the defining module populates a structure of function pointers, creates
215-
a capsule that points to it, and saves the capsule in a module-level attribute (e.g., sam
216-
ple._point_api).
217-
Other modules can be programmed to pick up this attribute when imported and extract
218-
the underlying pointer. In fact, Python provides the PyCapsule_Import() utility func‐
219-
tion, which takes care of all the steps for you. You simply give it the name of the attribute
220-
(e.g., sample._point_api), and it will find the capsule and extract the pointer all in one
221-
step.
222-
There are some C programming tricks involved in making exported functions look
223-
normal in other modules. In the pysample.h file, a pointer _point_api is used to point
224-
to the method table that was initialized in the exporting module. A related function
225-
import_sample() is used to perform the required capsule import and initialize this
226-
pointer. This function must be called before any functions are used. Normally, it would
227-
228-
be called in during module initialization. Finally, a set of C preprocessor macros have
229-
been defined to transparently dispatch the API functions through the method table.
230-
The user just uses the original function names, but doesn’t know about the extra indi‐
231-
rection through these macros.
232-
Finally, there is another important reason why you might use this technique to link
233-
modules together—it’s actually easier and it keeps modules more cleanly decoupled. If
234-
you didn’t want to use this recipe as shown, you might be able to cross-link modules
235-
using advanced features of shared libraries and the dynamic loader. For example, putting
236-
common API functions into a shared library and making sure that all extension modules
237-
link against that shared library. Yes, this works, but it can be tremendously messy in
238-
large systems. Essentially, this recipe cuts out all of that magic and allows modules to
239-
link to one another through Python’s normal import mechanism and just a tiny number
240-
of capsule calls. For compilation of modules, you only need to worry about header files,
241-
not the hairy details of shared libraries.
242-
Further information about providing C APIs for extension modules can be found in the
243-
Python documentation.
219+
本节基于一个前提就是,胶囊对象能获取任何你想要的对象的指针。
220+
这样的话,定义模块会填充一个函数指针的结构体,创建一个指向它的胶囊,并在一个模块级属性中保存这个胶囊,
221+
例如 ``sample._point_api`` .
222+
223+
其他模块能够在导入时获取到这个属性并提取底层的指针。
224+
事实上,Python提供了 ``PyCapsule_Import()`` 工具函数,为了完成所有的步骤。
225+
你只需提供属性的名字即可(比如sample._point_api),然后他就会一次性找到胶囊对象并提取出指针来。
226+
227+
在将被导出函数变为其他模块中普通函数时,有一些C编程陷阱需要指出来。
228+
在 ``pysample.h`` 文件中,一个 ``_point_api`` 指针被用来指向在导出模块中被初始化的方法表。
229+
一个相关的函数 ``import_sample()`` 被用来指向胶囊导入并初始化这个指针。
230+
这个函数必须在任何函数被使用之前被调用。通常来讲,它会在模块初始化时被调用到。
231+
最后,C的预处理宏被定义,被用来通过方法表去分发这些API函数。
232+
用户只需要使用这些原始函数名称即可,不需要通过宏去了解其他信息。
233+
234+
最后,还有一个重要的原因让你去使用这个技术来链接模块——它非常简单并且可以使得各个模块很清晰的解耦。
235+
如果你不想使用本机的技术,那你就必须使用共享库的高级特性和动态加载器来链接模块。
236+
例如,将一个普通的API函数放入一个共享库并确保所有扩展模块链接到那个共享库。
237+
这种方法确实可行,但是它相对繁琐,特别是在大型系统中。
238+
本节演示了如何通过Python的普通导入机制和仅仅几个胶囊调用来将多个模块链接起来的魔法。
239+
对于模块的编译,你只需要定义头文件,而不需要考虑函数库的内部细节。
240+
241+
更多关于利用C API来构造扩展模块的信息可以参考
242+
`Python的文档 <http://docs.python.org/3/extending/extending.html>`_

0 commit comments

Comments
 (0)