pythoncpython-3.xcython

How to set up a simple hello-world example where a C function calls a Cython function calling a Python function?


I am having trouble making a simple "Hello World" python function which I can call from a C program.

Here is the contents of my helloworld.py file:

def hw():
  print("Hello World")

Here is the contents of the caller.pyx file:

from helloworld import hw

cdef public void call_hw():
  hw()

And here is the contents of my main.c file:

#include <Python.h>
#include "caller.h"

int
main()
{
  Py_Initialize();
  call_hw();
  Py_Finalize();
}

Here are the commands I do:

$ cython caller.pyx
$ gcc -g -Wall -I/usr/include/python3.12 -c caller.c
$ gcc -g -Wall -I/usr/include/python3.12 -c main.c
$ gcc -g -Wall -I/usr/include/python3.12 -o main *.o -lpython3.12
$ ./main
Segmentation fault (core dumped)

Here is a backtrace:

Program received signal SIGSEGV, Segmentation fault.
0x0000555555558898 in __Pyx__GetModuleGlobalName (name=0x0) at caller.c:2739
2739        result = _PyDict_GetItem_KnownHash(__pyx_d, name, ((PyASCIIObject *) name)->hash);
(gdb) bt
#0  0x0000555555558898 in __Pyx__GetModuleGlobalName (name=0x0) at caller.c:2739
#1  0x0000555555556d9a in call_hw () at caller.c:2002
#2  0x000055555555cef6 in main () at main.c:8

Can anyone tell me what I'm doing wrong?


Solution

  • caller is still a Python module, and it needs to be initialised and imported before you can use it or any of its functions. That is, you need to ensure the from helloworld import hw bit of python code is run. See the documentation on embedding for more details. Further documentation on using Cython Declarations from C can be found here.

    The key additions you need are:

    For example, main.c would become:

    #include <Python.h>
    #include <stdio.h>
    #include "caller.h"
    
    int
    main()
    {
        PyObject *pmodule;
        /* Add a built-in module, before Py_Initialize
           PyInit_caller is generated code from caller.c
         * NB.the name will vary with the module name */
        if (PyImport_AppendInittab("caller", PyInit_caller) == -1) {
            fprintf(stderr, "Error: could not extend in-built modules table\n");
            exit(1);
        }
    
        /* Initialize the Python interpreter.  Required.
           If this step fails, it will be a fatal error. */
        Py_Initialize();
    
        /* Optionally set import path here */
        // PyRun_SimpleString("import sys\nsys.path.insert(0,'')");
    
        /* Import the module.
           If this step fails, it will be a fatal error. */
        pmodule = PyImport_ImportModule("caller");
        if (!pmodule) {
            PyErr_Print();
            fprintf(stderr, "Error: could not import module 'caller'\n");
            goto exit_with_error;
        }
    
        /* your code goes here */    
        call_hw();
    
        /* end of program */
        Py_Finalize();
        return 0;
    
        /* Clean up in the error cases above. */
    exit_with_error:
        Py_Finalize();
        return 1;
    }
    

    PYTHONPATH

    However, that is not all you need to do. You will also need to tell the interpreter where it can look when importing modules. By default, the current working directory is added when running normal python scripts. This is not the case for embedded scripts. Instead you will need to manually do this. One simple way is to set the environment variable PYTHONPATH to .. For instance:

    PYTHONPATH=. ./main
    

    Alternatively, you can put the following before your first module import (but after you initialise the interpreter) in main.c:main()

    PyRun_SimpleString("import sys\nsys.path.insert(0,'')");