Py_InitModule4 () возвращает NULL на встроенный Python-интерпретатор (с Cython)

Я пишу 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  #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.c генерируется Cython . Теперь то, что делает или должно делать bootstrapper, следующее:

  1. Определите, что iTunes хочет сообщить плагину

    • Если iTunes уведомляет об инициализации, он извлекает путь к iTunes.exe через вызов Windows API и инициализирует интерпретатор Python.
    • Если iTunes уведомляет его об очистке (например, iTunes закрывается), он завершает интерпретатор Python. Обратите внимание, что «Cython-call» выполняется до того, как это произойдет, и handlePyMain установлен на ноль, поэтому он не будет выполнен повторно, когда интерпретатор уже финализирован.
  2. Если 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 содержит 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

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() .

Обратите внимание, что это всего лишь идея (непроверенная).