Extending & Embedding Python Using C - A Module Using Windows
Written by Mike James   
Wednesday, 08 January 2025
Article Index
Extending & Embedding Python Using C - A Module Using Windows
Hello World
Testing

Hello World

To create a simple example, first select the File Explorer and click the Open Folder button, navigate to a suitable location and, using right-click and New, create a new folder called CProjects and open it. Create another folder called HelloWorld inside it. You don’t have to organize your projects in this way, but it is easier if each C program has its own folder within a top-level workspace folder:

VSC2

Finally create a file called hello.c in the HelloWorld folder and enter:

#include <stdio.h>
int main(int argc, char **argv)
{ printf("Hello C World\n"); return 0; }

You can compile and run the program by selecting the Run icon in the left hand panel and then selecting the compiler you want to use C++(GDB/LLDB):

debug

The program should be compiled and you will see the message displayed in the terminal window.

After this VS Code will use the details that it has stored in the tasks.json file to implement the build. If things go wrong just delete the file and try running the program again and VS Code will regenerate it for you. tasks.json is an important file because it is where you can specify the command line parameters that are passed to the compiler and the linker and these are how we make the change from building a simple command line program to building a Python extension.

Building a Python Extension

Now that we have checked that VS Code and MSVC are working it is time to move on to build a Python extension. Rather than spending time explaining how a Python extension works we will use a very basic “hello extension” program which simply adds two numbers together and returns the result. Exactly how this extension works is explained in Chapter 4. For the moment we simply use it as a proof that our build process works.

Open a new folder called arithmodule and create a file called arith.c containing the code listed below. The name you give the folder and the file is fairly irrelevant, but it helps to keep it meaningful. You can type the code in as listed or you can copy and paste from the book’s webpage at www.iopress.info:

#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject * add(PyObject *self, PyObject *args)
{
    int x, y, sts;
    if (!PyArg_ParseTuple(args, "ii", &x, &y))
        return NULL;
    sts = x+y;
    return PyLong_FromLong(sts);
}
static PyMethodDef AddMethods[] = {
    {"add", add, METH_VARARGS, "add two numbers"},
    {NULL, NULL, 0, NULL} // sentinel
};
static struct PyModuleDef addmodule = {
  PyModuleDef_HEAD_INIT,
  "arith",                              
  "C library for sum",  
  -1,                                   
  AddMethods                          
};
PyMODINIT_FUNC PyInit_arith(void) {    
     return PyModule_Create(&addmodule);
}

This creates an extension that has a single function sum(x,y) which returns the sum of x and y. The code sets the module name to arith and the name of the function to sum. Notice that it is the code that determines these names, not the names of the folders and files used for the project.

The first problem we have is that we need to include the header file Python.h and this isn’t stored on the standard header file path. With VS Code there are generally two ways to set an include path – one for the compiler and one for Intellisense. If you set the correct paths for the compiler the code will compile and hopefully work, but Intellisense will still show nonexistent errors in the editors if it can’t find the include files. Ideally you need to set both, but only the compiler is essential.

If you have installed Python Windows will have the development libraries already installed. Python is installed in either:

C:\Pythonxy

or

C:\Users\username\AppData\Local\Programs\Python\Pythonxy

where x and y are the major and minor version numbers.

To set the include file for the compiler we have to edit the tasks.json file to indicate where the header is stored. You can set the location of the include files using an environment variable but it is simpler and more direct to add it as an option to the MSVC compiler args in tasks.json:

"/IC:/Users/user/AppData/Local/Programs/Python/
Python311/include",

To generate a DLL shared library we also need to add:

"/link /dll /OUT:arith.pyd /LIBPATH:C:/Users/user/
AppData/Local/Programs/Python/Python311/libs"

Notice that a DLL compiled for use as a module has the extension .pyd and not the default and more usual .dll and so we have to specify this using the /OUT parameter to the linker. We also have to tell the linker where to look for the Pythonxy.dll file to link with the module as this is not stored on any of the usual DLL paths that the linker searches.

The complete tasks.json is:

{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: cl.exe build active file",
            "command": "cl.exe",
            "args": [
                "/Zi",
                "/EHsc",
                "/nologo",            
                "/IC:/Users/user/AppData/Local/
Programs/Python/Python311/include", "${file}", "/link /dll /OUT:arith.pyd /LIBPATH:C:/Users/user/AppData/ Local/Programs/Python/Python311/libs" ], "options": { "cwd": "${fileDirname}" }, "problemMatcher": [ "$msCompile" ], "group": { "kind": "build", "isDefault": true }, "detail": "Task generated by Debugger." } ], "version": "2.0.0" }


Last Updated ( Wednesday, 08 January 2025 )