Extending & Embedding Python Using C - Embedding
Written by Mike James   
Tuesday, 13 August 2024
Article Index
Extending & Embedding Python Using C - Embedding
Embedding Under Linux
Python or C?
Embedded Debugging

Embedding Under Linux

Linux is different from Windows in that the linker has to be told what shared libraries the executable wants to use. There is a utility, pythonx.y-config, that will tell you how to configure the compiler and linker. The x and y are the version numbers and the documentation says that it isn’t guaranteed to work, but in practice it works for standard Linux. To find the linker flags required for embedding using Python3.11 you can use:

python3.11-config --ldflags --embed

which produces:

-L/usr/local/lib/python3.11/
config-3.11-arm-linux-gnueabihf -L/usr/local/lib -lpython3.11
-lpthread -ldl -lutil -lm

You can also use:

python3.11-config --cflags –embed

to find the compiler flags, but these are more a matter of personal preference and the standard flags work well.

The linker also needs to know about symbols in the libpythonx,y.so file and this needs the additional linker options:

-Xlinker -export-dynamic

 Putting this together gives a basic tasks.json file:

{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: gcc-10 build active file",
"command": "/usr/bin/gcc-10",
"args": [
"-fdiagnostics-color=always",
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}",
"-I/usr/local/include/python3.11",
"-L/usr/local/lib/python3.11/
config-3.11-arm-linux-gnueabihf",
"-L/usr/local/lib",
"-Xlinker",
"-export-dynamic",
"-lpython3.11",
"-lpthread",
"-ldl", "-lutil", "-lm", ], "options": { "cwd": "${fileDirname}" }, "problemMatcher": [ "$gcc" ], "group": { "kind": "build", "isDefault": true }, "detail": "Task generated by Debugger."
} ], "version": "2.0.0" }

With this modification you can run and debug an embedded program in the usual way.

Adding A Module

To interact with the interpreter you have to modify its configuration and the most usual way of doing this is to add a module, or more generally an object. You can create a module in the same way as always – the only difference is that you have to inform the interpreter that it exists before a program can import it.

For example, the function that computes Pi can be added in much the same way as for an extension:

#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject *Pi(PyObject *self, PyObject *args)
{
  int m, n;
  double pi, s;
  if (!PyArg_ParseTuple(args, "ii", &m, &n))
    return NULL;
  pi = 0;
  for (int k = m; k < n; k++)
  {
    s = 1;
    if (k % 2 == 0)
      s = -1;
    pi = pi + s / (2 * k - 1);
  }
  return PyFloat_FromDouble(4 * pi);
};
static PyMethodDef AddMethods[] = {
    {"myPi", Pi, METH_VARARGS, "Compute Pi"},
    {NULL, NULL, 0, NULL} // sentinel
};
static struct PyModuleDef addmodule = {
    PyModuleDef_HEAD_INIT,
    "Pi",
    "C library to compute Pi",
    -1,
    AddMethods};
PyMODINIT_FUNC PyInit_Pi(void)
{
  return PyModule_Create(&addmodule);
};
int main(int argc, char *argv[])
{
  PyImport_AppendInittab("Pi", &PyInit_Pi);
  Py_Initialize();
  PyRun_SimpleString("import Pi\n"
    "print(Pi.myPi(1,1000))\n");
  Py_FinalizeEx();
  return 0;
}

You can see that the only real difference is that we have to use the AppendInittab function to add the module to the list of built-in modules before we call PyInitialize. The program that is run still has to import the module before making use of it.

You can add any module in this way and make its attributes, including classes, available to the Python program that runs as part of the embedded system.



Last Updated ( Tuesday, 13 August 2024 )