#python #swig
#python #swig
Вопрос:
У меня есть C API, который использует выходные параметры для строк. (Реальная подпись была изменена для защиты невиновных. Это упрощенный пример.)
void func( char* buf, size_t buflen, char* buf2, size_t buf2len );
buf
и buflen
фактически являются выходными параметрами, где buflen
и buf2len
— (уже) выделенный размер этих буферов.
В вызывающем коде я не хочу передавать какие-либо параметры. Скорее, я хочу, чтобы возвращались строки.
result1,result2 = func()
Я бы предпочел не передавать буфер / размер функции-оболочке, а скорее выделить его оболочкой, превратить в строку Python и освободить перед возвратом строки Python.
Большинство карт типов cstring.i, которые я вижу, связанных с этим, требуют, чтобы я предоставил функции-оболочке строку. Все карты типов распределения хотят иметь char**
.
Я ищу поведение, аналогичное использованию OUTPUT
в качестве имени параметра outparam, но пара буфер / размер — это (одиночный) параметр outparam.
У меня нет возможности изменять API. Я просто хочу сделать его простым в использовании.
Есть ли уже типовая карта для этого, или вы можете помочь мне создать ее?
Пробная версия 1 (только для Python)
Я заставил это функционировать (без проверки производительности или использования памяти).
%typemap(in,numinputs=0)(char* mutable_buffer, size_t mutable_buffer_size) {
$1 = malloc(sizeof(char)*4096);
$1[0] = 0x0;
$2 = 4096;
}
%typemap(argout)(char* mutable_buffer, size_t mutable_buffer_size) {
#ifdef SWIGPYTHON
PyObject *o;
$1[4095] = 0x0; // null-terminate it, just in case
o = PyUnicode_FromString($1);
resultobj = SWIG_Python_AppendOutput(resultobj,o);
#endif
}
%typemap(freearg)(char* mutable_buffer, size_t mutable_buffer_size) {
free($1);
}
Я бы предпочел решить эту проблему, не прибегая к исправлениям, специфичным для языка.
Комментарии:
1. Если вам нужен вывод, он должен быть
char**
иначе вы не сможете изменить указатель.2. @JensMunk Если вы передаете ранее выделенное значение
char*
иsize_t
указали, насколько оно велико, функция может записывать в него данные, не выделяя их.3. Вы правы, но тогда это также ввод — довольно раздражающий способ обработки выходных данных. Если выходные данные обрабатываются таким образом, имело бы смысл предоставить функцию, которая возвращает целое число, размер которого пользователь должен выделить перед вызовом func
4. В этом случае, как сопровождающий оболочки, я могу знать некоторые разумные границы для размещения в строках. С точки зрения клиента (особенно на языке сценариев), они не хотят обманывать с размерами строк и т.д. Если я знаю, что все всегда будет соответствовать 4096 байтам, тогда клиенту не нужно сообщать мне, насколько большим это может быть, и я могу просто вернуть строку.
5. В этом случае есть много вариантов. Вы можете включить
%include "carrays.i"
и%array_class(char, charArray)
.. выделить массив символов с помощьюcarr = lib.charArray(4096)
и просто передать это в качестве аргумента.
Ответ №1:
Поскольку вы специально запросили решения, для поддержки которых не требуется ничего специфичного для языка, я бы предложил использовать %inline
для предоставления альтернативной формы func
, которая имеет синтаксис, который вы предпочитаете. Что-то вроде этого должно это сделать:
%module test
%rename(func) func_adjusted;
%newobject func_adjusted();
%inline %{
struct func1_out {
// can make tuple syntax work here if you would rather, but likely having names is clearer anyway
char buf1[4096];
size_t buf1len;
char buf2[4096];
size_t buf2len;
};
// with the rename we pretend this is the C function, but really it is specific to our wrapper module.
struct func1_out *func_adjusted() {
struct func1_out *ret = malloc(sizeof *ret);
// could also dynamically allocate buf1 and buf2 instead of fixed max size.
func(buf1, amp;buf1len, buf2, amp;buf2len);
return ret;
}
%}
%ignore func;
%include "blahblah.h"
Вероятно, вам захочется немного поработать с char buf1
и buf2
массивами внутри этой структуры, чтобы сделать ее более естественной для пользователя Python, но это можно сделать с помощью carrays.i или %extend
в структуре, чтобы сохранить все это в C / SWIG и не привязывать к Python.
Вы также можете сделать что-то, что не относится конкретно к функции, которую вы переносите, используя карты типов с несколькими аргументами. Чтобы избежать привязки к Python, вы можете использовать %append_output
для возврата нескольких элементов из одной функции. Это не работает для статически типизированных языков, таких как Java или C #, но должно работать для большинства / всех динамически типизированных.
Чтобы в дальнейшем избежать необходимости в дополнительном, специфичном для языка коде, мы можем использовать carrays.i, который генерирует некоторые дополнительные функции для каждого типа, который мы определяем. В частности, мы используем ByteArray_cast
, new_ByteArray
и delete_ByteArray
для обработки довольно большого числа случаев, с которыми мы можем столкнуться. Это должно работать как для C, так и для C случаев.
%module test
%include <carrays.i>
%array_class(char, ByteArray);
%typemap(in,numinputs=0) (char* mutable_buffer, size_t mutable_buffer_size) (ByteArray *tmp=NULL) {
// N.B. using new_ByteArray() here makes this work well with both C and C code
tmp = new_ByteArray(4096);
$1 = ByteArray_cast(tmp);
$1[0] = 0x0;
$2 = 4096;
}
%typemap(freearg) (char* mutable_buffer, size_t mutable_buffer_size) {
// conditional is needed here as in some cases delete_ByteArray would dereference a null pointer
if (tmp$argnum) delete_ByteArray(tmp$argnum);
}
%typemap(argout) (char* mutable_buffer, size_t mutable_buffer_size) {
// Take ownership from in typemap
%append_output(SWIG_NewPointerObj(SWIG_as_voidptr(tmp$argnum), $descriptor(ByteArray*), SWIG_POINTER_NEW));
tmp$argnum = NULL;
}
%apply (char *mutable_buffer, size_t mutable_buffer_size) { (char *buf, size_t buflen), (char *buf2, size_t buf2len) };
%inline %{
void func(char* buf, size_t buflen, char* buf2, size_t buf2len) {
strncpy(buf, "this is buffer1", buflen);
strncpy(buf2, "this is buffer2", buf2len);
}
%}
Это работает, как и ожидалось, с Python 3.7 в моем тестировании. Обратите внимание, что вам нужно будет запустить swig с -builtin
аргументом, чтобы получить точное поведение, которое вы ищете здесь, или потребовать дополнительный пользовательский код для обходного пути:
a,b = test.func()
# needed for the non builtin case, I can't see a way to avoid that without Python specific code
aa=test.ByteArray_frompointer(a)
# for the -builtin case it just works though
Здесь не так много вариантов для аккуратных интерфейсов, потому что ваши требования довольно ограничительные:
- не прибегая к исправлениям, специфичным для языка.
- У меня нет возможности изменять API. Я просто хочу упростить его использование.
С учетом этих двух факторов не так много остается открытым для использования.
Лично я предпочитаю писать некоторые специфические для Python C, если это делает интерфейс, который пользователи Python видят более естественным, даже если это означает, что тот же интерфейс не может быть на 100% дублирован на другом языке. Здесь есть гораздо более аккуратные решения для небольшой дополнительной работы с языком, которая обычно окупается.
Я бы сказал, что сила SWIG не в том, чтобы «писать один раз, импортировать куда угодно», а в том, чтобы помочь вам абстрагировать и модулировать языковые части интуитивно понятного интерфейса.
Комментарии:
1. Недостатком этого является то, что мне приходится писать отдельную функцию для каждой функции API, которую я хочу обработать. Я ищу универсальное решение, которое работает для всех аналогичных (с точки зрения выходных параметров) функций API. Карты типов, похоже, делают это на основе типа аргумента и имени, но нет готовых, которые делают то, что я хочу.
2. Общий совет, который я бы дал, заключается в том, что вы можете выбрать только 2 из этих 3 вещей: интуитивно понятный интерфейс, переносимость языка, минимальный дублирующий шаблонный код. Тем не менее, я посмотрю, есть ли что-нибудь еще, что могло бы быть работоспособным. В этом случае чрезвычайно сложно избежать исправлений, зависящих от языка, поскольку не все языки могут динамически возвращать объекты кортежа.
3. @mojo Я добавил еще один пример, который более похож на ваши карты типов. Мне все еще не очень нравится это, хотя по сравнению с выполнением специфичных для Python вещей, и это никогда не сможет работать для динамически типизированных языков без дополнительной поддержки, но это, вероятно, лучшее, что вы можете получить за то, что вы просите.
4. Я бы поставил вам 2, если бы мог. Этот ответ был очень полезен (для моего понимания тоже).