在Windows上使用tkinter支持嵌入Python 3.5

bzz*_*zzr 7 c windows tkinter python-embedding python-3.5

我的项目结构如下所示:

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
Run Code Online (Sandbox Code Playgroud)

stdlib.zip包含Python 3.5.2安装中的DLL,Libsite-packages目录,其路径被追加到sys.path.我通过链接到python35.lib隐式加载python35.dll,python35.lib包含DLL中所有导出函数的存根.这是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)
Run Code Online (Sandbox Code Playgroud)

这是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;
}
Run Code Online (Sandbox Code Playgroud)

现在,这是我得到的错误:

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.
Run Code Online (Sandbox Code Playgroud)

我做错了什么,我该如何解决?

bzz*_*zzr 3

免责声明

\n\n

这个答案并不意味着是嵌入带有 Tkinter 支持的 Python 3.5 的正确或最佳方法。分步格式仅反映了这样一个事实:这就是我设法让所有内容在我的计算机上运行的方式,并且由于我无法在其他地方测试此解决方案,因此我无法确认它是否适用于所有甚至大多数情况。

\n\n
\n\n

我是怎么做到的

\n\n
    \n
  1. 创建includeliblib\\python35src在项目根目录中
  2. \n
  3. 将path\\to\\python35\\include的所有文件复制到include项目根目录中的
  4. \n
  5. 将path\\to\\python35\\Lib内的所有文件压缩到名为stdlib.zip的单个文件中的文件中,并将其放入项目根目录中。\xc2\xb9
  6. \n
  7. 将path\\to\\python35\\DLLs内的所有文件复制到项目根目录的lib\\python35目录中。_tkinter.pyd应该在里面。\xc2\xb2
  8. \n
  9. 将libpython35.a导入库从path\\to\\python35\\libs复制到lib项目根目录下的
  10. \n
  11. 在src中创建main.py文件其中包含以下内容:

    \n\n
    import tkinter as tk\n\ndef run():\n    root = tk.Tk()\n    root.mainloop()\n
    Run Code Online (Sandbox Code Playgroud)
  12. \n
  13. 将main.py压缩到名为source.zip的单个文件中的文件中,并将其放在项目根目录中。
  14. \n
  15. 在src中创建一个main.c文件其中包含以下内容:

    \n\n
    // WARNING: I did not check for errors but you definitely should!\n\n#import <Python.h>\n\nstatic const char* SYS_PATH = "source.zip;stdlib.zip;lib/python35";\n\nint main(int argc, char** argv)\n{\n    wchar_t* program = NULL;\n    wchar_t** wargv = NULL;\n    wchar_t* sys_path = NULL;\n    int i;\n\n    program = Py_DecodeLocale(argv[0], NULL);\n    Py_SetProgramName(program);\n\n    sys_path = Py_DecodeLocale(SYS_PATH, NULL);\n    Py_SetPath(sys_path);\n\n    Py_Initialize();\n\n    wargv = (wchar_t**) malloc(argc * sizeof(wchar_t*));\n    for (i = 0; i < argc; i++)\n        wargv[i] = Py_DecodeLocale(argv[i], NULL);\n    PySys_SetArgv(argc, wargv);\n\n    PyRun_SimpleString("import main\\n"\n                       "main.run()\\n");\n\n    Py_Finalize();\n    PyMem_RawFree(program);\n    PyMem_RawFree(sys_path);\n    for (i = 0; i < argc; i++)\n        PyMem_RawFree(wargv[i]);\n    free(wargv);\n    return 0;\n}\n
    Run Code Online (Sandbox Code Playgroud)
  16. \n
  17. 创建CMakeLists.txt文件,内容如下:

    \n\n
    cmake_minimum_required(VERSION 3.6)\nproject(emb)\n\nset(SOURCE_FILES src/main.c)\nadd_executable(emb ${SOURCE_FILES})\n\ninclude_directories(include)\n\nadd_library(libpython35 STATIC IMPORTED)\nset_property(\n    TARGET libpython35 PROPERTY IMPORTED_LOCATION\n    ${CMAKE_CURRENT_LIST_DIR}/lib/libpython35.a)\n\ntarget_link_libraries(emb libpython35)\n
    Run Code Online (Sandbox Code Playgroud)
  18. \n
  19. 构建并运行。如果到目前为止您所做的一切都是正确的,您应该会看到如下内容:

    \n\n
    Traceback (most recent call last):\n  File "<string>", line 2, in <module>\n  File "C:\\path\\to\\project\\stdlib.zip\\tkinter\\__init__.py", line 1868, in __init__\n_tkinter.TclError: Can\'t find a usable init.tcl in the following directories:\n    C:/path/to/project/lib/lib/tcl8.6\n    C:/path/to/project/lib/tcl8.6 \n    C:/path/to/project/library\n    C:/path/to/project/tcl8.6.4/library\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    找不到 Tcl 和 Tk 目录。我们需要引入它们并更新 TCL_LIBRARY 环境变量。

  20. \n
  21. tcl8.6tk8.6目录从C:\\path\\to\\python35\\tcl复制到lib项目根目录下的

  22. \n
  23. 创建 TCL_LIBRARY 环境变量并将其设置为"lib\\tcl8.6"

  24. \n
\n\n

现在一切都应该正常了。

\n\n

\xc2\xb9这并不是绝对必要的。您也可以将 .py 文件保存在一个目录中并将其路径附加到sys.path.

\n\n

\xc2\xb2 python 引发 before 的原因ImportError是因为_tkinter.pyd位于 zip 文件内,因此无法加载。

\n