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

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

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

Cython实现

pytunes.pyx实现的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"声明的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会话 ,它将告诉我们实际上是什么问题。

 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 >> fl, sys由Cython处理的行。 您可以在pytunes.c上找到pytunes.c的完整生成源代码。

调试会话告诉我们在上下文中使用__pyx_m_t与在Cython代码中使用sys模块(为什么呢?)。 无论如何,它是一个NULL -pointer。 它应该在699行初始化。 Py_InitModule4显然返回NULL ,因此应该在initpytunes引发initpytunes 。 (您可以在第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解释器错了? 最离奇的部分是,我有一个工作原型。 但我不能让它继续工作! (见下文)

参考和说明

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

由于我不允许用它分发iTunesVisualSDK, 这里有一个从apple.com下载的链接。

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


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

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

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

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

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

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