Embed Python 3.5 with tkinter support on Windows
Disclaimer
This answer does not purport to be the correct or best way to embed Python 3.5 with Tkinter support. The step-by-step format only reflects the fact that this was how I managed to get everything working on my machine, and since I am unable to test this solution elsewhere, I cannot confirm that it will work in all or even most cases.
How I did it
- Create include, lib, lib\python35 and src directories in theproject root directory.
- Copy all files inside of path\to\python35\include to the include directory in the project root directory.
- Zip all files inside of path\to\python35\Lib into a single file called stdlib.zip and put it in the project root directory.¹
- Copy all files inside of path\to\python35\DLLs to the lib\python35 directory in the project root directory. The _tkinter.pyd library file should be inside.²
- Copy the libpython35.a import library from path\to\python35\libs to the lib directory in the project root directory.
Create a main.py file inside of the src directory in the project root directory with the following contents:
import tkinter as tkdef run(): root = tk.Tk() root.mainloop()
- Zip main.py into a single file called source.zip and put it in the project root directory.
Create a main.c file inside of the src directory in the project root directory with the following contents:
// WARNING: I did not check for errors but you definitely should!#import <Python.h>static const char* SYS_PATH = "source.zip;stdlib.zip;lib/python35";int main(int argc, char** argv){ wchar_t* program = NULL; wchar_t** wargv = NULL; wchar_t* sys_path = NULL; int i; program = Py_DecodeLocale(argv[0], NULL); Py_SetProgramName(program); sys_path = Py_DecodeLocale(SYS_PATH, NULL); Py_SetPath(sys_path); Py_Initialize(); wargv = (wchar_t**) malloc(argc * sizeof(wchar_t*)); for (i = 0; i < argc; i++) wargv[i] = Py_DecodeLocale(argv[i], NULL); PySys_SetArgv(argc, wargv); PyRun_SimpleString("import main\n" "main.run()\n"); Py_Finalize(); PyMem_RawFree(program); PyMem_RawFree(sys_path); for (i = 0; i < argc; i++) PyMem_RawFree(wargv[i]); free(wargv); return 0;}
Create a CMakeLists.txt file in the project root directory with the following contents:
cmake_minimum_required(VERSION 3.6)project(emb)set(SOURCE_FILES src/main.c)add_executable(emb ${SOURCE_FILES})include_directories(include)add_library(libpython35 STATIC IMPORTED)set_property( TARGET libpython35 PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/lib/libpython35.a)target_link_libraries(emb libpython35)
Build and run. If you did everything correctly up to this point, you should see something like this:
Traceback (most recent call last): File "<string>", line 2, in <module> File "C:\path\to\project\stdlib.zip\tkinter\__init__.py", line 1868, in __init___tkinter.TclError: Can't find a usable init.tcl in the following directories: C:/path/to/project/lib/lib/tcl8.6 C:/path/to/project/lib/tcl8.6 C:/path/to/project/library C:/path/to/project/tcl8.6.4/library
Tcl and Tk directories are nowhere to be found. We need to bring those in and update the TCL_LIBRARY enviroment variable.
Copy tcl8.6 and tk8.6 directories from C:\path\to\python35\tcl to the lib directory in the project root directory.
Create and set the TCL_LIBRARY environment variable to
"lib\tcl8.6"
.
Everything should work now.
¹ This is not strictly necessary. You could just as well keep your .py files in a directory and append its path to sys.path
.
² The reason why python was raising an ImportError
before was because _tkinter.pyd was inside a zip file and thus could not be loaded.
Just updating after Jovito answered everything to specify how to make it a light installation (steps 3 to 5 this saves space):
Copy the _tkinter.pyd
file mine is in \Pythonpath\DLLs\
into the directory you have the python35.dll
located for your embedded installation. You also need 2 DLLs for tkinter
to work in the same location tcl86t.dll
and tk86t.dll
You need these directories: \Pythonpath\Lib\tkinter
and \Pythonpath\tcl\tcl8.6
and \Pythonpath\tcl\tk8.6
and have to set in your main.py
script as shown below:
import osos.environ['TCL_LIBRARY'] = "tcl//tcl8.6"os.environ['TK_LIBRARY'] = "tcl//tk8.6"
That makes Jovito's answer as lightweight as possible. Use the rest of his answer. Works for me.