swig get return type from variable в struct как строковый массив в java

Для небольшого проекта Java мне нужно было взаимодействовать с существующим кодом, написанным на C, поэтому сделать все просто (я, к сожалению, не программист на C / C ++ ..) Я решил использовать swig.

Первая проблема, с которой я столкнулся: функция C, которая вернула строку с разделителями в NULL, привела к завернутому коду, который вернул String, содержащий только первое значение, был разрешен 3 (!) Возможными решениями Flexo, предоставленными в: SWIG get returntype от String as Строковый массив в java

Продолжая разработку этого проекта, я столкнулся со второй проблемой с (аналогичным?) Шаблоном, который меня озадачивает: заголовочный файл содержит структуру «PROJECTDETAILS», которая (в свою очередь) содержит переменную itemList, которая должна содержать строку с NULL-ограничением. Сгенерированный swig getter для itemList возвращает первый результат как String, так же как и функцию GetProjects в моем исходном связанном вопросе (строка, содержащая первый результат). Я попытался применить ответ Flexo, предоставленный для этой проблемы, но, тем не менее, я «Невозможно« typemap »переменная itemList

Функция в файле заголовка C гласит:

typedef struct _PROJECT { int version; unsigned char vM; unsigned char fM; } * PROJECT; typedef struct _PROJECTDETAILS { int infoType; union { char *itemList; /* Returns a NULL-delimited string */ char *projectName; } info; } PROJECTDETAILS; DllImport PROJECT OpenProject dsproto((int, char *)); DllImport int GetProjectDetails dsproto((PROJECT, int, PROJECTDETAILS *)); 

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

 typedef struct _PROJECT { int version; unsigned char vM; unsigned char fM; } * PROJECT; #define INFOTYPE_ITEMLIST 0 #define INFOTYPE_PROJECTNAME 1 typedef struct _PROJECTDETAILS { int infoType; union { char *itemList; /* Returns a NULL-delimited string */ char *projectName; } info; } PROJECTDETAILS; static PROJECT OpenProject(int a, char *b) { (void)a;(void)b; static struct _PROJECT p = {100, 1, 2}; return &p; } static int GetProjectDetails(PROJECT p, int a, PROJECTDETAILS *out) { (void)p; // No idea what real impl does here if (a == 1) { out->infoType = INFOTYPE_ITEMLIST; out->info.itemList="Item 1\0Item 2\0Item 3\0"; } else { out->infoType = INFOTYPE_PROJECTNAME; out->info.projectName = "HELLO WORLD"; } return 0; } 

Это, в сочетании с ответом, который я дал в моем предыдущем ответе , достаточно, чтобы заставить itemList работать, хотя и в неуклюжем (для перспективы Java-разработчика) пути.

Чтобы правильно использовать шаблоны из предыдущего ответа, все, что нам нужно сделать, это выяснить, что их назвать (или что писать для %apply ). SWIG имеет очень удобный способ помочь нам понять это, -debug-tmsearch командной строки -debug-tmsearch который для каждого найденного поиска печатной карты печатает все кандидаты, которые считаются и игнорируются, потому что для них ничего не было введено. Поэтому я побежал:

 swig3.0 -java -Wall -debug-tmsearch test.i 

Который показывает пути, которые мы могли бы сопоставить с нашими примерами из предыдущего ответа.

 test.h:16: Searching for a suitable 'out' typemap for: char *_PROJECTDETAILS::_PROJECTDETAILS_info::itemList Looking for: char *_PROJECTDETAILS::_PROJECTDETAILS_info::itemList Looking for: char *itemList Looking for: char * Using: %typemap(out) char * 

Что показывает, что char *_PROJECTDETAILS::_PROJECTDETAILS_info::itemList является самым char *_PROJECTDETAILS::_PROJECTDETAILS_info::itemList соответствием для PROJECTDETAILS::info::itemList которому мы хотим применить нашу предыдущую карту. Таким образом, мы могли бы использовать предыдущие типовые символы в этом ответе и соответствовать по-разному (или даже использовать %apply для соответствия их нескольким обычаям), например:

 %module test %{ #include "test.h" #include  %} // See part 2 for discusson of these %rename("%(strip:[_])s") ""; %immutable _PROJECTDETAILS::infoType; %typemap(jni) char * _PROJECTDETAILS::_PROJECTDETAILS_info::itemList "jobjectArray"; %typemap(jtype) char * _PROJECTDETAILS::_PROJECTDETAILS_info::itemList "String[]"; %typemap(jstype) char * _PROJECTDETAILS::_PROJECTDETAILS_info::itemList "String[]"; %typemap(javaout) char * _PROJECTDETAILS::_PROJECTDETAILS_info::itemList { return $jnicall; } %typemap(out) char * _PROJECTDETAILS::_PROJECTDETAILS_info::itemList { size_t count = 0; const char *pos = $1; while (*pos) { while (*pos++); // SKIP ++count; } $result = JCALL3(NewObjectArray, jenv, count, JCALL1(FindClass, jenv, "java/lang/String"), NULL); pos = $1; size_t idx = 0; while (*pos) { jobject str = JCALL1(NewStringUTF, jenv, pos); assert(idx 

Это выбрало метод 2 из предыдущего ответа, главным образом потому, что он был полностью основан на карте, поэтому лучший пример аргумента SWIG -debug-tmsearch SWIG.

Этого достаточно, чтобы мы могли использовать его как:

 import java.util.Arrays; public class run { public static void main(String[] argv) { System.loadLibrary("test"); PROJECT p = test.OpenProject(1,"???"); PROJECTDETAILS pd1 = new PROJECTDETAILS(); test.GetProjectDetails(p, 1, pd1); System.out.println(Arrays.toString(pd1.getInfo().getItemList())); } } 

Но мы можем сделать намного лучше, чем для пользователей Java, создавая новый объект PROJECTDETAILS должен пройти, поскольку аргумент GetProjectDetails немного странный.


Чтобы аккуратно обернуть это в Java, есть немало вещей, которые вы хотите сделать, помимо переменной-члена с необычной семантикой для char * .

Сначала мы, вероятно, хотим переименовать некоторые из ваших структур. Вы можете сделать это в SWIG 2.0 и выше, используя расширенный оператор переименования.

Затем нам нужно решить, как обернуть самих участников. Типичным шаблоном проектирования в C является использование int для указания того, какой член объединения является правильным типом для данного объекта. В Python я просто возвращаю другой тип для каждого случая и полагаюсь на утиную печать. Для Java существует несколько различных вариантов, которые были бы разумными:

  1. Вы можете определить иерархию classов и использовать instanceof (или просто тип int), чтобы выяснить, как отличать правильный тип.
  2. Вы можете оставить его как есть и отобразить семантику C. (Технически доступ к «неправильному» члену - это неопределенное поведение, которое не очень интуитивно для разработчиков Java).
  3. Вы можете вернуть тип, который вызывает исключение, или вместо этого возвращает NULL, если вы попытаетесь получить доступ к «неправильному» члену.

Вариант 2 - это то, что было сделано в предыдущей части этого ответа. С моей точки зрения, вариант 3, вероятно, является самым предсказуемым поведением для Java-программиста, вот что я здесь сделал.

Третье веселое решение, которое нам нужно сделать, - это обработать аргумент выходной функции. В этом случае я собираюсь выбрать решение, которое поддерживает более Java-код более C / JNI, но здесь применяется тот же компромисс, что и с предыдущими ответами.

Так что я сделал, чтобы сказать SWIG игнорировать PROJECTDETAILS::info полностью, а также переименовать подчеркнутые структуры с подчеркиванием.

Затем я сделал версию GetProjectDetails в GetProjectDetails файле приватной и добавил суффикс Impl чтобы показать, что он не предназначен для того, чтобы кто-либо, кроме внутренних оболочек обертки, касался его.

Внутри самого модуля я использовал %pragma чтобы добавить еще одну общедоступную версию GetProjectDetails которая скрывает тот факт, что новый объект сконструирован для аргумента только для вывода, изменяет тип возвращаемого значения, чтобы вернуть это. Он также переключает стиль 'use int чтобы указать успех' C, в механизм Java, создающий исключение, если он идет не так. (В SWIG больше способов сделать это, чем это возможно, но это минимизирует кривую обучения C / JNI, чтобы сделать это так).

Затем добавляем две дополнительные переменные-члены только для PROJECTDETAILS в завернутую структуру PROJECTDETAILS . Они действительно не существуют в C напрямую, поэтому они реализованы в коде промежуточного интерфейса с помощью некоторого дополнительного кода C. Точка этого кода заключается в том, что он проверяет, в каком случае находится union , используя дополнительный член int который указывает тип. Если тип неправильный, они возвращают значение null (но либо C-код Java-кода может сделать это исключение).

Все, что мы можем сделать с этим, - это повторное использование typemaps из моего предыдущего ответа, чтобы получить семантику itemList работающую прямо по языковой границе. Опять же, я использовал метод 2 здесь, кроме незначительной проблемы с возвратом функций null, он не изменился.

 %module test %{ #include "test.h" #include  %} %rename("%(strip:[_])s") ""; %immutable _PROJECTDETAILS::infoType; %ignore info; // Ignore the member %ignore _PROJECTDETAILS_info; // Ignore the anonymous type %javamethodmodifiers GetProjectDetails "private"; %rename(GetProjectDetailsImpl) GetProjectDetails; %typemap(jni) char *_PROJECTDETAILS::itemList "jobjectArray"; %typemap(jtype) char *_PROJECTDETAILS::itemList "String[]"; %typemap(jstype) char *_PROJECTDETAILS::itemList "String[]"; %typemap(javaout) char *_PROJECTDETAILS::itemList { return $jnicall; } %typemap(out) char *_PROJECTDETAILS::itemList { if (!$1) return NULL; // This fixes a possible bug in my previous answer size_t count = 0; const char *pos = $1; while (*pos) { while (*pos++); // SKIP ++count; } $result = JCALL3(NewObjectArray, jenv, count, JCALL1(FindClass, jenv, "java/lang/String"), NULL); pos = $1; size_t idx = 0; while (*pos) { jobject str = JCALL1(NewStringUTF, jenv, pos); assert(idxinfoType != INFOTYPE_ITEMLIST) { // Throw a Java exception here instead? That is another question... return NULL; } return $self->info.itemList; } const char *projectName const { if ($self->infoType != INFOTYPE_PROJECTNAME) { // Throw exception? return NULL; } return $self->info.projectName; } } %include "test.h" 

Которая затем работает с:

 import java.util.Arrays; public class run { public static void main(String[] argv) { System.loadLibrary("test"); PROJECT p = test.OpenProject(1,"???"); System.out.println("PD1"); PROJECTDETAILS pd1 = test.GetProjectDetails(p, 1); System.out.println(Arrays.toString(pd1.getItemList())); System.out.println(pd1.getProjectName()); System.out.println("PD2"); PROJECTDETAILS pd2 = test.GetProjectDetails(p, 2); System.out.println(Arrays.toString(pd2.getItemList())); System.out.println(pd2.getProjectName()); } } 

(Примечание: все, что начинается с _, за которым следует заглавная буква, - это зарезервированное имя , возможно, не ваша ошибка, но не совсем отличная C)