SWIG, как сделать указатель на функцию typedef в структуре, вызываемой из Python - PullRequest
0 голосов
/ 21 ноября 2018

TL; DR Кто-нибудь знает, как проинструктировать SWIG обрабатывать эти члены C-структуры как указатель на функцию и делать ее вызываемой из Python?

Полная история У меня есть C-структуры, которые содержат указатели на функции.Все функции определены по типу.У меня есть функция C, которая будет выделять память для этой структуры C и которая будет устанавливать указатели на функции для указания на допустимые функции C.Мой упрощенный заголовочный файл выглядит следующим образом

// simplified api.h
typedef void *handle_t;
typedef void sample_t;
typedef error_t comp_close_t(handle_t *h);
typedef error_t comp_process_t(handle_t h,
                               sample_t *in_ptr,
                               sample_t *out_ptr,
                               size_t *nr_samples);
typedef struct
{
    comp_close_t *close;
    comp_process_t *process;
} audio_comp_t;

// prototype for init
error_t comp_init(handle_t *h, int size);

И соответствующий упрощенный исходный файл:

// simplified api.c
static comp_close_t my_close;
static comp_process_t my_process;

audio_comp_t comp = {
    my_close,
    my_process
};

error_t comp_init(audio_comp_t **handle) {
    *handle = ∁
    return 0;
}

error_t my_close(handle_t *h) {
    // stuff
    *h = NULL;
    return 0;
}

error_t my_process(handle_t h,
                   sample_t *in_ptr,
                   sample_t *out_ptr,
                   size_t *nr_samples) {
    audio_comp_t *c = (audio_comp_t*) h;
    // stuff 
    printf("doing something useful\n");
}

И последняя версия моего файла интерфейса:

%module comp_wrapper
%{
#include "api.h"
%}

%include "api.h"

// Take care of the double pointer in comp_init
%ignore comp_init;
%rename(comp_init) comp_init_overload;
%newobject comp_init;

%inline %{
audio_comp_t* comp_init_overload(int size) {
    audio_comp_t *result = NULL;
    error_t err = comp_init(&result, size);

    if (SSS_NO_ERROR == err) {
        ...
    }

    return result;
}
%}

// wrap the process call to verify the process_t * function pointer    
%inline %{
sss_error_t call_process(   audio_comp_t *h, 
                            sample_t *in, 
                            sample_t *out, 
                            size_t nr_samples)
{
    return h->process(h, in, out, &nr_samples);
}
%}

IЯ хочу использовать SWIG для создания языковых привязок, чтобы я мог вызывать эти объектоподобные структуры с минимальным кодом платформы из Python.В конечном итоге я хочу использовать это как:

h = comp_init(50)
h.process(h, input_data, output_data, block_size)
h.close(h)

Однако SWIG обрабатывает эти указатели функций в этих структурах как объекты, поэтому всякий раз, когда я хочу вызвать их, я получаю

>>> h = comp_init(50)
>>> h.api.process()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'SwigPyObject' object is not callable

, я могуОбойти это можно с помощью чего-то вроде функции 'call_process', которую вы можете найти в файле интерфейса:

call_process(h, in, out, 32) 

, но для этого потребуется добавить дополнительную оболочку для всех функций-членов структуры, в то время как это не должно 'в этом нет необходимости, поскольку [в документации SWIG указано, что указатели функций полностью поддерживаются] [1]

Полагаю, мне следует написать некоторый код в файле интерфейса, чтобы SWIG знал, что он имеет дело с функцией, а не с SwigPyObject.

Существует некоторая информация о том, как обращаться с обратными вызовами (python), но в данном случае, похоже, ничего из этого не работает, в частности: Указатели на функцию вызова SWIG хранятся в структуре

или без дублирования более или менее всей информации из заголовочного файла в файл интерфейса: Использование SWIG wi-й указатель на функцию в C struct

и последнее, но не менее важное, кажется, есть разница, когда вы оборачиваете указатель на функцию в структуре, поэтому решение 5 не работает: Как обернутьc ++ функция, которая получает указатель на функцию в python, используя SWIG

Кто-нибудь знает, как проинструктировать SWIG обрабатывать эти члены C-структуры как указатель на функцию и делать ее вызываемой из Python?

1 Ответ

0 голосов
/ 24 ноября 2018

Самое простое решение - если мы заявим SWIG, что указатели на функции являются просто функциями-членами , то сгенерированная им оболочка будет работать очень хорошо.

Чтобы продемонстрировать, что в этом случае нам нужночтобы исправить несколько ошибок в вашем примере кода, в итоге я получил api.h, похожий на этот:

// simplified api.h
#include <stdint.h>
#include <stdlib.h>

typedef uint32_t api_error_t;

typedef void *handle_t;
typedef void sample_t;
typedef api_error_t comp_close_t(handle_t h);
typedef api_error_t comp_process_t(handle_t h,
                               sample_t *in_ptr,
                               sample_t *out_ptr,
                               size_t nr_samples);
typedef struct
{
    comp_close_t *close;
    comp_process_t *process;
} audio_comp_t;

// prototype for init
api_error_t comp_init(handle_t *new_h);

и api.c, выглядящий так:

#include "api.h"
#include <stdio.h>

// simplified api.c
static comp_close_t my_close;
static comp_process_t my_process;

audio_comp_t comp = {
    my_close,
    my_process
};

api_error_t comp_init(handle_t *handle) {
    *handle = &comp;
    return 0;
}

api_error_t my_close(handle_t h) {
    (void)h; // stuff
    return 0;
}

api_error_t my_process(handle_t h,
                   sample_t *in_ptr,
                   sample_t *out_ptr,
                   size_t nr_samples) {
    audio_comp_t *c = (audio_comp_t*) h;
    (void)c;(void)in_ptr;(void)out_ptr;// stuff 
    printf("doing something useful\n");
    return 0;
}

Счто на месте мы можем написать api.i как показано ниже:

%module api

%{
#include "api.h"
%}

%include <stdint.i>

%typemap(in,numinputs=0) handle_t *new_h (handle_t tmp) %{
    $1 = &tmp;
%}

%typemap(argout) handle_t *new_h %{
    if (!result) {
        $result = SWIG_NewPointerObj(tmp$argnum, $descriptor(audio_comp_t *), 0 /*| SWIG_POINTER_OWN */);
    }
    else {
        // Do something to make the error a Python exception...
    }
%}

// From my earlier answer: https://stackoverflow.com/a/11029809/168175
%typemap(in,numinputs=0) handle_t self "$1=NULL;"
%typemap(check) handle_t self {
  $1 = arg1;
}

typedef struct {
  api_error_t close(handle_t self);
  api_error_t process(handle_t self,
                      sample_t *in_ptr,
                      sample_t *out_ptr,
                      size_t nr_samples);
} audio_comp_t;

%ignore audio_comp_t;
%include "api.h"

Здесь мы сделали несколько вещей, помимо сокрытия исходной структуры и утверждения, что она полна функций-членов вместо указателей на элементы:

  1. Сделать так, чтобы SWIG автоматически передавал дескриптор в качестве 1-го аргумента , вместо того, чтобы требовать от пользователей Python чрезмерно многословного.(В python он становится h.close() вместо h.close(h))
  2. Используйте argout typemap, чтобы обернуть настоящую функцию comp_init вместо простой ее замены.Это чисто вопрос предпочтений. Я просто добавил его, чтобы показать, как его можно использовать.

Это позволяет мне запустить следующий Python:

import api

h=api.comp_init()
print(h)
h.process(None, None, 0)
h.close()

Мы можем сделатьчто-то, что будет хорошо работать как для Python, так и для C, если вы хотите внести некоторые косметические изменения в заголовок вашего API, чтобы облегчить задачу.

Я ввел макрос в api.h, MAKE_API_FUNC, которыйпереносит операторы typedef, которые у вас были изначально.При компиляции с компилятором C он все еще дает те же самые результаты, однако он позволяет нам лучше управлять вещами с помощью SWIG.

Так что api.h теперь выглядит так:

// simplified api.h
#include <stdint.h>
#include <stdlib.h>

typedef uint32_t api_error_t;

typedef void *handle_t;
typedef void sample_t;
#ifndef MAKE_API_FUNC
#define MAKE_API_FUNC(name, type, ...) typedef api_error_t comp_ ## name ## _t(__VA_ARGS__)
#endif

MAKE_API_FUNC(close, audio_comp_t, handle_t);
MAKE_API_FUNC(process, audio_comp_t, handle_t, sample_t *, sample_t *, size_t);

typedef struct
{
    comp_close_t *close;
    comp_process_t *process;
} audio_comp_t;

// prototype for init
api_error_t comp_init(handle_t *new_h);

Так что вapi.i теперь мы заменим этот макрос другим макросом, который заявляет SWIG, что указатель функции typedef на самом деле представляет собой структуру со специально предоставленной функцией __call__.Создавая эту дополнительную функцию, мы можем автоматически проксировать все наши аргументы Python в вызове указателя реальной функции.

%module api

%{
#include "api.h"
%}

%include <stdint.i>

// From: https://stackoverflow.com/a/2653351
#define xstr(a) str(a)
#define str(a) #a

#define name_arg(num, type) arg_ ## num
#define param_arg(num, type) type name_arg(num, type)

#define FE_0(...)
#define FE_1(action,a1) action(0,a1)
#define FE_2(action,a1,a2) action(0,a1), action(1,a2)
#define FE_3(action,a1,a2,a3) action(0,a1), action(1,a2), action(2,a3)
#define FE_4(action,a1,a2,a3,a4) action(0,a1), action(1,a2), action(2,a3), action(3,a4)
#define FE_5(action,a1,a2,a3,a4,a5) action(0,a1), action(1,a2), action(2,a3), action(3,a4), action(4,a5)

#define GET_MACRO(_1,_2,_3,_4,_5,NAME,...) NAME
%define FOR_EACH(action,...)
  GET_MACRO(__VA_ARGS__, FE_5, FE_4, FE_3, FE_2, FE_1, FE_0)(action,__VA_ARGS__)
%enddef

%define MAKE_API_FUNC(name, api_type, ...)
%nodefaultctor comp_ ## name ## _t;
%nodefaultdtor comp_ ## name ## _t;
typedef struct {
    %extend {
        api_error_t __call__(FOR_EACH(param_arg, __VA_ARGS__)) {
            return $self(FOR_EACH(name_arg, __VA_ARGS__));
        }
    }
} comp_ ## name ## _t;
// Workaround from: https://github.com/swig/swig/issues/609
%rename("%s_fptr", "%$isvariable", "match$ismember"="1", "match$type"=xstr(comp_ ## name ## _t)) name;
%extend api_type {
    %pythoncode %{
        name = lambda self, *args: self.name ## _fptr(self, *args)
    %}
}
%enddef

%ignore comp_init;
%include "api.h"

%extend  audio_comp_t {
    audio_comp_t() {
        handle_t new_h = NULL;
        api_error_t err = comp_init(&new_h);
        if (err) {
            // throw or set Python error directly
        }
        return new_h;
    }

    ~audio_comp_t() {
        (void)$self;
        // Do whatever we need to cleanup properly here, could actually call close
    }
}

При этом используются те же самые механизмы препроцессора , которые я использовал в мой ответ об упаковке std::function объектов , но применительно к указателям на функции этой проблемы.Кроме того, я использовал %extend, чтобы создать конструктор / деструктор с точки зрения Python, что делает API более привлекательным для использования.Возможно, я бы тоже использовал %rename, если бы это был настоящий код.

С учетом сказанного мы можем теперь использовать следующий код Python:

import api

h=api.audio_comp_t()
print(h)
print(h.process)
h.process(None, None, 0)

См. Документы SWIG для обсуждения того, как правильно сопоставить коды ошибок с исключениями для Python.


Мы можем упростить это далее, устраняя необходимость перебирать аргументы макроса variadic с помощью одного простоготрюк.Если мы изменим наш макрос api.h так, чтобы он принимал 3 аргумента, 3-й из которых - все аргументы указателя функции, например:

// simplified api.h
#include <stdint.h>
#include <stdlib.h>

typedef uint32_t api_error_t;

typedef void *handle_t;
typedef void sample_t;
#ifndef MAKE_API_FUNC
#define MAKE_API_FUNC(name, type, args) typedef api_error_t comp_ ## name ## _t args
#endif

MAKE_API_FUNC(close, audio_comp_t, (handle_t self));
MAKE_API_FUNC(process, audio_comp_t, (handle_t self, sample_t *in_ptr, sample_t *out_ptr, size_t nr_samples));

typedef struct
{
    comp_close_t *close;
    comp_process_t *process;
} audio_comp_t;

// prototype for init
api_error_t comp_init(handle_t *new_h);

Тогда мы можем теперь изменить наш интерфейс SWIG, чтобы он не предоставлял определение__call__ функция, которую мы добавили через %extend, и вместо этого напишите макрос, который напрямую вызывает требуемый указатель на функцию:

%module api

%{
#include "api.h"
%}

%include <stdint.i>

// From: https://stackoverflow.com/a/2653351
#define xstr(a) str(a)
#define str(a) #a

%define MAKE_API_FUNC(name, api_type, arg_types)
%nodefaultctor comp_ ## name ## _t;
%nodefaultdtor comp_ ## name ## _t;
%{
#define comp_ ## name ## _t___call__(fptr, ...) fptr(__VA_ARGS__)
%}
typedef struct {
    %extend {
        api_error_t __call__ arg_types;
    }
} comp_ ## name ## _t;
// Workaround from: https://github.com/swig/swig/issues/609
%rename("%s_fptr", "%$isvariable", "match$ismember"="1", "match$type"=xstr(comp_ ## name ## _t)) name;
%extend api_type {
    %pythoncode %{
        name = lambda self, *args: self.name ## _fptr(self, *args)
    %}
}
%enddef

%ignore comp_init;
%include "api.h"

%extend  audio_comp_t {
    audio_comp_t() {
        handle_t new_h = NULL;
        api_error_t err = comp_init(&new_h);
        if (err) {
            // throw or set Python error directly
        }
        return new_h;
    }

    ~audio_comp_t() {
        (void)$self;
        // Do whatever we need to cleanup properly here, could actually call close
    }
}

Хитрость здесь заключалась в том, что использование идиомы typedef struct {...} name; сделало переименованиеили скрыть указатели функций внутри структуры сложнее.(Однако это было необходимо только для автоматического добавления аргумента handle_t).

...