Question

Calling C or C++ from Python: Building Python Bindings

ccpppython

Question

How can I create a Python binding for a C or C++ library in the quickest practical way?

I want to call functions from an existing C or C++ library in Python. What are the common approaches for doing this, and how does the choice differ for C versus C++?

I am using Windows, so if there are platform-specific considerations for building or loading libraries, those are also relevant.

Short Answer

By the end of this page, you will understand what a Python binding is, the main ways to connect Python with C or C++ code, and when to choose tools such as ctypes, Python C extensions, Cython, or C++ binding libraries like pybind11. You will also see simple examples and learn the trade-offs between speed of setup, ease of maintenance, and control.

Concept

A Python binding is a layer that allows Python code to call functions written in another language, such as C or C++.

This matters because many real-world libraries are written in C or C++ for:

  • performance
  • access to system APIs
  • reuse of existing native code
  • hardware or OS integration

When Python talks to native code, there are two main problems to solve:

  1. How to load or compile the native code
  2. How to convert data between Python objects and C/C++ types

Common approaches

1. ctypes

  • Built into Python
  • Good for calling functions from a compiled C-style shared library (.dll on Windows)
  • Fast to start with
  • Best when the API is simple and uses plain C types

2. Python C extension API

  • Write a module in C or C++ using Python's native extension interface
  • Very powerful
  • More verbose and lower-level
  • Useful when you want full control

3. Cython

  • Lets you write Python-like code that wraps C/C++
  • Easier than writing raw C extensions for many cases
  • Good balance of performance and productivity

4. C++ binding libraries such as pybind11

  • Designed specifically for exposing C++ classes and functions to Python
  • Much easier than using the raw Python C API directly for C++
  • Very common in modern C++ projects

Why C and C++ are different

A C library usually exposes plain functions like this:

int add(int a, int b);

This is straightforward to call from Python.

A C++ library may expose:

  • classes
  • method overloading
  • templates
  • exceptions
  • name mangling

That makes wrapping C++ directly more complex. Tools like pybind11 or Cython help manage that complexity.

Windows note

On Windows, native libraries are usually loaded from .dll files. You may also need:

  • a compiler compatible with your Python build
  • correct architecture match: 64-bit Python with 64-bit DLLs
  • exported symbols from the DLL
  • proper handling of calling conventions in some cases

So the quickest option depends on your library:

  • If it is a simple C API: use ctypes
  • If it is a C++ library with classes: use pybind11
  • If you need more custom wrapping logic: use Cython or a C extension

Mental Model

Think of Python as a receptionist who speaks Python, and your C or C++ library as an engineer who speaks another language.

A binding is the interpreter between them.

  • Python says: "Call add(2, 3)"
  • The binding translates that into a native call the library understands
  • The library returns a result
  • The binding translates it back into a Python value

Different tools are different kinds of interpreters:

  • ctypes is like using a phrasebook for simple conversations
  • Cython is like hiring a trained bilingual assistant
  • pybind11 is like having a specialist for C++ conversations
  • the Python C API is like doing the translation manually yourself
Take Quiz

Syntax and Examples

Option 1: Call a C library with ctypes

Suppose a DLL exports this C function:

int add(int a, int b);

You can call it from Python like this:

import ctypes

lib = ctypes.CDLL("mylib.dll")

lib.add.argtypes = [ctypes.c_int, ctypes.c_int]
lib.add.restype = ctypes.c_int

result = lib.add(2, 3)
print(result)  # 5

What this does

  • CDLL loads the DLL
  • argtypes tells Python what argument types the function expects
  • restype tells Python what type comes back

This is often the quickest route for a simple C API.

Option 2: Expose C++ with pybind11

C++ code:

#include <pybind11/pybind11.h>

int add(int a, int b) {
    return a + b;
}

namespace py = pybind11;

PYBIND11_MODULE(example, m) {
    m.def("add", &add, "Add two integers");
}

Python usage:

import example

print(example.add(2, 3))  # 5

Why this is useful

pybind11 handles much of the work of converting Python values to C++ values and back.

Option 3: Wrap with Cython

Cython file:

cdef extern from "mylib.h":
    int add(int a, int b)

def py_add(int a, int b):
    return add(a, b)

Python usage after building:

import mywrapper

print(mywrapper.py_add(2, 3))

When to use this

Cython is great when you want to stay close to Python syntax while still working with C or C++ code.

Which example should a beginner choose?

  • Choose ctypes for a simple existing C DLL
  • Choose pybind11 for C++ classes and functions
  • Choose Cython when you want more control but easier syntax than the raw C API

Step by Step Execution

Consider this Python example using ctypes:

import ctypes

lib = ctypes.CDLL("mylib.dll")
lib.add.argtypes = [ctypes.c_int, ctypes.c_int]
lib.add.restype = ctypes.c_int

answer = lib.add(10, 20)
print(answer)

Step by step

1. Import ctypes

import ctypes

This gives Python tools for working with shared libraries and C-compatible data types.

2. Load the DLL

lib = ctypes.CDLL("mylib.dll")

Python loads the compiled native library into memory.

3. Define the parameter types

lib.add.argtypes = [ctypes.c_int, ctypes.c_int]

This tells Python that add expects two C integers.

4. Define the return type

lib.add.restype = ctypes.c_int

This tells Python to interpret the result as a C integer.

5. Call the native function

answer = lib.add(10, 20)

Python passes 10 and 20 into the DLL function.

Inside the C library, this is effectively:

int result = add(10, 20);

6. Convert the result back to Python

The returned C integer becomes a Python integer value.

7. Print the result

print(answer)

Output:

30

Important detail

If argtypes or restype are wrong, you may get:

  • incorrect values
  • crashes
  • memory corruption

That is why bindings must match the native function signature exactly.

Real World Use Cases

Python bindings to C or C++ are widely used in practice.

Common scenarios

Using a high-performance library

A team has a fast image-processing library in C++ and wants Python scripts to use it for automation.

Reusing existing native code

A company already has a mature C library for device communication and wants a Python interface instead of rewriting it.

Accessing operating system or hardware APIs

Some APIs are easier or only practical to access through native libraries.

Scientific and numeric computing

Many scientific Python tools rely on C or C++ under the hood for speed.

Game tools and simulation

A simulation engine may be written in C++, while Python is used for scripting, testing, or configuration.

Practical examples

  • wrapping a compression library so Python can compress files faster
  • calling a native DLL that talks to industrial hardware
  • exposing a C++ geometry engine to Python for data analysis
  • using a native parser in a Python command-line tool
Take Quiz

Real Codebase Usage

In real projects, developers usually do not expose every internal native function directly. They build a small, clean Python-facing API.

Common patterns

Thin wrapper layer

A wrapper translates between Python-friendly values and lower-level native calls.

Example idea:

# Python-facing API
result = image.resize(width=200, height=100)

Behind the scenes, the binding may call several native functions.

Validation before native calls

Developers often validate input in Python before calling C/C++.

def safe_add(lib, a, b):
    if not isinstance(a, int) or not isinstance(b, int):
        raise TypeError("a and b must be integers")
    return lib.add(a, b)

This prevents confusing low-level errors.

Error translation

Native code may use error codes or exceptions. Good bindings convert these into Python exceptions.

  • C error code -> RuntimeError or custom exception
  • C++ exception -> Python exception through a binding library

Stable public API

Even if the native implementation changes, the Python API is kept stable for users.

Small C API in front of C++

In many codebases, developers create a simple extern "C" layer around C++ code and call that from Python tools like ctypes.

This can simplify integration a lot.

Why this matters

A real binding is not just about making one function callable. It is about making the native library feel natural and safe to use from Python.

Common Mistakes

1. Using the wrong tool for the job

Trying to wrap a complex C++ class library with ctypes is usually painful.

Better approach

  • Use ctypes for simple C APIs
  • Use pybind11 or Cython for C++

2. Forgetting to match types exactly

Broken example:

import ctypes

lib = ctypes.CDLL("mylib.dll")
lib.add.argtypes = [ctypes.c_double, ctypes.c_double]  # wrong
lib.add.restype = ctypes.c_int

If the real function expects int, this mismatch can cause incorrect behavior.

3. Ignoring 32-bit vs 64-bit compatibility

A 64-bit Python interpreter cannot load a 32-bit DLL, and vice versa.

Avoid this by checking

  • Python architecture
  • compiler target
  • DLL architecture

4. Forgetting symbol export details on Windows

A function must be exported from the DLL or Python cannot find it.

For C, this may involve export declarations. For C++, name mangling can also make symbol names harder to use directly.

5. Calling C++ directly without handling name mangling

Broken idea:

lib = ctypes.CDLL("mylib.dll")
lib.MyCppClass_doThing()

C++ function names are often mangled by the compiler.

Better options

  • expose an extern "C" wrapper
  • use pybind11
  • use Cython

6. Not handling memory ownership

If native code allocates memory, your binding must define who frees it.

Otherwise you can cause:

  • memory leaks
  • double free errors
  • crashes

7. Expecting native errors to look like Python errors automatically

C functions often return error codes instead of raising exceptions. You need to check and convert them yourself.

Comparisons

Common options for Python bindings

ApproachBest forEase of setupWorks well with C++ classesControl levelNotes
ctypesSimple C librariesHighLowMediumBuilt into Python; no compile step for the wrapper itself
Python C APICustom native extensionsLowMediumVery highPowerful but verbose
CythonC/C++ wrappers and performance workMediumMedium to highHighPython-like syntax
pybind11Modern C++ bindingsMedium to highHighHighExcellent for exposing C++ classes

ctypes vs pybind11

Featurectypespybind11
Good for prebuilt C DLLsYesNot the main use case
Good for C++ classesNoYes
Requires writing C++ wrapper codeUsually no for CYes
Built into PythonYesNo
Easiest for simple function callsYesNot usually

Rule of thumb

  • Simple C DLL -> ctypes
  • C++ library -> pybind11
  • Need Python-like wrapper code -> Cython
  • Need very low-level control -> Python C API
Take Quiz

Cheat Sheet

Quick decision guide

  • Simple C library already compiled as a DLL -> ctypes
  • C++ classes or methods -> pybind11
  • Python-like wrapper syntax with C/C++ support -> Cython
  • Maximum low-level control -> Python C API

ctypes essentials

import ctypes
lib = ctypes.CDLL("mylib.dll")
lib.func.argtypes = [ctypes.c_int, ctypes.c_int]
lib.func.restype = ctypes.c_int
result = lib.func(1, 2)

Common ctypes types

  • ctypes.c_int
  • ctypes.c_double
  • ctypes.c_char_p
  • ctypes.c_void_p

C++ warning

  • C++ names may be mangled
  • classes are not easy to call directly via ctypes
  • prefer pybind11 or Cython

Windows reminders

  • use .dll
  • match 32-bit or 64-bit builds
  • make sure symbols are exported
  • ensure Python can find the DLL

Safety rules

  • always set argtypes
  • always set restype
  • validate input before native calls
  • define memory ownership clearly

FAQ

What is a Python binding?

A Python binding is code that lets Python call functions or classes written in another language, usually C or C++.

What is the easiest way to call a C library from Python?

For a simple C library with exported functions, ctypes is often the easiest option.

Can Python call C++ directly with ctypes?

Not comfortably. ctypes works best with C-style exported functions, not complex C++ classes or overloaded methods.

What should I use for C++ bindings in Python?

pybind11 is one of the most common and beginner-friendly choices for modern C++ bindings.

Do I need to recompile the C or C++ library?

Not always. If you already have a suitable shared library with a simple C API, you may be able to use it directly with ctypes. For C++ bindings, you often build a wrapper module.

Why does Windows matter for Python bindings?

Windows uses DLLs, and you must match Python's architecture and ensure the DLL exports the required symbols.

Is Cython only for optimization?

No. It is also a practical tool for wrapping C or C++ code and creating Python extensions.

What if my C++ library is too complex to expose directly?

A common approach is to write a simpler C API wrapper around the C++ code, then bind that API to Python.

Take Quiz

Mini Project

Description

Build a tiny Python wrapper around a native C-style add function using ctypes. This project demonstrates the basic binding workflow: load a DLL, describe the function signature, call it from Python, and return a Python value. Even though the example is small, it mirrors the same idea used when connecting Python to larger native libraries.

Goal

Create a Python script that loads a native library and successfully calls an add(a, b) function from Python.

Requirements

  • Load a native library from Python using ctypes
  • Define the argument and return types for one exported function
  • Call the native function with two integers
  • Print the returned result
  • Add a small Python wrapper function for cleaner usage
Take Quiz

Keep learning

Related questions

View more