在Windows上嵌入带有tkinter支持的Python 3.5

7

我的项目结构如下:

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 包含 Python 3.5.2 安装中的 DLLsLibsite-packages 目录,这些目录的路径将被添加到 sys.path 中。我通过链接到包含 DLL 中所有导出函数存根的 python35.lib 隐式加载了 python35.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)

以下是 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;
}

现在,我收到的错误消息是:
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.

我做错了什么,我应该怎么处理?
2个回答

3

免责声明

本回答并不保证是嵌入Python 3.5与Tkinter支持的正确或最佳方法。逐步格式仅反映了这是我如何在我的机器上使一切正常工作的方式,由于我无法在其他地方测试此解决方案,因此无法确认它是否适用于所有甚至大多数情况。


我是如何做到的

  1. Create include, lib, lib\python35 and src directories in the project root directory.
  2. Copy all files inside of path\to\python35\include to the include directory in the project root directory.
  3. Zip all files inside of path\to\python35\Lib into a single file called stdlib.zip and put it in the project root directory.¹
  4. 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.²
  5. Copy the libpython35.a import library from path\to\python35\libs to the lib directory in the project root directory.
  6. 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()
    
  7. Zip main.py into a single file called source.zip and put it in the project root directory.
  8. 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;
    }
    
  9. 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)
    
  10. 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.

  11. Copy tcl8.6 and tk8.6 directories from C:\path\to\python35\tcl to the lib directory in the project root directory.

  12. Create and set the TCL_LIBRARY environment variable to "lib\tcl8.6".

现在应该一切正常了。

¹ 这并非必须。您可以将.py文件保存在目录中,并将其路径附加到sys.path上。

² 以前Python引发ImportError的原因是因为_tkinter.pyd位于zip文件中,因此无法加载。


0

在Jovito回答完所有问题后,更新一下以指定如何进行轻量级安装(步骤3到5可以节省空间):

_tkinter.pyd文件(我的在\Pythonpath\DLLs\中)复制到嵌入式安装的python35.dll所在的目录中。您还需要2个tkinter DLL文件才能在同一位置工作:tcl86t.dlltk86t.dll

您需要这些目录:\Pythonpath\Lib\tkinter\Pythonpath\tcl\tcl8.6\Pythonpath\tcl\tk8.6,并且必须在您的main.py脚本中设置如下:

import os
os.environ['TCL_LIBRARY'] = "tcl//tcl8.6"
os.environ['TK_LIBRARY'] = "tcl//tk8.6"

这使得Jovito的答案尽可能地轻量级。使用他的其余答案。对我来说可行。


请查看我的答案。 - bzrr
@Jovito 我在第8步中发现了一个错误,当设置wargv malloc以便在PySys_SetArgv(argc, wargc)中使用时,会出现以下错误 - "无法将类型为"void *"的值赋给类型为"wchar_t **"的变量 - 顺便谢谢你的答案。BTW这是我必须完成的最后一步,因为Tkinter要求在C++中设置ARGV。 - Matt
调用应该是 PySys_SetArgv(argc, wargv) - bzrr
@Jovito,那是一个打字错误,我在5分钟后无法编辑。编译器错误是“无法将“void”类型的值分配给类型为“wchar_t **”的实体”,就在wargv = malloc(argc * sizeof(wchar_t*));处。 - Matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接