Skip to content

Ctypes Module

The ctypes module provides C-compatible data types and allows calling functions in DLLs (Windows) or shared libraries (Unix).

Complexity Reference

Operation Time Space Notes
CDLL(name) O(1) O(1) Load library
c_int/c_float/c_char O(1) O(1) Create type
function_call() O(n) O(n) n = arg count
Structure/Union O(1) O(1) Define class

Common Operations

Loading and Calling C Functions

from ctypes import CDLL, c_int, c_double

# O(1) - load standard C library
import ctypes
libc = ctypes.CDLL(None)  # Unix
# or on Windows:
# msvcrt = ctypes.CDLL('msvcrt')

# O(1) - get function reference
sqrt = libc.sqrt

# O(1) - call C function with conversion
result = sqrt(16.0)  # Returns 4.0
print(result)

# With explicit types - O(1) for arg conversion
from ctypes import c_double, CDLL
libc = CDLL('libc.so.6')  # On Linux
sqrt = libc.sqrt
sqrt.argtypes = [c_double]  # O(1)
sqrt.restype = c_double     # O(1)
result = sqrt(16.0)         # O(1) typed call

Working with C Data Types

from ctypes import (
    c_int, c_float, c_char, c_bool,
    c_int32, c_int64, c_uint8
)

# O(1) - create instances
x = c_int(42)           # 32-bit integer
y = c_float(3.14)       # Float
z = c_char(b'A')        # Single char
flag = c_bool(True)     # Boolean

# O(1) - access values
print(x.value)          # 42
print(y.value)          # 3.14

# O(1) - modify values
x.value = 100

Common Use Cases

Calling System Functions

from ctypes import CDLL, c_char_p, c_int
import ctypes

# Unix/Linux example
libc = ctypes.CDLL('libc.so.6')

# O(1) - strlen function
strlen = libc.strlen
strlen.argtypes = [c_char_p]
strlen.restype = c_int

# O(n) where n = string length (C-side)
text = b"Hello"
length = strlen(text)  # O(n)
print(length)  # 5

Creating Structures

from ctypes import Structure, c_int, c_char_p

# O(1) - define C struct equivalent
class Point(Structure):
    _fields_ = [
        ('x', c_int),
        ('y', c_int),
    ]

# O(1) - create instance
p = Point()
p.x = 10  # O(1)
p.y = 20  # O(1)

print(f"Point: ({p.x}, {p.y})")  # O(1)

# O(1) - construct with values
p2 = Point(x=5, y=15)

Passing Structures to C Functions

from ctypes import Structure, c_int, CDLL
import ctypes

class Point(Structure):
    _fields_ = [('x', c_int), ('y', c_int)]

# Assuming C function exists: int distance(Point p);
libc = ctypes.CDLL(None)
distance = libc.distance
distance.argtypes = [Point]  # O(1)
distance.restype = c_int     # O(1)

# O(1) - pass structure
p = Point(x=3, y=4)
dist = distance(p)  # O(1) + C computation

Working with Pointers

from ctypes import c_int, POINTER, pointer

# O(1) - create integer
x = c_int(42)

# O(1) - get pointer
ptr = pointer(x)

# O(1) - dereference pointer
print(ptr.contents.value)  # 42

# O(1) - modify through pointer
ptr.contents.value = 100
print(x.value)  # 100

Arrays in Ctypes

from ctypes import c_int, ARRAY

# O(1) - define array type
IntArray = c_int * 5

# O(n) - create array where n = size
arr = IntArray()

# O(1) - set values
arr[0] = 10
arr[1] = 20

# O(1) - get values
print(arr[0])  # 10

# O(n) - iterate all elements
for i in range(5):
    print(arr[i])

Performance Tips

Cache Library Loading

from ctypes import CDLL

class LibraryCache:
    """Cache loaded libraries - O(1) access"""

    def __init__(self):
        self._libs = {}

    def get_lib(self, name):
        """O(1) cached or O(1) load"""
        if name not in self._libs:
            # O(1) to load DLL/SO
            self._libs[name] = CDLL(name)
        return self._libs[name]

# Usage
cache = LibraryCache()
libc = cache.get_lib('libc.so.6')  # O(1)
libc = cache.get_lib('libc.so.6')  # O(1) - cached

Set argtypes Once

from ctypes import CDLL, c_int, c_double
import ctypes

# Bad: Set argtypes each call (expensive)
libc = ctypes.CDLL('libc.so.6')
pow_func = libc.pow

for i in range(1000):
    pow_func.argtypes = [c_double, c_double]  # O(k) each time
    pow_func.restype = c_double
    result = pow_func(2.0, i)  # O(1)

# Good: Set once - O(k) + O(n) instead of O(n*k)
pow_func = libc.pow
pow_func.argtypes = [c_double, c_double]  # O(k) once
pow_func.restype = c_double

for i in range(1000):
    result = pow_func(2.0, i)  # O(1)

Use by_reference for Large Structures

from ctypes import Structure, c_int, CDLL, byref
import ctypes

class LargeStruct(Structure):
    _fields_ = [('data', c_int * 1000)]

# Bad: Pass by value (copies) - O(n)
func = ctypes.CDLL('libfoo').process
func.argtypes = [LargeStruct]
result = func(large_obj)  # O(n) copy

# Good: Pass by reference - O(1)
func.argtypes = [POINTER(LargeStruct)]
result = func(byref(large_obj))  # O(1) reference

Version Notes

  • Python 2.5+: ctypes available
  • Python 3.x: All features available
  • Windows/Unix: Portable across platforms
  • Python 3.11+: Some optimizations