SWIG в типографике работает, но аргумент не

У меня есть этот файл foobar.h

class Foobar {
public: void method(int arg[2]) {};
};

После компиляции интерфейса SWIG на Python, если я пытаюсь запустить этот метод с Python, он говорит

TypeError: in method 'Foobar_method', argument 2 of type 'int [2]'

Конечно. Поэтому я пишу эту картографическую карту SWIG:

%typemap(in) int [2] {}

и когда я скомпилирую это, Python запускает этот метод без жалоб. Поэтому я думаю, что я понимаю, как писать типовую карту.

Но если я изменю карту-страницу на argout:

%typemap(argout) int [2] {}

Теперь Python возвращается к предыдущей ошибке.

Я просто делаю это непосредственно из руководства SWIG, это должно работать без этой ошибки, как in typemap.

Что я делаю неправильно?

Ответ 1

Что не так?

Короче говоря, это не предложение или предложение с этими типами.

Ключевой бит информации, которую вам не хватает, заключается в том, как несколько типов типов взаимодействуют для обертывания одной функции.

argout вставляется в созданную оболочку после вызова. Это ваша возможность скопировать (теперь измененный) вход обратно на Python разумным способом.

Это не затрагивает проблему того, как аргумент создается и передается перед вызовом.

Вы можете увидеть это достаточно четко, проверив код, сгенерированный этим интерфейсом:

%module test

%{
#include "test.h"
%}

%typemap(in) int[2] {
  // "In" typemap goes here
}

%typemap(argout) int[2] {
  // "argout" goes here
}

%include "test.h"

Что, когда test.h является вашим примером, выполните:

  // ... <snip>
  arg1 = reinterpret_cast< Foobar * >(argp1);
  {
    // "In" typemap goes here
  }
  (arg1)->method(arg2);
  resultobj = SWIG_Py_Void();
  {
    // "argout" goes here
  }
  return resultobj;
  // ... <snip>

В этих typemaps цель "in" typemap состоит в том, чтобы сделать arg2 разумным значением перед вызовом, а карта "argout" должна сделать что-то разумное со значениями после вызова (возможно, изменив возвращаемое значение if вы хотите).


Что должно быть в typemaps?

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

Для этого нам нужно сначала изменить карту ввода, попросив SWIG создать временный массив для нас:

Важно, чтобы мы сделали SWIG для этого, используя обозначение добавления скобок после типа вместо того, чтобы добавлять его в тело типовой карты, чтобы область была правильной для этой переменной. (Если бы мы не были временными, они не были бы доступны из карты "argout" еще и были бы очищены до того, как сам вызов был сделан даже).

%typemap(in) int[2] (int temp[2]) {
  // If we defined the temporary here then it would be out of scope too early.
  // "In" typemap goes here
}

Код, созданный SWIG, теперь включает в себя этот временный массив для нас, поэтому мы хотим использовать API Python C для итерации по нашему вводу. Это может выглядеть примерно так:

%typemap(in) int[2] (int temp[2]) {
  // "In" typemap goes here:
  for (Py_ssize_t i = 0; i < PyList_Size($input); ++i) {
    assert(i < sizeof temp/sizeof *temp); // Do something smarter
    temp[i] = PyInt_AsLong(PyList_GetItem($input, i)); // Handle errors
  }
  $1 = temp; // Use the temporary as our input
}

(Мы могли бы использовать протокол итератора Python вместо этого, если бы мы предпочли).

Если мы скомпилируем и запустим интерфейс, то нам будет достаточно, чтобы передать вход, но ничего не возвращается. Прежде чем мы напишем "aboutout" typemap, в сгенерированном коде заметьте еще одну вещь. Наш временный массив в сгенерированном коде фактически выглядит как int temp2[2]. Это не ошибка. SWIG по умолчанию переименовал переменную, которая должна быть выведена из позиции аргумента, чтобы разрешить одну и ту же типовую карту применять несколько раз к одному вызову функции, если требуется один аргумент.

В моей "аргументированной" типовой карте я собираюсь вернуть другой список Python с новыми значениями. Это далеко не единственный разумный выбор - есть другие варианты, если вы предпочитаете.

%typemap(argout) int[2] {
  // "argout" goes here:
  PyObject *list = PyList_New(2);
  for (size_t i = 0; i < 2; ++i) {
    PyList_SetItem(list, i, PyInt_FromLong(temp$argnum[i]));
  }
  $result = list;
}

В этой связи две точки примечания: во-первых, нам нужно написать temp$argnum явно, чтобы соответствовать преобразованию, которое SWIG сделал для нашего временного массива, а во-вторых, что мы используем $result в качестве вывода.

Чистые выходные аргументы

Часто мы имеем аргумент, который используется только для вывода, а не для ввода. Для них нет смысла принуждать пользователя Python предоставлять список, который просто игнорируется.

Мы можем это сделать, изменив карту "in" , используя numinputs=0, чтобы указать, что от Python не ожидается никакого ввода. Вы также должны позаботиться о том, чтобы инициализировать временное место здесь. Типовая карта теперь просто:

%typemap(in,numinputs=0) int[2] (int temp[2]) {
  // "In" typemap goes here:
  memset(temp, 0, sizeof temp);
  $1 = temp;
}

Итак, теперь в "typemap" фактически не вводится какой-либо вход с Python. Его можно рассматривать как просто подготовку ввода к собственному вызову.

В обратном направлении вы можете избежать использования имени, используемого SWIG (при этом не имея возможности использовать одну и ту же типовую карту несколько раз для одной и той же функции или с другой типовой страницей, которая имеет столкновение имен) с помощью noblock=1 в картотеке "in" . Я бы не рекомендовал этого.

Длина нефиксированного массива?

Наконец, стоит отметить, что мы можем написать все эти typemaps, чтобы они были более универсальными и работали для любого фиксированного размера. Для этого мы заменяем 2 на "ЛЮБОЙ" в сопоставлении с картами, а затем используем $1_dim0 вместо 2 внутри тел типовой карты, поэтому весь интерфейс в конце этого становится:

%module test

%{
#include "test.h"
%}

%typemap(in,numinputs=0) int[ANY] (int temp[$1_dim0]) {
  // "In" typemap goes here:
  memset(temp, 0, sizeof temp);
  $1 = temp;
}

%typemap(argout) int[ANY] {
  // "argout" goes here:
  PyObject *list = PyList_New($1_dim0);
  for (size_t i = 0; i < $1_dim0; ++i) {
    PyList_SetItem(list, i, PyInt_FromLong(temp$argnum[i]));
  }
  $result = list;
}

%include "test.h"