28.4.6.1.Making a Windows executable¶
On Windows, registration of the .pyz extension is optional, and
furthermore, there are certain places that don’t recognise registered
extensions “transparently” (the simplest example is that
subprocess.run(['myapp']) won’t find your application - you need to
explicitly specify the extension).
On Windows, therefore, it is often preferable to create an executable from the
zipapp. This is relatively easy, although it does require a C compiler. The
basic approach relies on the fact that zipfiles can have arbitrary data
prepended, and Windows exe files can have arbitrary data appended. So by
creating a suitable launcher and tacking the .pyz file onto the end of it,
you end up with a single-file executable that runs your application.
A suitable launcher can be as simple as the following:
#define Py_LIMITED_API 1
#include "Python.h"
#define WIN32_LEAN_AND_MEAN
#include
#ifdef WINDOWS
int WINAPI wWinMain(
HINSTANCE hInstance, /* handle to current instance */
HINSTANCE hPrevInstance, /* handle to previous instance */
LPWSTR lpCmdLine, /* pointer to command line */
int nCmdShow /* show state of window */
)
#else
int wmain()
#endif
{
wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
myargv[0] = __wargv[0];
memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
return Py_Main(__argc+1, myargv);
}
If you define the WINDOWS preprocessor symbol, this will generate a
GUI executable, and without it, a console executable.
To compile the executable, you can either just use the standard MSVC
command line tools, or you can take advantage of the fact that distutils
knows how to compile Python source:
>>>from distutils.ccompiler import new_compiler
>>>import distutils.sysconfig
>>>import sys
>>>import os
>>>from pathlib import Path
>>>def compile(src):
>>> src = Path(src)
>>> cc = new_compiler()
>>> exe = src.stem
>>> cc.add_include_dir(distutils.sysconfig.get_python_inc())
>>> cc.add_library_dir(os.path.join(sys.base_exec_prefix, 'libs'))
>>> # First the CLI executable
>>> objs = cc.compile([str(src)])
>>> cc.link_executable(objs, exe)
>>> # Now the GUI executable
>>> cc.define_macro('WINDOWS')
>>> objs = cc.compile([str(src)])
>>> cc.link_executable(objs, exe + 'w')
>>>if __name__ == "__main__":
>>> compile("zastub.c")
The resulting launcher uses the “Limited ABI”, so it will run unchanged with
any version of Python 3.x. All it needs is for Python (python3.dll) to be
on the user’s PATH.
For a fully standalone distribution, you can distribute the launcher with your
application appended, bundled with the Python “embedded” distribution. This
will run on any PC with the appropriate architecture (32 bit or 64 bit).