python 的 C语言中调用方法 作者:xieaotian发表于2008-08-21 15:19:43
Lots more details on writing wrappers
From DANSE
Table of contents [showhide]
1 Simple examples1.1 Convert input
1.2 Call the function
1.3 Convert the output
1.4 Complete wrapper example
2 class instances3 aggregate types3.1 Example: Unpacking a Python Dictionary
3.2 Example: Creating a Python list out of C++ variables
Every wrapper function does three things:
Converts the inputs from Python objects to C or C++ objects,
calls the C++ function,
converts the output to a Python object with the result and return it.
Let's first run through these steps with numbers and strings, for which there's an immediate connection between Python and C++ types; we can use a function, PyArg_ParseTuple, which is built in to the API. Later examples look at tasks like working with C++ class instances and using aggregate Python types such as dictionaries and lists.
[edit]Simple examples
[edit]Convert input
Every wrapper has the same signature as every other:
PyObject * wrap_a_function(PyObject *, PyObject * args)
PyObject (and everything else around here with the Py-prefix) is defined for us in the API. The first pointer argument, used for some highly specific purpose, is generally ignored. (When you do need it, you'll need a lot more help than I can give). The second argument is a PyTuple (a "subtype" of PyObject) that holds the tuple of arguments that the user called the function with in Python.
//One integer:int a;int ok = PyArg_ParseTuple(args, "i",&a); if(!ok) return 0;
//Two integersint a, b;int ok = PyArg_ParseTuple(args, "ii",&a, &b); if(!ok) return 0;
//One integer, a string, two doublesint anint;char * astring; // don't allocate memory!double dub1, dub2;int ok = PyArg_ParseTuple(args, "isdd", &anint, &astring, &dub1, &dub2); if(!ok) return 0;
A complete list of what can go into the format string is given in the extension documentation (look in the "ext" subdirectory of the "doc" directory in your Python distribution, or look online at http://docs.python.org/ext/ext.html ). In the current documentation, you want section 1.7, "Extracting Parameters in Extension Functions".
PyArg_ParseTuple() checks the types of the objects in the tuple "args" against the types given in the format string. If there's a discrepancy, it sets an appropriate exception and returns a null pointer; The Python interpreter regards the null pointer as failure and raises an exception. So if you're using PyArg_ParseTuple(), most of the error checking is done for you! Once PyArg_ParseTuple has succesfully returned, do any additional processing or checking of the input data that's appropriate. For example, Python has no unsigned type, so you might need to make sure that an integer is greater than zero.
[edit]Call the function
It's your function, call it.
[edit]Convert the output
Convert the output to a Python object. The API gives a function called Py_BuildValue; here are some examples:
// return an integerPyObject *py_result = Py_BuildValue("i", result);
// return a stringPyObject *py_result = Py_BuildValue("s", astring);
[edit]Complete wrapper example
Here's a complete example of wrapping a function that takes a double, a string, and an int (in that order) and returns an int. We expect that the arguments will come in the order string, int, double.
PyObject *wrap_bogus(PyObject *, PyObject * args){ //One: get the arguments from Python int anint = 0; double adub = 0; char * astring = 0; int ok = PyArg_ParseTuple( args, "sid", &astring, &anint, &adub); if(!ok) return 0; //do any checking of arguments here... //Two: make the function call int result = bogus( adub, astring, anint); //do any postprocessing of the result ... //Three: build a Python object to return return PyBuildValue("i",result);}
[edit]class instances
How do you use C++ classes from Python? You wrap the class's constructor(s) to dynamically allocate an instance of a C++ class and pass a handle back to Python. That handle can then be used with other wrappers for class methods.
We get the arguments to the constructor from the Python API, create the object using new, and return a pointer to that object to the interpreter. Use the API function PyCObject_FromVoidPtr to return the pointer to Python. Here's an example with a real (if trivial) class called Numbers:
class Numbers{public: Numbers(int first, double second) : one( first), two(second){} double NumMemberMult(void){ return m_first*m_second;}private: int m_first; double m_second;};
Here's a wrapper that creates a new instance of Numbers:
PyObject *wrap_new_Numbers(PyObject *, PyObject* args){ //First, extract the arguments from a Python tuple int arg1; double arg2; int ok = PyArg_ParseTuple(args,"id",&arg1,&arg2); if(!ok) return 0; //Second, dynamically allocate a new object Numbers *newnum = new Numbers(arg1, arg2); //Third, wrap the pointer as a "PyCObject" and // return that object to the interpreter return PyCObject_FromVoidPtr( newnum, PyDelNumbers);}
Look familiar? This wrapper has essentially the same structure as wrap_bogus(). That's because ALL wrappers have this structure.
The pointer to the dynamically allocated object, newnum, goes out of scope as soon as wrap_new_Numbers() returns. The only thing keeping this from being the mega-classic memory leak is that the Python interpreter has an object that will keep track of the address of the new object.
The interpreter tracks that object for us, and when we lose interest in it (its reference count goes to zero), the interpreter will call a C++ function to delete the object. The second slot in PyCObject_AsVoidPtr() is a pointer to a function with return type void and one void * argument. So the overall signature of PyCObject_AsVoidPtr is:
PyObject * PyCObject_FromVoidPtr( void *, void (*DeleteFunction)(void*));
You must supply the function pointed to (in this example called PyDelNumbers). It has the "delete" corresponding to the "new" in the wrapper. Here's the definition of that function:
static void PyDelNumbers(void *ptr){ //std::cout<<"Called PyDelNumbers()\n"; //Good to see once Numbers * oldnum = static_cast
