【问题标题】:Embed Python 3.5 with tkinter support on Windows在 Windows 上嵌入带有 tkinter 支持的 Python 3.5
【发布时间】:2017-03-22 18:18:32
【问题描述】:

我的项目结构如下:

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)

这是 ma​​in.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.

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

【问题讨论】:

    标签: c windows tkinter python-3.5 python-embedding


    【解决方案1】:

    在 Jovito 回答完所有内容以指定如何使其成为轻型安装后进行更新(步骤 3 到 5 可以节省空间):

    _tkinter.pyd 文件复制到\Pythonpath\DLLs\ 中的python35.dll 目录,用于嵌入式安装。您还需要 2 个 DLL 供 tkinter 在同一位置 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 的答案尽可能轻量级。使用他的其余答案。对我有用。

    【讨论】:

    • 请看我的回答。
    • @Jovito 我在第 8 步中看到在设置 wargv malloc 以在 PySys_SetArgv(argc, wargc) 中使用时出现错误 - “无法将“void *”类型的值分配给键入“wchar_t **” - 感谢您的回答顺便说一句,这是我必须让它工作的最后一步,因为 Tkinter 需要在 C++ 中设置 ARGV。
    • 电话应该是PySys_SetArgv(argc, wargv)
    • @Jovito 这是一个错字,我在 5m 后无法编辑。编译器错误是“无法将“void”类型的值分配给“wchar_t **”类型的实体,就在wargv = malloc(argc * sizeof(wchar_t*));
    【解决方案2】:

    免责声明

    这个答案并不声称是嵌入 Python 3.5 和 Tkinter 支持的正确或最佳方式。循序渐进的格式仅反映了这样一个事实,即这是我设法让所有东西在我的机器上运行的方式,并且由于我无法在其他地方测试此解决方案,我无法确认它是否适用于所有甚至大多数情况。


    我是怎么做到的

    1. 在 项目根目录。
    2. path\to\python35\include内的所有文件复制到项目根目录下的include目录下。
    3. path\to\python35\Lib 内的所有文件压缩到一个名为 stdlib.zip 的文件中,并将其放在项目根目录中。¹
    4. path\to\python35\DLLs内的所有文件复制到项目根目录下的lib\python35目录下。 _tkinter.pyd 库文件应该在里面。²
    5. path\to\python35\libs中的libpython35.a导入库复制到项目根目录下的lib目录下。
    6. 在项目根目录的src目录下创建一个ma​​in.py文件,内容如下:

      import tkinter as tk
      
      def run():
          root = tk.Tk()
          root.mainloop()
      
    7. ma​​in.py 压缩到一个名为 source.zip 的文件中,并将其放在项目根目录中。
    8. 在项目根目录的src目录下创建一个ma​​in.c文件,内容如下:

      // 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. 在项目根目录下创建一个CMakeLists.txt文件,内容如下:

      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. 构建并运行。如果到目前为止您所做的一切都正确,您应该会看到如下内容:

      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 和 Tk 目录。我们需要引入这些并更新 TCL_LIBRARY 环境变量。

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

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

    现在一切都应该正常了。

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

    ² python 之前提出ImportError 的原因是因为 _tkinter.pyd 在一个 zip 文件中,因此无法加载。

    【讨论】:

      猜你喜欢
      • 2016-03-19
      • 1970-01-01
      • 2017-11-15
      • 2010-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多