How to create C-extension/embed Python with MinGW-w64 on Windows
Is it possible (and how) to use MinGW-w64 for building of C-extensions for Python or embeding Python on Windows?
Let's take as example the following cython-extension foo.pyx
:
print("foo loaded")
from which the C-code can be generated either via cython -3 foo.pyx
or cython -3 --embed foo.pyx
if interpreter should be embedded.
While mingw-w64
-compiler is not really supported (the only supported windows compiler is MSVC), it can be used to create C-extensions or to embed Python. There are however no guarantee, this won't break in the future versions.
distutils
does not support mingw-w64
, so there is no gain in setting up a setup.py-file - the steps must be performed manually.
First we need some information usually provided by distutils
:
- Headers: We need the path to the Python includes. For a way to find them see this SO-post.
-
DLL: mingw-w64's linker works differently than MSVC's: python-dll and not python-lib is needed. So we need the path to the
pythonXY.dll
which is usually next the thepython.exe
.
Once the C-code is created/generated, the extension can be build via
x86_64-w64-mingw32-gcc -shared foo.c -DMS_WIN64 -O2 <other_options> -I <path_to_python_include> -L <path_to_python_dll> -lpython37 -o foo.pyd
The important details are:
- it is probably Ok to use only use
-O2
for optimization and leave<other_options>
empty- - It is important to define
MS_WIN64
-macro (e.g. via-DMS_WIN64
). In order to build for x64 on windows it must be set, but it works out of the box only for MSVC (defining_WIN64
could have slightly different outcomes):
#ifdef _WIN64
#define MS_WIN64
#endif
if it is not done, at least for files generated by Cython the following error message will be generated by the compiler:
error: enumerator value for ‘__pyx_check_sizeof_voidp’ is not an integer constant
201 | enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) };
-
pyd
is just a dll in disguise, thus we need the-shared
option, which means a dynamic library (i.e. shared-object in Linux-world) will be created. -
It is important, that the python-library (
pythonXY
) should be the dll itself and not thelib
(see this SO-post). Thua we use the path topythonXY.dll
(in my case python37) and notpythonXY.lib
, as it would be the case for MSVC.
One probably should add the proper suffix to the resulting pyd-file, I use the old convention for simplicity here.
Embeded Python:
In this case an executable should be build (e.g. the C-file is generated by Cython with --embed
option: cython -3 --embed foo.pyx
) and thus the command line looks as follows:
x86_64-w64-mingw32-gcc foo.c -DMS_WIN64 -O2 <other_options> -I <path_to_python_include> -L <path_to_python_dll> -lpython37 -o foo.exe -municode
There are two important differences:
-
-shared
should no longer be used, as the result is no longer a dynamic library (that is what *.pyd-file is after all) but an executable. -
-municode
is needed, because for Windows, Cython definesint wmain(int argc, wchar_t **argv)
instead ofint main(int argc, char** argv)
. Without this option, an error message like
/build/mingw-w64-_1w3Xm/mingw-w64-4.0.4/mingw-w64-crt/crt/crt0_c.c:18: undefined reference to 'WinMain'
collect2: error: ld returned 1 exit status
would appear (see this SO-post for more information).
Note: for the resulting executable to run, a whole python-distribution (and not only the dll) is needed (see also this SO-post), otherwise the resulting executable will abort with error (either the python dll wasn't found or the python installation or the site packages - depending on the configuration of the machine on which the exe has to run).
mingw-w64
can also be used on Linux for cross-compilation for Windows, see this SO-post.