python обратных вызовов ctypes внутри интерфейса - PullRequest
0 голосов
/ 04 марта 2020

Я пытаюсь воспроизвести XAudio2 с Python, используя Ctypes и COM в библиотеке dll. У меня уже воспроизводится звук из файла, и он работает просто отлично. Однако у меня возникают проблемы при попытке реализовать некоторые из асинхронных режимов c, которые требуют обратных вызовов. В частности, интерфейс IXAudio2VoiceCallback требует, чтобы вы передавали его CreateSourceVoice, если вы хотите получать обратные вызовы, когда что-то срабатывает. Как только он попытается прочитать буфер, моя программа будет работать с sh, я подозреваю, потому что у меня нет этой правильной настройки (onBufferStart?).

Мои вопросы: возможно ли для Python принимать вызовы из библиотек на Python таким образом? Это что-то поддерживает COM и Ctypes?

Если это так, как мне сопоставить вызовы этих функций с функциями Python? Я видел несколько примеров определения типа обратного вызова и присвоения его через callback(function) (см. Пример ниже, пытаясь заставить его напечатать что-то), но я не уверен, как это работает, если он находится в структуре.

Вот интерфейс, определенный в заголовочном файле:

#undef INTERFACE
#define INTERFACE IXAudio2VoiceCallback
DECLARE_INTERFACE(IXAudio2VoiceCallback)
{
    // Called just before this voice's processing pass begins.
    STDMETHOD_(void, OnVoiceProcessingPassStart) (THIS_ UINT32 BytesRequired) PURE;

    // Called just after this voice's processing pass ends.
    STDMETHOD_(void, OnVoiceProcessingPassEnd) (THIS) PURE;

    // Called when this voice has just finished playing a buffer stream
    // (as marked with the XAUDIO2_END_OF_STREAM flag on the last buffer).
    STDMETHOD_(void, OnStreamEnd) (THIS) PURE;

    // Called when this voice is about to start processing a new buffer.
    STDMETHOD_(void, OnBufferStart) (THIS_ void* pBufferContext) PURE;

    // Called when this voice has just finished processing a buffer.
    // The buffer can now be reused or destroyed.
    STDMETHOD_(void, OnBufferEnd) (THIS_ void* pBufferContext) PURE;

    // Called when this voice has just reached the end position of a loop.
    STDMETHOD_(void, OnLoopEnd) (THIS_ void* pBufferContext) PURE;

    // Called in the event of a critical error during voice processing,
    // such as a failing xAPO or an error from the hardware XMA decoder.
    // The voice may have to be destroyed and re-created to recover from
    // the error.  The callback arguments report which buffer was being
    // processed when the error occurred, and its HRESULT code.
    STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) PURE;
};

Эта реализация интерфейса COM, которая прекрасно работает для вызова библиотечных функций в Python. Однако, наоборот, мне нужно сделать.

Здесь:

class METHOD(object):
    '''COM method.'''
    def __init__(self, restype, *args):
        self.restype = restype
        self.argtypes = args

    def get_field(self):
        return ctypes.WINFUNCTYPE(self.restype, *self.argtypes)

class STDMETHOD(METHOD):
    '''COM method with HRESULT return value.'''
    def __init__(self, *args):
        super(STDMETHOD, self).__init__(ctypes.HRESULT, *args)

class COMMethodInstance(object):
    '''Binds a COM interface method.'''
    def __init__(self, name, i, method):
        self.name = name
        self.i = i
        self.method = method

    def __get__(self, obj, tp):
        if obj is not None:
            def _call(*args):
                ret = self.method.get_field()(self.i, self.name)(obj, *args)
                return ret
            return _call

        raise AttributeError()

class COMInterface(ctypes.Structure):
    '''Dummy struct to serve as the type of all COM pointers.'''
    _fields_ = [
        ('lpVtbl', ctypes.c_void_p),
    ]

class InterfaceMetaclass(type(ctypes.POINTER(COMInterface))):
    '''Creates COM interface pointers.'''
    def __new__(cls, name, bases, dct):
        methods = []
        for base in bases[::-1]:
            methods.extend(base.__dict__.get('_methods_', ()))
        methods.extend(dct.get('_methods_', ()))

        for i, (n, method) in enumerate(methods):
            dct[n] = COMMethodInstance(n, i, method)

        dct['_type_'] = COMInterface

        return super(InterfaceMetaclass, cls).__new__(cls, name, bases, dct)

Interface = InterfaceMetaclass(str('Interface'), (ctypes.POINTER(COMInterface),), {
    '__doc__': 'Base COM interface pointer.',
    })

class IUnknown(Interface):
    _methods_ = [
        ('QueryInterface', STDMETHOD(REFIID, ctypes.c_void_p)),
        ('AddRef', METHOD(ctypes.c_int)),
        ('Release', METHOD(ctypes.c_int))
    ]

Я пытался сделать что-то подобное, но, похоже, не работает:

def testfunc1(value):
    print("testfunc1")

def testfunc2():
    print("testfunc2")

# Call back types.
cb_buffer = ctypes.WINFUNCTYPE(None, POINTER(None))  # onBufferEnd,onBufferStart, onLoopEnd
cb_start = ctypes.WINFUNCTYPE(None, UINT32)  # onVoiceProcessingPassStart
cb_end = ctypes.WINFUNCTYPE(None)  # onStreamEnd, onVoiceProcessingPassEmd
cb_voice_error = ctypes.WINFUNCTYPE(None, POINTER(None), HRESULT)

class IXAudio2VoiceCallback(com.Interface):
    _methods_ = [
        ('OnVoiceProcessingPassStart',
         cb_start(testfunc1)),
        ('OnVoiceProcessingPassEnd',
         cb_end(testfunc2)),
        ('onStreamEnd',
         cb_end(testfunc2)),
        ('onBufferStart',
         cb_buffer(testfunc1)),
        ('OnBufferEnd',
         cb_buffer(testfunc1)),
        ('OnLoopEnd',
         cb_buffer(testfunc1))
    ]

Любые предложения с благодарностью

...