我正在使用 Cython 编写 C 插件。最终,这应该将 Python 解释器嵌入到 Windows 上的 iTunes 中。为了使其工作,需要一个引导程序。它实现 iTunes 插件入口点,初始化或完成,或任何需要的操作,然后调用从 Cython 生成的代码。
我在Windows 7 64Bit和CPython 2.7上使用MinGW gcc 4.6.2。
前言
Windows 上的 iTunes 插件入口点称为iTunesPluginMain。据我所知,实现插件的共享库并非在iTunes 运行时一直保留,因此一旦调用入口点,您就无法存储任何全局变量。这就是为什么 iTunes 希望开发人员存储一个指向句柄void*的指针,该句柄在每次调用 时都会被传递iTunesPluginMain。
iTunesPluginMain
void*
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生成。现在引导程序执行或应该执行的操作如下:
pytunes.c
iTunes.exe
handlePyMain
initpytunes
PyTunes_Main
Cython 实现
PyTunes_Main在 中实现的对 的调用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"使 API 可用于 Cython 的声明,但在这里并不那么重要。
iTunesVisalAPI.pxd
cdef extern from "iTunesVisualAPI/iTunesVisualAPI.h"
问题描述
问题出现的原因,仅仅是比如sys在 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 语句的行。您可以在paste.pocoo.orgprint >> fl, sys上找到完整的生成源代码。pytunes.c
print >> fl, sys
调试会话告诉我们,在 Cython 代码中__pyx_m_t使用模块时会用到(为什么?)。无论如何,它是一个NULL指针。它应该在第699行初始化。显然返回NULL,因此应该在 中引发ImportError 。(您可以在第751行找到 的相应实现)。sys``Py_InitModule4``initpytunes``goto __pyx_L1_error
__pyx_m_t
sys``Py_InitModule4``initpytunes``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 下载的链接。
请不要评论“为什么不使用原型?”或类似的话。这是一个原型,我写的原型很脏很不干净,通常重写整个原型会取得更好的结果。
块init函数返回void,因此您应始终PyErr_Occurred()在它之后调用它以检查它是否失败。如果发生错误,您必须处理它,最好以某种方式向用户显示它。如果 stdout 可用,PyErr_Print()将打印出完整的回溯。
init
void
PyErr_Occurred()
PyErr_Print()
我不确定 iTunes 插件是如何工作的,但如果它确实在调用之间卸载了 DLL,那么 Python 也将被卸载,其状态将丢失。您需要在每次调用时Py_Initialize()调用,这意味着您的所有 Python 对象也将丢失。很可能不是您想要的。Py_Finalize()``iTunesPluginMain()
Py_Initialize()
Py_Finalize()``iTunesPluginMain()
防止这种情况发生的一个方法是在 中重新打开插件 DLL kPluginInitMessage,然后在 中关闭它kPluginCleanupMessage。Windows 会跟踪进程打开 DLL 的次数。只有在计数达到 0 后才会卸载 DLL。因此,如果您调用LoadLibrary()DLL,计数将增加到 2,并且只有在 iTunes 和您的代码都调用 后才会卸载 DLL FreeLibrary()。
kPluginInitMessage
kPluginCleanupMessage
LoadLibrary()
FreeLibrary()
请注意,这只是一个(未经测试的)想法。