Skip to content
Menu
Klein Embedded
  • About
Klein Embedded
January 31, 2023

Calling C code from Python

The majority of all firmware is written in C (or C++), but for writing small utility and automation scripts on the host machine, Python is usually the go-to language. I was recently presented with the problem of running some of our firmware algorithms on a set of pre-recorded data, as part of our suite of automation scripts written in Python. Instead of translating the C code to Python – which could quickly become a maintenance nightmare – I opted for creating an interface so the C code could be called directly from a Python script. Luckily, Python has plenty of support for this.

Python bindings

To create Python bindings for C or C++ code, there a several packages to choose from such as ctypes, CFFI, Cython, PyBind11, Boost.Python and more. You can try these out for yourself, but I am going to keep it simple and use the built-in ctypes package.

The steps required for executing a C function in Python is as follows:

  • Load a dynamic-link library (DLL) with the function you need.
  • Specify the return type and the argument types.
  • Call the function with the correct data types.

So, before writing any Python code, let us start by making a simple C library.

Compiling a C function into a dynamic-link library with CMake

As a basic example, we will create a library that contains just a single function, scale(), that simply scales an array of numbers by a given scaling factor. It might look something like this:

#ifndef SCALE_H_
#define SCALE_H_

#include <stdint.h>

void scale(float *source, float *destination, uint32_t length, float scaling_factor);

#endif // SCALE_H_

and implemented like this:

#include "scale.h"

void scale(float *source, float *destination, uint32_t length, float scaling_factor)
{
    for (uint32_t i = 0; i < length; i++)
    {
        destination[i] = source[i] * scaling_factor;
    }
}

Now we will create a CMakeLists.txt to build the shared library:

cmake_minimum_required (VERSION 3.24.1)
project(scale)

set(SOURCES
    scale.c
)

add_library(scale SHARED ${SOURCES})

Then build the DLL by first running cmake -Bbuild -G"MSYS Makefiles" (I am using MSYS on Windows, but use whatever generator you have, of course) and then cmake --build build. We should end up with a libscale.dll in the build folder which we are going to use in the Python script.

Calling the C code from Python using ctypes

Now, let us create a Python script scale.py. Following the steps listed previously, we come up with the following script:

import ctypes
import pathlib

if __name__ == '__main__':

    # Import the library
    lib_path = pathlib.Path("build/libscale.dll")
    dll = ctypes.CDLL(str(lib_path.resolve()))

    # Specify return type and argument types
    dll.scale.restype = None  # Function returns void
    dll.scale.argstype = [ctypes.POINTER(ctypes.c_float),
                          ctypes.POINTER(ctypes.c_float),
                          ctypes.c_uint32,
                          ctypes.c_float]

    # Create arbitrary input and allocate memory for the output
    input = [1,2,3,4,5]
    scaling_factor = 10
    output = (ctypes.c_float * len(input))()

    # Call the function with the arguments correctly casted to ctypes
    dll.scale((ctypes.c_float * len(input))(*input),
              output,
              len(input),
              ctypes.c_float(scaling_factor))
    

    # Cast the output back to a Python list and print the results
    output = list(output)
    print(output)

Now when running the script, you should see the output printed as the input array scaled by 10:

[10.0, 20.0, 30.0, 40.0, 50.0]

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Subscribe to the newsletter

Get notified by email when a new blog post is published.

Check your inbox or spam folder to confirm your subscription.

Recent Posts

  • STM32 without CubeIDE (Part 4): CMake, FPU and STM32 libraries
  • Adding right-click context menu items in Windows 10
  • CI/CD with Jenkins and Docker
  • STM32 without CubeIDE (Part 3): The C Standard Library and printf()
  • Understanding the (Embedded) Linux boot process

Recent Comments

  1. john on STM32 without CubeIDE (Part 4): CMake, FPU and STM32 libraries
  2. Kristian Klein-Wengel on STM32 without CubeIDE (Part 2): CMSIS, make and clock configuration
  3. Nora on STM32 without CubeIDE (Part 2): CMSIS, make and clock configuration
  4. Kristian Klein-Wengel on STM32 without CubeIDE (Part 3): The C Standard Library and printf()
  5. Milos on STM32 without CubeIDE (Part 3): The C Standard Library and printf()

Archives

  • October 2025
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • June 2022
  • May 2022
  • April 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021

Categories

  • C++
  • DevOps
  • DSP
  • Electronics
  • Embedded C
  • Embedded Linux
  • Firmware
  • Project
  • Python
  • Software Design
  • Testing
  • Tutorial
  • Uncategorized
©2025 Klein Embedded