打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Embedding Python in C/C++: Part I

Introduction

Inspired by the article "Embedding Python in Multi-Threaded C/C++ Applications" (Linux Journal), I felt the need for a more comprehensive coverage on the topic of embedding Python. While writing this article, I had two objectives:

  1. This is written for programmers who are more experienced in C/C++ than in Python, the tutorial takes a practical approach and omits all theoretical discussions.
  2. Try to maintain Python's cross-platform compatibility when writing the embedding code.

Now, you have got some modules written in Python by others and you want to use them. You are experienced in C/C++, but fairly new to Python. You might wonder if you can find a tool to convert them to C code, like the conversion from FORTRAN. The answer is no. Some tools can help you generate the executable from a Python module. Is the problem solved? No. Converting code into executables usually makes things even more complicated, as you must figure out how your C/C++ application communicates with the executable "black-box".

I am going to introduce C/C++ programmers to Python/C API, a C library that helps to embed python modules into C/C++ applications. The API library provides a bunch of C routines to initialize the Python Interpreter, call into your Python modules and finish up the embedding. The library is built with Python and distributed with all the recent Python releases.

Part I of this article series discusses the basics of Python embedding. Part II will move on to more advanced topics. This tutorial does not teach the Python language systematically, but I will briefly describe how the Python code works when it comes up. The emphasis will be on how to integrate Python modules with your C/C++ applications. See article: "Embedding Python in C/C++: Part II".

In order to use the source code, you should install a recent Python release, Visual C++ (or GCC compiler on Linux). The environment that I have used to test is: Python 2.4 (Windows and Linux), Visual C++ 6.0 (Windows) or GCC 3.2 (RedHat 8.0 Linux). With Visual C++, select the Release configuration to build. Debug configuration requires Python debug library "python24_d.lib", which is not delivered with normal distributions.

Background

Python is a powerful interpreted language, like Java, Perl and PHP. It supports a long list of great features that any programmer would expect, two of my favorite features are "simple" and "portable". Along with the available tools and libraries, Python makes a good language for modeling and simulation developers. Best of all, it's free and the tools and libraries written for Python programmers are also free. For more details on the language, visit the official website.

Embedding basics: functions, classes and methods

First, let us start from a sample C program that calls a function within a Python module. Here is the source file "call_function.c":

// call_function.c - A sample of calling // python functions from C code// #include <Python.h>int main(int argc, char *argv[]){    PyObject *pName, *pModule, *pDict, *pFunc, *pValue;    if (argc < 3)     {        printf("Usage: exe_name python_source function_name\n");        return 1;    }    // Initialize the Python Interpreter    Py_Initialize();    // Build the name object    pName = PyString_FromString(argv[1]);    // Load the module object    pModule = PyImport_Import(pName);    // pDict is a borrowed reference     pDict = PyModule_GetDict(pModule);    // pFunc is also a borrowed reference     pFunc = PyDict_GetItemString(pDict, argv[2]);    if (PyCallable_Check(pFunc))     {        PyObject_CallObject(pFunc, NULL);    } else     {        PyErr_Print();    }    // Clean up    Py_DECREF(pModule);    Py_DECREF(pName);    // Finish the Python Interpreter    Py_Finalize();    return 0;}

The Python source file "py_function.py" is as follows:

'''py_function.py - Python source designed to ''''''demonstrate the use of python embedding'''def multiply():    c = 12345*6789    print 'The result of 12345 x 6789 :', c    return c

Note that checks for the validity of objects are omitted for brevity. On Windows, simply compile the C source and get the executable, which we call "call_function.exe". To run it, enter the command line "call_function py_function multiply". The second argument is the name of the Python file (without extension), which when loaded becomes the module name. The third argument is the name of the Python function you are going to call within the module. The Python source does not take part in compiling or linking; it's only loaded and interpreted at run-time. The output from the execution is:

The result of 12345 x 6789 : 83810205

The C code itself is self-explanatory, except that:

  • Everything in Python is an object. pDict and pFunc are borrowed references so we don't need to Py_DECREF() them.
  • All the Py_XXX and PyXXX_XXX calls are Python/C API calls.
  • The code will compile and run on all the platforms that Python supports.

Now, we want to pass arguments to the Python function. We add a block to handle arguments to the call:

if (PyCallable_Check(pFunc)) {    // Prepare the argument list for the call    if( argc > 3 )    {            pArgs = PyTuple_New(argc - 3);            for (i = 0; i < argc - 3; i++)            {            pValue = PyInt_FromLong(atoi(argv[i + 3]));                    if (!pValue)                    {                PyErr_Print();                         return 1;                    }                    PyTuple_SetItem(pArgs, i, pValue);                }                    pValue = PyObject_CallObject(pFunc, pArgs);        if (pArgs != NULL)        {            Py_DECREF(pArgs);        }    } else    {        pValue = PyObject_CallObject(pFunc, NULL);    }    if (pValue != NULL)     {        printf("Return of call : %d\n", PyInt_AsLong(pValue));        Py_DECREF(pValue);    }    else     {        PyErr_Print();    }        // some code omitted...}

The new C source adds a block of "Prepare the argument list for the call" and a check of the returned value. It creates a tuple (list-like type) to store all the parameters for the call. You can run the command "call_function py_source multiply1 6 7" and get the output:

The result of 6 x 7 : 42Return of call : 42

Writing classes in Python is easy. It is also easy to use a Python class in your C code. All you need to do is create an instance of the object and call its methods, just as you call normal functions. Here is an example:

// call_class.c - A sample of python embedding // (calling python classes from C code)//#include <Python.h>int main(int argc, char *argv[]){    PyObject *pName, *pModule, *pDict,                   *pClass, *pInstance, *pValue;    int i, arg[2];    if (argc < 4)     {        printf(          "Usage: exe_name python_fileclass_name function_name\n");        return 1;    }    // some code omitted...       // Build the name of a callable class     pClass = PyDict_GetItemString(pDict, argv[2]);    // Create an instance of the class    if (PyCallable_Check(pClass))    {        pInstance = PyObject_CallObject(pClass, NULL);     }    // Build the parameter list    if( argc > 4 )    {        for (i = 0; i < argc - 4; i++)            {                    arg[i] = atoi(argv[i + 4]);            }        // Call a method of the class with two parameters        pValue = PyObject_CallMethod(pInstance,                     argv[3], "(ii)", arg[0], arg[1]);    } else    {        // Call a method of the class with no parameters        pValue = PyObject_CallMethod(pInstance, argv[3], NULL);    }    if (pValue != NULL)     {        printf("Return of call : %d\n", PyInt_AsLong(pValue));        Py_DECREF(pValue);    }    else     {        PyErr_Print();    }       // some code omitted...}

The third parameter to PyObject_CallMethod(), "(ii)" is a format string, which indicates that the next arguments are two integers. Note that PyObject_CallMethod() takes C variables types as its arguments, not Python objects. This is different from the other calls we have seen so far. The Python source "py_class.py" is as follows:

'''py_class.py - Python source designed to demonstrate''' '''the use of python embedding'''class Multiply:     def __init__(self):             self.a = 6             self.b = 5         def multiply(self):            c = self.a*self.b    print 'The result of', self.a, 'x', self.b, ':', c            return c        def multiply2(self, a, b):            c = a*b    print 'The result of', a, 'x', b, ':', c    return c

To run the application, you add a class name between the module and function names, which is "Multiply" in this case. The command line becomes "call_class py_class Multiply multiply" or "call_class py_class Multiply multiply2 9 9".

Multi-threaded Python embedding

With the above preparations, we are ready for some serious business. The Python module and your C/C++ application have to run simultaneously from time to time. This is not uncommon in simulation communities. For instance, the Python module to be embedded is part of a real-time simulation and you run it in parallel with the rest of your simulation. Meanwhile, it interacts with the rest at run-time. One conventional technique is multi-threading. There are a number of options for multi-threaded embedding. We are going to discuss two of them here.

In one approach you create a separate thread in C and call the Python module from the thread function. This is natural and correct, except that you need to protect the Python Interpreter state. Basically, we lock the Python Interpreter before you use it and release after the use so that Python can track its states for the different calling threads. Python provides the global lock for this purpose. Let us look at some source code first. Here is the complete content of "call_thread.c":

// call_thread.c - A sample of python embedding // (C thread calling python functions)// #include <Python.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#include <errno.h>#ifdef WIN32    // Windows includes#include <Windows.h>#include <process.h>#define sleep(x) Sleep(1000*x)HANDLE handle;#else    // POSIX includes#include <pthread.h>pthread_t mythread;#endifvoid ThreadProc(void*);#define NUM_ARGUMENTS 5typedef struct {   int argc;   char *argv[NUM_ARGUMENTS]; } CMD_LINE_STRUCT;int main(int argc, char *argv[]){    int i;    CMD_LINE_STRUCT cmd;    pthread_t mythread;    cmd.argc = argc;    for( i = 0; i < NUM_ARGUMENTS; i++ )    {        cmd.argv[i] = argv[i];    }    if (argc < 3)     {        fprintf(stderr,          "Usage: call python_filename function_name [args]\n");        return 1;    }    // Create a thread#ifdef WIN32    // Windows code    handle = (HANDLE) _beginthread( ThreadProc,0,&cmd);#else    // POSIX code    pthread_create( &mythread, NULL,                  ThreadProc, (void*)&cmd );#endif    // Random testing code    for(i = 0; i < 10; i++)    {        printf("Printed from the main thread.\n");    sleep(1);    }    printf("Main Thread waiting for My Thread to complete...\n");    // Join and wait for the created thread to complete...#ifdef WIN32    // Windows code    WaitForSingleObject(handle,INFINITE);#else    // POSIX code    pthread_join(mythread, NULL);#endif    printf("Main thread finished gracefully.\n");    return 0;}void ThreadProc( void *data ){    int i;    PyObject *pName, *pModule, *pDict,                *pFunc, *pInstance, *pArgs, *pValue;    PyThreadState *mainThreadState, *myThreadState, *tempState;    PyInterpreterState *mainInterpreterState;        CMD_LINE_STRUCT* arg = (CMD_LINE_STRUCT*)data;    // Random testing code    for(i = 0; i < 15; i++)    {        printf("...Printed from my thread.\n");    sleep(1);    }    // Initialize python inerpreter    Py_Initialize();            // Initialize thread support    PyEval_InitThreads();    // Save a pointer to the main PyThreadState object    mainThreadState = PyThreadState_Get();    // Get a reference to the PyInterpreterState    mainInterpreterState = mainThreadState->interp;    // Create a thread state object for this thread    myThreadState = PyThreadState_New(mainInterpreterState);        // Release global lock    PyEval_ReleaseLock();        // Acquire global lock    PyEval_AcquireLock();    // Swap in my thread state    tempState = PyThreadState_Swap(myThreadState);    // Now execute some python code (call python functions)    pName = PyString_FromString(arg->argv[1]);    pModule = PyImport_Import(pName);    // pDict and pFunc are borrowed references     pDict = PyModule_GetDict(pModule);    pFunc = PyDict_GetItemString(pDict, arg->argv[2]);    if (PyCallable_Check(pFunc))     {        pValue = PyObject_CallObject(pFunc, NULL);    }    else {        PyErr_Print();    }    // Clean up    Py_DECREF(pModule);    Py_DECREF(pName);    // Swap out the current thread    PyThreadState_Swap(tempState);    // Release global lock    PyEval_ReleaseLock();        // Clean up thread state    PyThreadState_Clear(myThreadState);    PyThreadState_Delete(myThreadState);    Py_Finalize();    printf("My thread is finishing...\n");    // Exiting the thread#ifdef WIN32    // Windows code    _endthread();#else    // POSIX code    pthread_exit(NULL);#endif}

The thread function needs a bit of explanation. PyEval_InitThreads() initializes Python's thread support. PyThreadState_Swap(myThreadState) swaps in the state for the current thread, and PyThreadState_Swap(tempState) swaps it out. The Python Interpreter will save what happens between the two calls as the state data related to this thread. As a matter of fact, Python saves the data for each thread that is using the Interpreter so that the thread states are mutually exclusive. But it is your responsibility to create and maintain a state for each C thread. You may wonder why we didn't call the first PyEvel_AcquireLock(). Because PyEval_InitThreads() does so by default. In other cases, we do need to use PyEvel_AcquireLock() and PyEvel_ReleaseLock() in pairs.

Run "call_thread py_thread pythonFunc" and you can get the output as shown below. The file "py_thread.py" defines a function called pythonFunc() in which a similar random testing block prints "print from pythonFunc..." to screen fifteen times.

Printed from the main thread....Printed from my thread.Printed from the main thread....Printed from my thread.Printed from the main thread....Printed from my thread.Printed from the main thread....Printed from my thread.Printed from the main thread....Printed from my thread....Printed from my thread.Printed from the main thread.Printed from the main thread....Printed from my thread.Printed from the main thread....Printed from my thread.Printed from the main thread....Printed from my thread.Printed from the main thread....Printed from my thread.Main Thread waiting for My Thread to complete......Printed from my thread....Printed from my thread....Printed from my thread....Printed from my thread....Printed from my thread.My thread is finishing...Main thread finished gracefully.

Obviously, the implementation is getting complicated because writing multi-threading code in C/C++ is not trivial. Although the code is portable, it contains numerous patches, which require detailed knowledge of the system call interfaces for specific platforms. Fortunately, Python has done most of this for us, which brings up the second solution to our question under discussion, namely, letting Python handle the multi-threading. This time the Python code is enhanced to add a threading model:

''' Demonstrate the use of python threading'''import timeimport threadingclass MyThread(threading.Thread):    def __init__(self):        threading.Thread.__init__(self)    def run(self):        for i in range(15):            print 'printed from MyThread...'            time.sleep(1)def createThread():    print 'Create and run MyThread'    background = MyThread()    background.start()    print  'Main thread continues to run in foreground.'    for i in range(10):        print 'printed from Main thread.'        time.sleep(1)    print  'Main thread joins MyThread and waits until it is done...'    background.join() # Wait for the background task to finish    print  'The program completed gracefully.'

The C code does not handle threading any more. All it needs to do is just call createThread(). Try to use the previous "call_function.c". You can run "call_function py_thread createThread" to see how the output looks like. In this case, the second solution is cleaner and simpler. More importantly, Python threading model is portable. While the C threading code for Unix and Windows is different, it remains the same in Python.

The Python function createThread() is not required if our C code calls the thread class' start() and joint() methods. The relevant changes are listed in the following (from the C source file "call_thread_2.c"):

// Create instancepInstance = PyObject_CallObject(pClass, NULL); PyObject_CallMethod(pInstance, "start", NULL);i = 0;while(i<10){printf("Printed from C thread...\n");// !!!Important!!! C thread will not release CPU to // Python thread without the following call.PyObject_CallMethod(pInstance, "join", "(f)", 0.001);        Sleep(1000);i++;}printf(  "C thread join and wait for Python thread to complete...\n");PyObject_CallMethod(pInstance, "join", NULL);        printf("Program completed gracefully.\n");

Basically, after you create the class instance, call its start() method to create a new thread and execute its run() method. Note that without frequent short joins to the created thread, the created thread can only get executed at the beginning and the main thread will not release any CPU to it until it's done. You may try this out by commenting the joint call within the while loop. The behavior is somehow different from that of the previous case where we had called start() from within the Python module. This seems to be a feature of multi-threading which is not documented in the Python Library Reference.

Points of interest

I have deliberately paid attention to writing generic, portable C code for Python embedding. By encapsulating low-level system calls, Python supports platform portability and makes writing portable code easier. Most Python modules can be ported between Unix-like environment and Windows with little effort. We should keep this portability in mind when writing C/C++ wrapping code for Python modules. It may not be always straightforward to write portable C code yourself. Python has done a lot of hard work, as in the above case. Try to explore easier, simpler and cleaner solutions. Anyway, I stop here. How to write portable Python code goes beyond the scope of this tutorial. It could well make a good title for a new article.

While embedding is a good option for utilizing Python modules in C/C++ applications, there are other alternative approaches. On Windows, some tools (e.g. "py2exe") can convert Python modules into Windows executables directly. Then, you can spawn a process to run the executable from within the C/C++ application. One drawback is that you cannot call the module directly. Instead, your application has to interact with the module through certain types of IPC. This requires that the Python module under discussion be "IPC ready", meaning that it should have implemented an IPC interface to process incoming and outgoing data. Part II of this article will discuss embedding-related IPC techniques.

All the source code provided in this article is simple C code for demonstration purpose. In practice, I recommend to put the Python embedding code in C++ wrapper classes. This way, high-level application developers don't have to deal with the embedding details.

Conclusion

In this part, I have covered Python embedding from the basics such as calling functions, classes and methods, to not so basic topics like multi-threaded embedding. Python/C API provides a consistent calling interface to ease the task of integration between C/C++ and Python modules.

The discussion of multi-threaded embedding has raised a question for us: how does your C/C++ application communicate with the embedded Python module? The second part of this article shall address this issue from the IPC point of view.

History

  • This is the first revision of the article and the source code.
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
python脚本的调试方法讲解
MACS2软件call m6A甲基化peaks
很好的c 和Python混合编程文章
用boost.python为python写c/c 扩展曲折配置最终成功历程
python中的main函数
混合开发调用篇--如何用Python调用shell脚本和命令
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服