15.5 从扩张模块中定义和导出C的API

问题

You have a C extension module that internally defines a variety of useful functions thatyou would like to export as a public C API for use elsewhere. You would like to use thesefunctions inside other extension modules, but don’t know how to link them together,and doing it with the C compiler/linker seems excessively complicated (or impossible).

解决方案

This recipe focuses on the code written to handle Point objects, which were presentedin Recipe 15.4. If you recall, that C code included some utility functions like this:

/ Destructor function for points [](#)/static void del_Point(PyObject *obj) {

free(PyCapsule_GetPointer(obj,”Point”));

}

/ Utility functions [](#)/static Point *PyPoint_AsPoint(PyObject *obj) {

return (Point *) PyCapsule_GetPointer(obj, “Point”);

}

static PyObject *PyPoint_FromPoint(Point *p, int must_free) {return PyCapsule_New(p, “Point”, must_free ? del_Point : NULL);
}

The problem now addressed is how to export the PyPoint_AsPoint() and PyPoint_FromPoint() functions as an API that other extension modules could use andlink to (e.g., if you have other extensions that also want to use the wrapped Pointobjects).To solve this problem, start by introducing a new header file for the “sample” extensioncalled pysample.h. Put the following code in it:

/ pysample.h [](#)/#include “Python.h”#include “sample.h”#ifdef __cplusplusextern “C” {#endif

/ Public API Table [](#)/typedef struct {

Point *(*aspoint)(PyObject *);PyObject *(*frompoint)(Point *, int);

} _PointAPIMethods;

setup(name="ptexample",ext_modules=[Extension(‘ptexample",[‘ptexample.c"],include_dirs = [], # May need pysample.h directory) ]

)

If it all works, you’ll find that your new extension function works perfectly with the CAPI functions defined in the other module:

>>> import sample
>>> p1 = sample.Point(2,3)
>>> p1
<capsule object "Point *" at 0x1004ea330>
>>> import ptexample
>>> ptexample.print_point(p1)
2.000000 3.000000
>>>

讨论

This recipe relies on the fact that capsule objects can hold a pointer to anything youwish. In this case, the defining module populates a structure of function pointers, createsa capsule that points to it, and saves the capsule in a module-level attribute (e.g., sample._point_api).Other modules can be programmed to pick up this attribute when imported and extractthe underlying pointer. In fact, Python provides the PyCapsule_Import() utility func‐tion, which takes care of all the steps for you. You simply give it the name of the attribute(e.g., sample._point_api), and it will find the capsule and extract the pointer all in onestep.There are some C programming tricks involved in making exported functions looknormal in other modules. In the pysample.h file, a pointer _point_api is used to pointto the method table that was initialized in the exporting module. A related functionimport_sample() is used to perform the required capsule import and initialize thispointer. This function must be called before any functions are used. Normally, it would

be called in during module initialization. Finally, a set of C preprocessor macros havebeen defined to transparently dispatch the API functions through the method table.The user just uses the original function names, but doesn’t know about the extra indi‐rection through these macros.Finally, there is another important reason why you might use this technique to linkmodules together—it’s actually easier and it keeps modules more cleanly decoupled. Ifyou didn’t want to use this recipe as shown, you might be able to cross-link modulesusing advanced features of shared libraries and the dynamic loader. For example, puttingcommon API functions into a shared library and making sure that all extension moduleslink against that shared library. Yes, this works, but it can be tremendously messy inlarge systems. Essentially, this recipe cuts out all of that magic and allows modules tolink to one another through Python’s normal import mechanism and just a tiny numberof capsule calls. For compilation of modules, you only need to worry about header files,not the hairy details of shared libraries.Further information about providing C APIs for extension modules can be found in thePython documentation.

文章导航