Py_InitModule4 () возвращает NULL для встроенного интерпретатора Python (с Cython) - PullRequest
1 голос
/ 17 февраля 2012

Я пишу C-плагин, используя Cython.Наконец, это должно встроить Python-интерпретатор в 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 генерируется Cython .Теперь загрузчик выполняет или должен делать следующее:

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

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

Чтобы проверить это, я немного изменил код, и результат "положительный" в этомcontext.

    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);
        }
    }

message-box displaying the name of the exception

Вопрос

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

Ссылки и примечания

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

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

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


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

1 Ответ

1 голос
/ 17 февраля 2012

ImportError означает, что Python не может импортировать модуль. Проверьте значение исключения, чтобы увидеть, какой модуль не был найден.

Модуль init функции возвращают void, поэтому вы всегда должны вызывать PyErr_Occurred() после него, чтобы проверить, не удалось ли это. Если произошла ошибка, вы должны устранить ее, желательно, показав ее пользователю. Если доступен стандартный вывод, PyErr_Print() распечатает полный отчет.

Я не уверен, как работают плагины iTunes, но если он действительно выгружает библиотеки DLL между вызовами, то Python также будет выгружен и его состояние будет потеряно. Вам нужно будет вызывать Py_Initialize() и Py_Finalize() при каждом вызове iTunesPluginMain(), что означает, что все ваши объекты Python также будут потеряны. Скорее всего не то, что вы хотите.

Одной из идей предотвратить это может быть повторное открытие библиотеки DLL вашего плагина в kPluginInitMessage и ее закрытие в kPluginCleanupMessage. Windows отслеживает, сколько раз DLL была открыта процессом. DLL выгружается только после того, как счетчик достигнет 0. Поэтому, если вы вызовете LoadLibrary() в вашей DLL, счетчик увеличится до 2, а DLL будет выгружен только после того, как iTunes и ваш код вызовут FreeLibrary().

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

...