Я пишу C-Plugin, используя Cython. В финале это должно включить Python-Interpreter в iTunes в Windows. Для того, чтобы это сработало, необходим загрузчик. Он реализует точку входа в плагин iTunes, инициализирует или завершает работу или что-то еще необходимо, чтобы затем вызвать код, сгенерированный из Cython.
Я использую MinGW gcc 4.6.2 для Windows 7 64Bit и CPython 2.7.
Преамбула
Входная точка плагина iTunes в Windows называется 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;
}
pytunes.c
порождается Китоном. Теперь то, что делает или должно делать bootstrapper, следующее:
Определите, что iTunes хочет сообщить плагину
iTunes.exe
через вызов Windows API и инициализирует интерпретатор Python.handlePyMain
установлено на ноль, чтобы он не выполнялся снова, когда интерпретатор уже финализирован.Если handlePyMain
не установлено в ноль, что означает, что вызов Cython не должен выполняться, вызываются initpytunes
и PyTunes_Main
, которые генерируются из Cython. Вызов initpytunes
необходим, поскольку Cython выполняет инициализацию глобальных переменных. PyTunes_Main
- это, наконец, реализация Cython того, что делает плагин.
Реализация 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, но это имеет меньшее значение здесь.
Описание проблемы
Проблема возникает, например, при импорте модуля sys
в Cython и его использовании. Простой пример:
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)
Sidenote: строка 553 - это строка, в которой оператор Python print >> fl, sys
обрабатывался Cython. Вы можете найти полный исходный код pytunes.c
на paste.pocoo.org.
Отладочный сеанс сообщает нам, что __pyx_m_t
используется в контексте с использованием модуля sys
в коде Cython (почему?). Во всяком случае, это NULL-указатель. Он должен быть инициализирован в строке 699. Py_InitModule4
, очевидно, возвращает NULL, и поэтому ImportError должен быть поднят в пределах initpytunes
. (Вы можете найти соответствующую реализацию goto __pyx_L1_error
в строке 751).
Чтобы проверить это, я немного изменил код, и результат был "положительным" в этом контексте.
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
An ImportError
указывает, что Python не смог импортировать модуль. Проверьте значение исключения, чтобы узнать, какой модуль не найден.
Функции модуля init
возвращают void
, поэтому вы всегда должны вызывать PyErr_Occurred()
после него, чтобы проверить, не сработало ли оно. Если произошла ошибка, вы должны обработать ее, предпочтительно, показывая ее пользователю. Если stdout доступен, PyErr_Print()
распечатает полную трассу.
Я не уверен, как работают плагины iTunes, но если он действительно выгружает DLL между вызовами, тогда Python также будет выгружен, и его состояние будет потеряно. Вам нужно будет вызвать Py_Initialize()
и Py_Finalize()
в каждом вызове iTunesPluginMain()
, что означает, что все ваши объекты Python также будут потеряны. Скорее всего, не то, что вы хотите.
Одна из идей предотвратить это может заключаться в том, чтобы повторно открыть вашу DLL-модуль плагина в kPluginInitMessage
и закрыть его в kPluginCleanupMessage
. Windows отслеживает, сколько раз DLL была открыта процессом. DLL выгружается только после того, как счет достигает 0. Поэтому, если вы вызываете LoadLibrary()
в своей DLL, счетчик будет увеличен до 2, а DLL будет выгружен только после iTunes и вашего кодового вызова FreeLibrary()
.
Обратите внимание, что это всего лишь идея (непроверенная).
iTunesPluginMain
все из Python исчезло. Ваша идея очевидна и очень ценится. Я попробую это, спасибо!
LoadLibrary
но это не iTunesPluginMain
, потоки по-прежнему уничтожаются, когда заканчивается iTunesPluginMain
. Я думаю, лучше было бы открыть еще один вопрос по этому поводу?