Самое простое решение - если мы заявим 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 = ∁
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"
Здесь мы сделали несколько вещей, помимо сокрытия исходной структуры и утверждения, что она полна функций-членов вместо указателей на элементы:
- Сделать так, чтобы SWIG автоматически передавал дескриптор в качестве 1-го аргумента , вместо того, чтобы требовать от пользователей Python чрезмерно многословного.(В python он становится
h.close()
вместо h.close(h)
) - Используйте 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
).