Py_InitModule4 () возвращает NULL для встроенного интерпретатора Python (с Cython)

1

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

Изображение 174551

Вопрос

Знаете ли вы или знаете, что я делаю неправильно? Может быть, я неправильно инициализирую Python-интерпретатор? Самая причудливая часть - у меня был рабочий прототип этого. Но я не могу заставить его работать! (см. ниже)

Ссылки и заметки

Возможно, вам захочется увидеть полный источник. Вы можете найти рабочий прототип здесь (Virustotal) и фактический проект здесь (Virustotal). (Оба, ссылки на mediafire.com)

Поскольку мне не разрешено распространять iTunesVisualSDK с ним, здесь - это ссылка для загрузки с apple.com.

Пожалуйста, не комментируйте: "Почему бы не работать с прототипом?" или аналогично. Это прототип, и я пишу прототипы грязными и нечистыми, и обычно я добиваюсь лучших результатов при переписывании всего этого.


Спасибо всем, кто смотрит на это, внимательно прочитав его и инвестируя время, чтобы помочь мне решить мою проблему.:-)
-Niklas

  • 0
    Добавьте значение исключения в msgbox. Это может дать вам подсказку. Может быть, он не может найти stdlib. Значение скажет вам, какой модуль не может быть импортирован.
  • 0
    Кроме того, я знаю, что вы еще не достигли этой точки, но Python использует глобальное состояние, поэтому у вас могут возникнуть проблемы, если iTunes решит выгрузить вашу dll между вызовами main.
Показать ещё 3 комментария
Теги:
embed
cython

1 ответ

1
Лучший ответ

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

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

  • 0
    Спасибо большое як! И да, я точно пришел к этой проблеме сейчас. Я не могу запустить поток в Python, потому что после окончания iTunesPluginMain все из Python исчезло. Ваша идея очевидна и очень ценится. Я попробую это, спасибо!
  • 0
    Нужно ли делать какие-то особые вещи для инициализации, чтобы заставить работать потоки в Python? Я вызвал LoadLibrary но это не iTunesPluginMain , потоки по-прежнему уничтожаются, когда заканчивается iTunesPluginMain . Я думаю, лучше было бы открыть еще один вопрос по этому поводу?
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню