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

Generated with cgit - Back to sebastiano.tronto.net