Py_InitModule4()在嵌入式Python解释器上返回NULL(使用Cython)

时间:2012-02-16 21:24:37

标签: python c embed cython

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

我在 Windows 7 64Bit CPython 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.c Cython 生成。现在引导程序做或应该做的是以下内容:

  1. 确定iTunes想要告诉插件的内容

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

  3. Cython实施

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

    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行是Cython处理Python语句print >> fl, sys的行。您可以在paste.pocoo.org上找到pytunes.c的完整生成源代码。

    调试会话告诉我们在上下文中使用__pyx_m_t使用Cython代码中的sys模块(为什么呢?)。无论如何,它是 NULL -pointer。它应该在 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);
            }
        }
    

    message-box displaying the name of the exception

    问题

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

    参考和注释

    您可能希望查看完整的来源。您可以找到工作原型hereVirustotal)和实际项目hereVirustotal)。 (两者都链接到mediafire.com)

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

    请不要评论“为什么不使用原型?”或者一样的。它是一个原型,我写的是脏和不洁的原型,通常我在重写整个事情时会取得更好的效果。


    感谢所有关注此事的人,仔细阅读并花时间帮助我解决问题。 :-)
    -Niklas

1 个答案:

答案 0 :(得分:1)

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

模块init函数返回void,因此您应始终在其后调用PyErr_Occurred()以检查其是否失败。如果发生错误,您必须处理它,最好是以某种方式向用户显示。如果stdout可用,PyErr_Print()将打印出完整的回溯。

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

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

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