My project structure looks like this:
emb
| CMakeLists.txt
| main.c
| python35.lib
| stdlib.zip
| _tkinter.pyd
|
+---include
| |
| | abstract.h
| | accu.h
| | asdl.h
...
| | warnings.h
| | weakrefobject.h
|
+---build
| | emb.exe
stdlib.zip contains the DLLs, Lib and site-packages directories from Python 3.5.2 installation whose paths are appended to sys.path
. I'm implicitly loading python35.dll by linking to python35.lib which contains the stubs for all of the exported functions in the DLL. Here's the contents of CMakeLists.txt:
cmake_minimum_required(VERSION 3.6)
project(embpython)
set(SOURCE_FILES main.c)
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
set(PYTHON_INCLUDE_DIR include)
include_directories(${PYTHON_INCLUDE_DIR})
target_link_libraries(
${PROJECT_NAME}
${CMAKE_CURRENT_LIST_DIR}/python35.lib
${CMAKE_CURRENT_LIST_DIR}/_tkinter.pyd)
And here's the contents of main.c:
#include <Python.h>
int main(int argc, char** argv)
{
wchar_t* program_name;
wchar_t* sys_path;
char* path;
program_name = Py_DecodeLocale(argv[0], NULL);
if (program_name == NULL)
{
fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
exit(1);
}
Py_SetProgramName(program_name);
path = "stdlib.zip;stdlib.zip/DLLs;stdlib.zip/Lib;"
"stdlib.zip/site-packages";
sys_path = Py_DecodeLocale(path, NULL);
Py_SetPath(sys_path);
Py_Initialize();
PySys_SetArgv(argc, argv);
PyRun_SimpleString("import tkinter\n");
Py_Finalize();
PyMem_RawFree(sys_path);
PyMem_RawFree(program_name);
return 0;
}
Now, here's the error I'm getting:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File " ... emb\stdlib.zip\Lib\tkinter\__init__.py", line 35, in <module>
ImportError: DLL load failed: The specified module could not be found.
What am I doing wrong and how can I fix it?
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.
Create a main.py file inside of the src directory in the project root directory with the following contents:
import tkinter as tk
def run():
root = tk.Tk()
root.mainloop()
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With