1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
# Write your first Python module in C
Something I may want to do in the near future is making a tool /
library I am developing in C available in Python. I know it is
possible, but I have never done before, so yesterday I wrote
[my first Python module in C](https://git.tronto.net/python-c),
just to see how hard it is.
The answer is: it's easy, but the documentation is not great. The
[official guide](https://docs.python.org/3/extending/extending.html)
is quite lengthy, but somehow it does not even explain how to build
the damn thing! Don't get me wrong, I am a fan of the "theory first"
approach, but at some point I would expect a code snippet and a
command that I can just copy-paste on my terminal and see everything
work. Nope!
So here is my step-by-step tutorial. You can find all the code in
[my git repository](https://git.tronto.net/python-c).
## 1. Write your C library
Well I guess this is actually step zero, but today we are 1-based.
My beautiful library consists of only one source file `sum.c`:
```
int sum(int a, int b) { return a+b; }
```
and one header file `sum.h`:
```
int sum(int, int);
```
## 2. The adapter code
As you may expect, you need some kind of glue code between the raw
C library and the Python interpreter. This is largely boilerplate.
I put mine in a file called `sum_module.c`.
First you include `Python.h` and your library header:
```
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "sum.h"
```
(I am not sure what the `PY_SSIZE_T_CLEAN` macro does, but the official
tutorial suggets defining it, so I have kept it there.)
Then for each of your library's function you need a corresponding
wrapper that takes Python objects, converts them to C objects, calls
the functions and converts the results back:
```
static PyObject *csum(PyObject *self, PyObject *args) {
int a, b, result;
if (!PyArg_ParseTuple(args, "ii", &a, &b))
return NULL;
result = sum(a, b);
return PyLong_FromLong(result);
}
```
The `PyArg_ParseTuple()` function looks a bit magical. It is a
variadic function that takes the Python objects contained in `args`
and converts them following the given pattern - in this case "ii" for
two integers.
Then we need to map the wrapper functions to their python name. Here
I chose to call my function `sum_from_c`:
```
static PyMethodDef SumMethods[] = {
{ "sum_from_c", csum, METH_VARARGS, "Sum two integers." },
{ NULL, NULL, 0, NULL }
};
```
Finally, we just need some more boilerplate for creating the module:
```
static struct PyModuleDef summodule = {
PyModuleDef_HEAD_INIT, "sum", NULL, -1, SumMethods
};
PyMODINIT_FUNC PyInit_sum_module(void) {
return PyModule_Create(&summodule);
}
```
And we are ready to build! Kind of...
## 3. Install the Python development packages
To build the code above you need the `Python.h` header. Depending
on your system, this may be included in the default Python installation
or in a separate package. For example in Void Linux I needed to
install the `pytnon3-devel` package.
Anyway, once you have installed the correct package, you can check
where this library header file is with
```
$ python3-config --includes
```
This command will return a string like `-I/usr/include/python3.12`.
Keep it in mind for the next step!
## 4. Build the damn thing!
First, build the C library code:
```
cc -c -o sum.o sum.c
```
Here `-c` tells the compiler to skip the
[linking](https://en.wikipedia.org/wiki/Linker_(computing)) step -
otherwise it would complain about a missing `main()` function. The
`cc` command should be, on any UNIX system, a link to either `gcc`
or some other C compiler. You can use `gcc` instead, if you prefer.
Then we need to build the adapter code to create the actual module:
```
$ cc -shared -I/usr/include/python3.12 -o sum_module.so sum.o sum_module.c
```
Here the `-shared` option tells the compiler to build a
[shared object](https://en.wikipedia.org/wiki/Shared_library),
the equivalent of a DLL in Windows. This is a compiled library than
can be dynamically loaded into a running program.
## 5. Import and run
And finally, you can open the Python REPL and run your code. From the same
directory where the `sum_module.so` file is:
```
>>> import sum_module
>>> sum_module.sum_from_c(23, 19)
42
```
Enjoy!
## 6. All the rest
There are still a couple of things I need to check before I can
repeat these steps with a more complex library. Namely, I need
convert more complex data types from Python to C, for example some
function pointers that I am using for
[callback](../2024-06-20-callback-log). I'll check again the
official documentation when I get to that point, but for now I am
happy that this simple example works!
|