SWIG typemap для возврата выходного параметра char */size_t

#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
  

Здесь не так много вариантов для аккуратных интерфейсов, потому что ваши требования довольно ограничительные:

  1. не прибегая к исправлениям, специфичным для языка.
  2. У меня нет возможности изменять API. Я просто хочу упростить его использование.

С учетом этих двух факторов не так много остается открытым для использования.

Лично я предпочитаю писать некоторые специфические для Python C, если это делает интерфейс, который пользователи Python видят более естественным, даже если это означает, что тот же интерфейс не может быть на 100% дублирован на другом языке. Здесь есть гораздо более аккуратные решения для небольшой дополнительной работы с языком, которая обычно окупается.

Я бы сказал, что сила SWIG не в том, чтобы «писать один раз, импортировать куда угодно», а в том, чтобы помочь вам абстрагировать и модулировать языковые части интуитивно понятного интерфейса.

Комментарии:

1. Недостатком этого является то, что мне приходится писать отдельную функцию для каждой функции API, которую я хочу обработать. Я ищу универсальное решение, которое работает для всех аналогичных (с точки зрения выходных параметров) функций API. Карты типов, похоже, делают это на основе типа аргумента и имени, но нет готовых, которые делают то, что я хочу.

2. Общий совет, который я бы дал, заключается в том, что вы можете выбрать только 2 из этих 3 вещей: интуитивно понятный интерфейс, переносимость языка, минимальный дублирующий шаблонный код. Тем не менее, я посмотрю, есть ли что-нибудь еще, что могло бы быть работоспособным. В этом случае чрезвычайно сложно избежать исправлений, зависящих от языка, поскольку не все языки могут динамически возвращать объекты кортежа.

3. @mojo Я добавил еще один пример, который более похож на ваши карты типов. Мне все еще не очень нравится это, хотя по сравнению с выполнением специфичных для Python вещей, и это никогда не сможет работать для динамически типизированных языков без дополнительной поддержки, но это, вероятно, лучшее, что вы можете получить за то, что вы просите.

4. Я бы поставил вам 2, если бы мог. Этот ответ был очень полезен (для моего понимания тоже).