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

Py_InitModule4()在嵌入式Python解释器上返回NULL(使用Cython),python,c,embed,cython,Python,C,Embed,Cython,我正在用Cython编写一个C插件。最后,这应该将Python解释器嵌入Windows上的iTunes中。为了使这项工作正常进行,需要一个新的解决方案。它实现iTunes插件入口点,初始化或完成,或者任何需要的操作,然后调用Cython生成的代码 我正在Windows 7 64位和CPython 2.7上使用MinGW gcc 4.6.2 序言 Windows上的iTunes插件入口点称为iTunesPluginMain。据我所知,实现该插件的共享库并非在iTunes运行的所有时间都保留,因此一

我正在用Cython编写一个C插件。最后,这应该将Python解释器嵌入Windows上的iTunes中。为了使这项工作正常进行,需要一个新的解决方案。它实现iTunes插件入口点,初始化或完成,或者任何需要的操作,然后调用Cython生成的代码

我正在Windows 7 64位和CPython 2.7上使用MinGW gcc 4.6.2

序言

Windows上的iTunes插件入口点称为
iTunesPluginMain
。据我所知,实现该插件的共享库并非在iTunes运行的所有时间都保留,因此一旦调用入口点,就无法存储任何全局变量。这就是为什么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;
}
当我启动iTunes时,会创建文件并将文本写入其中

iTunesVisalAPI.pxd
包含来自“iTunesVisualAPI/iTunesVisualAPI.h”的
cdef外部声明,以使该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会话,它将告诉我们问题到底是什么

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语句的那一行。您可以在上找到
pytunes.c
的完整生成源代码

调试会话告诉我们,
\uupyx\u m\t
是在Cython代码中使用
sys
模块的上下文中使用的(为什么?)。无论如何,它是一个空指针。应在第699行对其进行初始化
Py_InitModule4
显然返回NULL,因此应在
initpytunes
中引发ImportError。(您可以在第751行找到
goto\uupyx\ul1\u 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解释器时出错了?最奇怪的是,我有一个工作原型。但是我再也不能让它工作了!(见下文)

参考文献和注释

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

由于我不能随它一起发布iTunesVisualSDK,因此有一个从apple.com下载的链接

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


感谢大家仔细阅读,并投入时间帮助我解决问题。:-)

-Niklas

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

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

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

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


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

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

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

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

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

请注意,这只是一个例子
    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);
        }
    }