【问题标题】:Py_InitModule4() returns NULL on embedded Python-interpreter (with Cython)Py_InitModule4() 在嵌入式 Python 解释器(使用 Cython)上返回 NULL
【发布时间】:2012-03-08 07:48:34
【问题描述】:

我正在使用 Cython 编写 C 插件。最后,这应该将 Python-Interpreter 嵌入到 Windows 上的 iTunes 中。为此,需要bootstrapper。它实现了 iTunes 插件入口点,初始化或完成,或任何需要的,然后调用从 Cython 生成的代码。

我在 Windows 7 64BitCPython 2.7 上使用 MinGW gcc 4.6.2

序言

Windows 上的 iTunes 插件入口点称为 iTunesPluginMain。据我了解,实现该插件的共享库一直保持 iTunes 运行,因此您可以在条目后存储任何全局变量-point 被调用。这就是为什么 iTunes 希望开发人员存储一个指向句柄的 void* 指针,该句柄在每次调用 iTunesPluginMain 时都会传递给该句柄。

iTunesPluginMain 被调用用于多个通知,例如初始化和清理。

引导程序如下所示:

/** coding: utf-8
    file:   bootstrap.c
    Copyright (c) 2012 by Niklas Rosenstein

    This file implements the bootstrapper for loading the PyTunes plugin. **/

#include <Python.h>
#include <stdlib.h>
#include <windows.h>
#include <iTunesVisualAPI/iTunesAPI.h>

#include "pytunes.c"

#define EXPORT(type) __declspec(dllexport) type

extern void     initpytunes(void);
extern OSStatus PyTunes_Main(OSType, PluginMessageInfo*, void*);

EXPORT(OSStatus) iTunesPluginMain(OSType message, PluginMessageInfo* msgInfo, void* refCon) {

    OSStatus status             = unimpErr;
    char     handlePyMain       = 1;

    switch(message) {
        case kPluginInitMessage: {
            // Sent to notify the plugin that this is the first time it is loaded
            // and should register itself to iTunes

            char** argv = malloc(sizeof(char*) * 1);
            argv[0]     = malloc(sizeof(char)  * 256);

            // WinAPI call, retrieves the path to iTunes.exe
            GetModuleFileName(0, argv[0], 256);

            // Initialize the Python-interpreter
            Py_SetProgramName(argv[0]);
            PyEval_InitThreads();
            Py_Initialize();
            PySys_SetArgvEx(1, argv, 0);
            handlePyMain = 1;

            free(argv[0]);
            free(argv);
            break;
        }

        case kPluginCleanupMessage: {
            // Sent to cleanup the memory when the plugin gets unload

            status       = PyTunes_Main(message, msgInfo, refCon);
            handlePyMain = 0;
            Py_Finalize();
            break;
        }

        default: break;
    }

    if (handlePyMain != 0) {
        initpytunes();
        status = PyTunes_Main(message, msgInfo, refCon);
    }

    return status;
}

pytunes.cCython 生成。现在引导程序做什么,或者应该做什么,如下:

  1. 确定 iTunes 想要告诉插件什么

    • 如果 iTunes 通知它初始化,它会通过 Windows API 调用检索到 iTunes.exe 的路径并初始化 Python 解释器。
    • 如果 iTunes 通知它进行清理(例如 iTunes 关闭),它会最终确定 Python 解释器。请注意,“Cython 调用”在此之前完成,handlePyMain 设置为零,因此在解释器已经完成时不会再次执行。
  2. 如果 handlePyMain 未设置为零,表示不应执行 Cython 调用,则调用从 Cython 生成的 initpytunesPyTunes_Main。调用 initpytunes 是必要的,因为 Cython 在那里对全局变量进行初始化。 PyTunes_Main 最终是插件功能的 Cython 实现。

Cython 实现

pytunes.pyx 中实现的对PyTunes_Main 的调用运行顺利。以下实现在我的桌面上打开一个文件并向其中写入一条消息。

cimport iTunesVisualAPI     as itapi

cdef public itapi.OSStatus PyTunes_Main(itapi.OSType message,
                                        itapi.PluginMessageInfo* msgInfo,
                                        void* refCon):
    fl = open("C:/Users/niklas/Desktop/feedback.txt", "w")
    print >> fl, "Greetings from PyTunes!"
    fl.close()
    return itapi.unimpErr

当我启动 iTunes 时,会创建文件并将文本写入其中。

iTunesVisalAPI.pxd 包含 cdef extern from "iTunesVisualAPI/iTunesVisualAPI.h" 声明以使 API 可用于 Cython,但这在这里不太重要。

问题描述

例如,当在 Cython 中导入 sys 模块并使用它时,就会出现问题。简单例子:

cimport iTunesVisualAPI     as itapi

import sys

cdef public itapi.OSStatus PyTunes_Main(itapi.OSType message,
                                        itapi.PluginMessageInfo* msgInfo,
                                        void* refCon):
    fl = open("C:/Users/niklas/Desktop/feedback.txt", "w")
    print >> fl, sys
    fl.close()
    return itapi.unimpErr

这会导致 iTunes 崩溃。这是完整的gdb-session,它会告诉我们问题到底是什么。

C:\Program Files (x86)\iTunes>gdb -q iTunes.exe
Reading symbols from c:\program files (x86)\itunes\iTunes.exe...(no debugging symbols found)...done.
(gdb) b pytunes.c:553
No symbol table is loaded.  Use the "file" command.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (pytunes.c:553) pending.
(gdb) r
Starting program: c:\program files (x86)\itunes\iTunes.exe
[New Thread 3244.0x3a8]
[New Thread 3244.0xd90]
[New Thread 3244.0x11c0]
[New Thread 3244.0x125c]
[New Thread 3244.0x1354]
[New Thread 3244.0x690]
[New Thread 3244.0x3d8]
[New Thread 3244.0xdb8]
[New Thread 3244.0xe74]
[New Thread 3244.0xf2c]
[New Thread 3244.0x13c0]
[New Thread 3244.0x1038]
[New Thread 3244.0x12b4]
[New Thread 3244.0x101c]
[New Thread 3244.0x10b0]
[New Thread 3244.0x140]
[New Thread 3244.0x10e4]
[New Thread 3244.0x848]
[New Thread 3244.0x1b0]
[New Thread 3244.0xc84]
[New Thread 3244.0xd5c]
[New Thread 3244.0x12dc]
[New Thread 3244.0x12fc]
[New Thread 3244.0xf84]
warning: ASL checking for logging parameters in environment variable "iTunes.exe.log"

warning: ASL checking for logging parameters in environment variable "asl.log"

BFD: C:\Windows\SysWOW64\WMVCORE.DLL: Warning: Ignoring section flag IMAGE_SCN_MEM_NOT_PAGED in section .reloc

Breakpoint 1, PyTunes_Main (__pyx_v_message=1768843636, __pyx_v_msgInfo=0xd7e798, __pyx_v_refCon=0x0)
    at C:/Users/niklas/Desktop/pytunes/pytunes/build-cython/pytunes.c:553
553       __pyx_t_1 = __Pyx_GetName(__pyx_m, __pyx_n_s__sys); if (unlikely(!__pyx_t_1)) {__pyx_filename = __pyx_f[0]; __pyx_lineno
 = 75; __pyx_clineno = __LINE__; goto __pyx_L1_error;}
(gdb) print __pyx_m
$1 = (PyObject *) 0x0
(gdb) print __pyx_n_s__sys
$2 = (PyObject *) 0x92f42c0
(gdb) print __pyx_t_1
$3 = (PyObject *) 0x0
(gdb) step
__Pyx_GetName (dict=0x0, name=0x92f42c0) at C:/Users/niklas/Desktop/pytunes/pytunes/build-cython/pytunes.c:788
788         result = PyObject_GetAttr(dict, name);
(gdb) step

Program received signal SIGSEGV, Segmentation fault.
0x1e089f57 in python27!PyObject_GetAttr () from C:\Windows\SysWOW64\python27.dll
(gdb)

旁注:第 553 行是 Python 语句 print &gt;&gt; fl, sys 由 Cython 处理的行。您可以在paste.pocoo.org 上找到pytunes.c 的完整生成源代码。

调试会话告诉我们 __pyx_m_t 在 Cython 代码中使用 sys 模块的上下文中使用(为什么?)。无论如何,它是一个 NULL 指针。它应该在 699 行初始化。 Py_InitModule4 显然返回 NULL,因此应该在 initpytunes 内引发 ImportError。 (可以在751行找到goto __pyx_L1_error的对应实现。

为了检查这一点,我稍微修改了代码,结果在该上下文中是“肯定的”。

    if (handlePyMain != 0) {
        initpytunes();
        if (PyErr_Occurred()) {
            PyObject* exception, *value, *traceback;
            PyErr_Fetch(&exception, &value, &traceback);
            PyObject* errString = PyObject_Str(exception);

            // WinAPI call
            MessageBox(NULL, PyString_AsString(errString), "PyErr_Occurred()?", 0);
            status = paramErr;
        }
        else {
            // WinAPI call
            MessageBox(NULL, "No error, calling PyTunes_Main.", "PyPyErr_Occurred()?", 0);
            status = PyTunes_Main(message, msgInfo, refCon);
        }
    }

问题

你知道或知道我做错了什么吗?也许我初始化 Python 解释器错误?最奇怪的部分是,我有一个工作原型。但我不能让它工作了! (见下文)

参考资料和注释

您可能想查看完整的源代码。您可以找到工作原型here (Virustotal) 和实际项目here (Virustotal)。 (两者都是 mediafire.com 的链接)

由于我不允许随其分发 iTunesVisualSDK,here 是从 apple.com 下载它的链接。

请不要评论“为什么不使用原型?”或类似的。它是一个原型,而我写的原型又脏又脏,通常我在重写整个东西时会取得更好的结果。


感谢所有看到此内容、仔细阅读并投入时间帮助我解决问题的人。 :-)
-尼克拉斯

【问题讨论】:

  • 将异常的值添加到 msgbox。这可能会给你一个线索。也许它找不到标准库。该值将告诉您它无法导入哪个模块。
  • 另外,我知道你还没有达到那一点,但是 Python 使用全局状态,所以如果 iTunes 决定在调用 main 之间卸载你的 dll,你可能(将会)遇到麻烦。
  • @yak 天哪!这太丢人了!我非常确信 pytunes 模块的创建失败,我没有查看异常的值。 No module named iTunesVis,真丢人。 cimport iTunesVis as itpytunes.pxd 的行之一。由于iTunesVis.pxd 中有一个 Python 类声明,Cython 需要一个名为 iTunesVis 的模块。删除 Python 声明或完全删除 cimport 可以修复它。非常感谢,我真的应该在问这个问题之前就这样做.. >.> 也许你想写一个答案?免费声望哈哈。
  • @yak 嗯,我实际上并没有考虑过。这是否意味着我需要初始化 every上完成解释器> 打电话给iTunesPluginMain?

标签: python c embed cython


【解决方案1】:

ImportError 表示 Python 无法导入模块。检查异常的值以查看未找到哪个模块。

模块init 函数返回void 所以你应该总是在它之后调用PyErr_Occurred() 来检查它是否失败。如果发生错误,您必须处理它,最好以某种方式将其显示给用户。如果标准输出可用,PyErr_Print() 将打印出完整的回溯。

我不确定 iTunes 插件是如何工作的,但如果它确实在调用之间卸载了 DLL,那么 Python 也将被卸载并且它的状态将会丢失。您需要在每次调用iTunesPluginMain() 时调用Py_Initialize()Py_Finalize(),这意味着您的所有Python 对象也会丢失。很可能不是您想要的。

防止这种情况的一个方法是在kPluginInitMessage 中重新打开您的插件DLL 并在kPluginCleanupMessage 中关闭它。 Windows 会跟踪某个进程打开 DLL 的次数。只有在计数达到 0 后才会卸载 DLL。因此,如果您在 DLL 中调用 LoadLibrary(),则计数将增加到 2,并且仅在 iTunes 和您的代码调用 FreeLibrary() 之后才会卸载 DLL。

请注意,这只是一个(未经测试的)想法。

【讨论】:

  • 非常感谢牦牛!是的,我现在已经完全解决了这个问题。我无法在 Python 中启动线程,因为在 iTunesPluginMain 结束后,来自 Python 的一切都消失了。你的想法很明显,非常感谢。我去试试,谢谢!
  • 我是否必须做任何特殊的初始化工作才能使 Python 中的线程工作?我已经调用了LoadLibrary 但这并没有解决它,当iTunesPluginMain 结束时线程仍然被杀死。我想,最好就此提出另一个问题?
  • 嗯等等,肯定有问题。我现在无法在 iTunes 打开时删除 pytunes.dll,因此它不会被卸载。它一定是别的东西。
  • 了解 Apple,他们可能会加倍努力以确保“正确”的插件行为。也许您可以使用多个进程?
  • 你是什么意思为了确保“正确”的插件行为? |我从未使用过多处理,无论是在 Python 中还是在 C 中。关于如何实现您的想法的任何参考资料?
猜你喜欢
  • 1970-01-01
  • 2014-08-24
  • 1970-01-01
  • 2012-07-01
  • 2014-10-12
  • 2023-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多