|
14 | 14 | ----------
|
15 | 15 | 解决方案
|
16 | 16 | ----------
|
17 |
| -本节聚焦在处理Point对象(在15.4小节已经讲过)。仔细回一下,在C代码中包含了如下这些工具函数: |
| 17 | +本节主要问题是如何处理15.4小节中提到的Point对象。仔细回一下,在C代码中包含了如下这些工具函数: |
18 | 18 |
|
19 | 19 | ::
|
20 | 20 |
|
|
72 | 72 | }
|
73 | 73 | #endif
|
74 | 74 |
|
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 | + >>> |
207 | 213 |
|
208 | 214 | |
|
209 | 215 |
|
210 | 216 | ----------
|
211 | 217 | 讨论
|
212 | 218 | ----------
|
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