All examples in this guide are demonstrated on a Linux environment.
Why Bridge Python and C?
While low-level systems are often written in C or C++, Python is frequently used for scripting, testing, and rapid development. To leverage existing C libraries in Python, we need a mechanism to interface between the two languages. Python's built-in ctypes library serves as the "glue" for this purpose.
Understanding ctypes
ctypes is Python's foreign function library. It provides C-compatible data types and allows calling functions in dynamic link libraries (DLLs) or shared objects (SOs).
Creating the Shared Library
Before Python can interact with C code, the C source must be compiled into a shared library. On Linux, this is typically a .so file.
gcc -shared -fPIC -o libmathtools.so mathtools.c
-shared -fPIC: Flags required to generate position-independent code for a shared library.-o libmathtools.so: Specifies the output filename.
Loading the Library in Python
Once compiled, the library can be loaded using ctypes. The method depends on the operating system and the calling convention.
import ctypes
# Linux/macOS (通常使用 C calling convention)
math_lib = ctypes.cdll.LoadLibrary("./libmathtools.so")
# Windows (通常使用 stdcall for system DLLs, though cdll is also common)
# math_lib = ctypes.windll.LoadLibrary("./libmathtools.dll")
The varible math_lib now acts as an interface to the functions defined in the shared object.
Basic Example: Hello World
Let's start with a simple function that takes no arguments and returns nothing.
mathtools.c:
#include <stdio.h>
void greet_user(void) {
printf("Hello from C!\n");
}
script.py:
from ctypes import *
lib = cdll.LoadLibrary("./libmathtools.so")
lib.greet_user()
Handling Return Values and Arguments
The complexity arises when functions return values or accept specific data types. Python must be told how to map these types.
Type Mapping
ctypes provides specific types (e.g., c_int, c_char_p) that mirrror C types. You must define the restype (return type) and argtypes (argument types) for the C function you wish to call.
Example with Integer Return
Consider a C function that performs a calculation and returns a result.
mathtools.c:
#include <stdio.h>
int calculate_value(int input) {
printf("Processing: %d\n", input);
return input + 10;
}
script.py:
from ctypes import *
lib = cdll.LoadLibrary("./libmathtools.so")
# Define the return type and argument types explicitly
lib.calculate_value.restype = c_int
lib.calculate_value.argtypes = [c_int]
result = lib.calculate_value(32)
print(f"Result from C: {result}")
print(f"Type in Python: {type(result)}")
Output:
Processing: 32
Result from C: 42
Type in Python: <class 'int'>
Character and String Handling
Note that c_char in Python is treated as a single-character byte string (bytes), not a Python string object.
mathtoools.c:
char get_char_code(int val) {
return (char)(val + 1);
}
script.py:
lib.get_char_code.restype = c_char
lib.get_char_code.argtypes = [c_int]
# Passing 48 (ASCII '0'), expecting 49 (ASCII '1')
ret_val = lib.get_char_code(48)
print(ret_val) # Output: b'1' (bytes object)
Advanced Data Structures
When dealing with pointers, arrays, or structs, the mapping becomes more verbose.
Structures and Pointers
To use a C struct in Python, you must define a class that inherits from ctypes.Structure and map the fields.
mathtools.c:
#include <string.h>
typedef struct {
int id;
float rate;
char label[20];
} DataPacket;
DataPacket* update_packet(DataPacket* pkt) {
pkt->rate = 99.9;
strcpy(pkt->label, "Updated");
return pkt;
}
script.py:
from ctypes import *
lib = cdll.LoadLibrary("./libmathtools.so")
class DataPacket(Structure):
_fields_ = [("id", c_int),
("rate", c_float),
("label", c_char * 20)]
# Configure the function signature
lib.update_packet.restype = POINTER(DataPacket)
lib.update_packet.argtypes = [POINTER(DataPacket)]
# Create an instance
packet = DataPacket(id=1, rate=10.5)
# Pass by reference using byref() or pointer()
updated_pkt_ptr = lib.update_packet(byref(packet))
# Access contents
print(f"ID: {updated_pkt_ptr.contents.id}")
print(f"Rate: {updated_pkt_ptr.contents.rate}")
print(f"Label: {updated_pkt_ptr.contents.label.decode('utf-8')}")
Implementing Callbacks (Function Pointers)
If a C library expects a function pointer (callback), you can define the prototype using CFUNCTYPE.
mathtools.c:
typedef void (*LogFunc)(const char*);
void process_with_log(LogFunc log_fn, int value) {
// Simulate work
if (log_fn) {
log_fn("Processing complete");
}
}
script.py:
from ctypes import *
lib = cdll.LoadLibrary("./libmathtools.so")
# Define the C function prototype: void return, const char* argument
LOG_CALLBACK = CFUNCTYPE(None, c_char_p)
def python_logger(message):
print(f"[Python Log] Received: {message.decode('utf-8')}")
# Wrap the Python function
callback_instance = LOG_CALLBACK(python_logger)
# Set argtypes for safety
lib.process_with_log.argtypes = [LOG_CALLBACK, c_int]
# Call C function
lib.process_with_log(callback_instance, 100)