Python SWIG Interface Generator — Deep Dive
SWIG’s apparent simplicity — point it at a header and get bindings — conceals a sophisticated code generation engine. Production use demands understanding typemaps, ownership semantics, director classes, and build system integration. This guide covers the techniques that separate a working wrapper from a reliable one.
The Typemap System
Typemaps are SWIG’s core mechanism for controlling how data crosses the language boundary. Every type conversion — function arguments, return values, member variables — is governed by a typemap.
Typemap Categories
| Typemap | When It Runs | Purpose |
|---|---|---|
in | Before calling C | Convert Python arg → C parameter |
out | After C returns | Convert C return → Python object |
argout | After C returns | Handle output parameters (e.g., int *output) |
freearg | Cleanup | Free temporary memory allocated during in |
check | After in | Validate converted values |
typecheck | Overload resolution | Determine which overload matches |
Writing Custom Typemaps
Example: wrapping a C function that takes a buffer and length:
// C API
void process_data(const unsigned char* buf, size_t len);
%typemap(in) (const unsigned char* buf, size_t len) {
Py_buffer view;
if (PyObject_GetBuffer($input, &view, PyBUF_SIMPLE) != 0) {
SWIG_fail;
}
$1 = (unsigned char*)view.buf;
$2 = (size_t)view.len;
}
%typemap(freearg) (const unsigned char* buf, size_t len) {
PyBuffer_Release(&view$argnum);
}
This accepts any Python object implementing the buffer protocol (bytes, bytearray, numpy arrays) without copying data.
Multi-Argument Typemaps
SWIG can match patterns across consecutive arguments. The (const unsigned char* buf, size_t len) typemap above matches those two parameters together, which is critical for C APIs that use pointer+length pairs.
Memory Ownership and Lifetime
The most dangerous aspect of SWIG wrappers is getting ownership wrong. C++ and Python have incompatible memory models, and SWIG must be told who owns what.
The %newobject Directive
If a C function returns a heap-allocated object that the caller must free:
%newobject create_widget;
Widget* create_widget(const char* name);
Without %newobject, Python wraps the pointer but never frees it (memory leak). With it, Python’s garbage collector calls delete when the wrapper is collected.
%delobject for Explicit Destruction
For APIs with paired create/destroy functions:
%newobject Database_open;
%delobject Database_close;
Database* Database_open(const char* path);
void Database_close(Database* db);
Preventing Double Frees
When C++ code retains a pointer that SWIG also tracks:
%feature("ref") Texture "Py_INCREF($this);"
%feature("unref") Texture "Py_DECREF($this);"
Or use SWIG_prevent_dangling_pointer patterns to detect when Python’s wrapper outlives the C++ object.
Director Classes: Cross-Language Polymorphism
Directors allow Python subclasses to override C++ virtual methods, enabling callbacks from C++ into Python.
%feature("director") EventHandler;
%inline %{
class EventHandler {
public:
virtual ~EventHandler() {}
virtual void on_event(const std::string& name) = 0;
virtual void on_error(int code) { /* default impl */ }
};
void register_handler(EventHandler* handler);
%}
Python code:
class MyHandler(mylib.EventHandler):
def on_event(self, name):
print(f"Event: {name}")
def on_error(self, code):
logging.error(f"Error code: {code}")
handler = MyHandler()
mylib.register_handler(handler)
When C++ calls handler->on_event("click"), the call routes through SWIG’s director mechanism to the Python method.
Director Pitfalls
- GIL acquisition: Director callbacks must acquire the GIL if called from a non-Python thread. SWIG generates GIL management code automatically when
-threadsis enabled. - Exception translation: Python exceptions in director methods become C++ exceptions. Use
%feature("director:except")to customize this. - Performance: Each director call crosses the C++→Python boundary. For hot paths, consider caching or batching.
Template Instantiation
SWIG cannot wrap C++ templates directly — templates are compile-time constructs with no runtime representation. Instead, you explicitly instantiate the types you need:
%include "std_vector.i"
%include "std_map.i"
%template(IntVector) std::vector<int>;
%template(StringVector) std::vector<std::string>;
%template(StringIntMap) std::map<std::string, int>;
Each %template directive generates a concrete Python class. Without these, template-based APIs are invisible to SWIG.
Smart Pointer Support
%include "std_shared_ptr.i"
%shared_ptr(MyClass)
class MyClass {
public:
void do_work();
};
std::shared_ptr<MyClass> create_instance();
SWIG understands that shared_ptr<MyClass> manages lifetime and adjusts reference counting accordingly.
Build System Integration
CMake
find_package(SWIG REQUIRED)
include(UseSWIG)
find_package(Python3 COMPONENTS Development)
set_property(SOURCE mylib.i PROPERTY CPLUSPLUS ON)
swig_add_library(mylib
TYPE SHARED
LANGUAGE python
SOURCES mylib.i mylib.cpp
)
target_include_directories(mylib PRIVATE ${Python3_INCLUDE_DIRS})
target_link_libraries(mylib Python3::Module)
setuptools
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
import subprocess
class SwigBuildExt(build_ext):
def build_extensions(self):
subprocess.check_call([
'swig', '-python', '-c++', '-o', 'mylib_wrap.cxx', 'mylib.i'
])
super().build_extensions()
setup(
ext_modules=[Extension(
'_mylib',
sources=['mylib_wrap.cxx', 'mylib.cpp'],
)],
cmdclass={'build_ext': SwigBuildExt},
)
Thread Safety
Enable SWIG’s thread support with the -threads flag:
swig -python -c++ -threads mylib.i
This wraps every C function call with Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS, releasing the GIL during native execution. For director callbacks, it automatically acquires the GIL.
Selective threading control:
%nothread; // Disable GIL release globally
%thread some_function; // Enable GIL release for specific functions
void some_function(); // Will release GIL
void other_function(); // Will NOT release GIL
Exception Handling
Map C++ exceptions to Python exceptions:
%exception {
try {
$action
} catch (const std::invalid_argument& e) {
PyErr_SetString(PyExc_ValueError, e.what());
SWIG_fail;
} catch (const std::runtime_error& e) {
PyErr_SetString(PyExc_RuntimeError, e.what());
SWIG_fail;
} catch (...) {
PyErr_SetString(PyExc_Exception, "Unknown C++ exception");
SWIG_fail;
}
}
Without this, uncaught C++ exceptions crash the Python interpreter.
Performance Considerations
- Proxy overhead: SWIG generates a Python shadow class for each C++ class. Attribute access goes through
__getattr__, adding latency. For hot paths, use-builtinmode which creates CPython types directly. - String copies:
std::string↔ Pythonstralways copies data. For large strings, use buffer-based typemaps. - Overload resolution: C++ function overloading requires SWIG to try each signature in order. Many overloads slow down dispatch. Use
%renameto give overloads distinct Python names.
# Enable builtin mode for faster attribute access
swig -python -c++ -builtin mylib.i
Debugging SWIG Wrappers
swig -E mylib.i— Preprocess only; shows what SWIG sees after macro expansion.swig -debug-typemap mylib.i— Shows which typemaps match each parameter.swig -xmlout dump.xml mylib.i— Full parse tree in XML for deep inspection.
One Thing to Remember
SWIG’s power lies in its typemap engine and multi-language code generation. For large legacy C/C++ codebases, it remains the fastest path from native headers to working Python bindings — but production quality demands explicit ownership directives, careful template instantiation, and exception translation.
See Also
- Python Boost Python Bindings Boost.Python lets C++ code talk to Python using clever C++ tricks, like teaching two people to understand each other through a shared phrasebook.
- Python Buffer Protocol The buffer protocol lets Python objects share raw memory without copying, like passing a notebook around the table instead of photocopying every page.
- Python Capsule Api Python Capsules let C extensions secretly pass pointers to each other through Python, like friends passing a sealed envelope through a mailbox.
- Python Cffi Bindings CFFI lets Python talk to fast C libraries, like giving your app a translator that speaks both languages at the same table.
- Python Extension Modules Api The C Extension API is how Python lets you plug in hand-built C code, like adding a turbo engine under your Python program's hood.