How To: Extend Python with C

Sometimes, scripting languages aren’t fast enough to do actions to my liking, but I still need to use that language. Thankfully, Python has a way to make modules in the C language. These modules can not only speed up a program, but also lets one use C functions. This is more of an intermediate topic and requires knowledge of the Python and C languages.

Note: This is kinda long and might be hard to follow (maybe? I don’t know, It all seems right in my head). If I need to clear anything up, just ask in the comments. The full code for this tutorial is on Pastebin
C
Python

Package

To implement C into python, you need the python development package. To install:

Linux:
Use your Distro’s package manager, and install python-dev. I use Ubuntu so I would use:
sudo apt-get install python-dev

Windows:
This should have been with the normal python installation

Let’s get started

C code:

We will create a C module with a function that opens a file, and prints the content. We will need only one parameter, the file name. We will call the module CExten and the function CCat.

#include <Python.h> //Python dev module 

// Define a function
static PyObject* CExten_CCat(PyObject* self, PyObject* args){
     // Your code here
}

Each function you create will return a static PyObject* type and have the parameters of self, and a tuple of arguments, both being PyObject pointers. Historically, the function name will be the module name, an underscore, and the function name. The user won’t see these function names so you can name them whatever (I’ll cover that later).

Now we need to parse the arguments that were passed with the function:

#include <Python.h> //Python dev module 

// Define a function
static PyObject* CExten_CCat(PyObject* self, PyObject* args){
    
   const char* file_path;

    //parsing the arguments (only if needed)
   if(!PyArg_ParseTuple(args, "s", &file_path)){
        return NULL; //send error to the python interpreter
   }
    
   //Function code goes here

}

Using the PyArg_ParseTuple function will take the arguments passed to the function and put them into C-readable type. The first parameter for this function is the tuple of arguments, the second is the conversion type (s for char*, i for int, f for float, etc…). The last args are the variables to store the args in.

Now we can finish this function with the main code.

#include <Python.h> //Python dev module 

// Define a function
static PyObject* CExten_CCat(PyObject* self, PyObject* args){
   
   const char* file_path;
   FILE* fd;
   char chunk[128];

   //parsing the arguments (only if needed)
   if(!PyArg_ParseTuple(args, "s", &file_path)){
      return NULL; //send error to the python interpreter
   }
    
   if((fd = fopen(file_path,"r")) < 0){
      return NULL;
   } 

   while((fgets(chunk,sizeof(chunk),(FILE*)fd)) != NULL){
      printf("%s",chunk);
   }

   printf("\n");
   fclose(fd);
}

Now that we have our function created, we need to create the Method definition struct and the module init function. This will let the Python interpreter import our module and call the functions inside the module.


static PyMethodDef CExten_methods[] = {
    //Python Func Name    C Func Name    Arg Count        Description
    {"CCat",         CExten_CCat,        METH_VARARGS,    NULL},
    {NULL,NULL,0,NULL}    //ending sentinel
};

PyMODINIT_FUNC initCExten(void){
    PyObject *m;
    m = Py_InitModule("CExten",CExten_methods);
    if(m == NULL){
        return; 
    }
}

The PyMethodDef struct and the init function have VERY strict naming rules. The struct must have the module name (case sensitive), followed by ‘_methods’ while the init function must be named ‘init’ with the module name (case sensitive).

Inside the methods struct, you will need four parameters for each function. The first is the name of the function in C and the second is the name of the function for Python. The interpreter takes the second param as the function name, and the first param as the function to execute. So the one using the module will never see the names of the functions in C. The third param is the arguments and the only two options that go in this section is METH_VARARGS, and METH_NOARGS. The last param can hold a string that describes the function or can simply be NULL. The struct ends with an ending sentinel statement of 3 NULL values, and a 0.

Finally, the function that init’s the module. First, a PyObject pointer is defined to store the value of the Py_InitModule function which takes the parameters of the (case-sensitive) module name and the methods struct.

Now that the module is finished, we need a setup script to get the python interpreter to read the C code.

#!/usr/bin/python

from distutils.core import setup, Extension

module = Extension("CExten",
             sources = ["CExtenmodule.c"])

setup(name="CExten",
       description="C extension module for Python",
       ext_modules=[module])

Save the python code in a file called setup.py in the same directory as your C module code.

Now, from a terminal, navigate to the directory where you saved your module and your setup script. To build and install the module, use the following command:
python setup.py build && sudo python setup.py install

Now you should have a Python module written in C. To check, open up the python interactive console by typing “python” into the terminal and import CExten. If no error occur, you have a successful installation. If you do get errors, make sure you have all the necessary functions and methods and have installed it correctly.

Also, I have not tested this on Windows, but the process should be close to the same, but if not, let me know and I’ll correct it!

~ JINX

8 Likes

This is great. We could even combine this with something like @0x00pf’s ELF Injector to make a simple Python-based GUI.

2 Likes

Thank you for this how-to! Can this also be done with C++ instead of C?

From what I can tell, you can use C++ with this also, but I haven’t given it a real thorough test. If anyone would like to give it a try, send your results! :slight_smile:

Nice post…
It is funny that I was working in something similar for a different language.

1 Like

That is a great idea, oaktree! @0x00pf, would it be okay if I used your code to make a ELF Injector with a GUI? With credit given, of course.

1 Like

@0x00_Jinx, it is GPL do whatever you want with it. This is what it is intended for. I will be glad to see it is useful to other people!

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.