C std:: string в качестве выходных параметров в Java с SWIG [дубликат]
На этот вопрос уже есть ответ здесь:
Мне нужно обернуть библиотеку C++ с SWIG, чтобы использовать ее с Java.
У меня уже есть несколько методов работы, но я столкнулся с ситуацией, в которой я не знаю, как ее решить.У меня есть пара методы, подобные этому:
void method1(std::string & name, std::string & result);
bool method2(std::string & name, std::string & alias, std::string & resurnValue, std::string & returnType);
Примечание: На самом деле это методы-члены класса с именем MyClass.
Я мог бы изменить первый метод, чтобы вернутьstd::string
вместо void
, и это должно сработать; но я понятия не имею, как обращаться со вторым методом, где два последних параметра являются выходными параметрами. Я видел пару вопросов судейства к char *
выходным парам (передача нескольких параметров и выделение строк в C с помощью Swig / Python ), но в моем случае должен быть std::string
и документация SWIG не упоминает эту ситуацию Введите описание ссылки здесь. Кроме того, я, вероятно, столкнусь с большим количеством методов, возвращающих 3 или более выходных параметров, вероятно, с различными типами.
Наконец, у меня есть немного контроля над интерфейсом, я также разрабатываю класс, который действует как точка входа в библиотеку, но он просто передает вызов реальной реализации.
Например, мне удалось изменить такой метод, как method3(std::string & s)
на method3(const std::string & s)
, поэтому я мог бы использовать его из Java с нормальным String
.
Таким образом, можно немного изменить сигнатуры методов, но если собственный метод возвращает n выходных параметров, я должен вернуть их все (я не могу создать новые методы для возврата каждого из них).
Обновление: Я смотрел на решение, данное Flexo и отлично работает, однако я рассматриваю возможность создания класса для обертывания std:: string и использования его для взаимодействия с возвращаемыми строками, очень похожий подход ко второму решение Flexo, но с использованием этого StringWrapper вместо использования массива строк java, в основном выглядит следующим образом:
/*
* The MyClass.i file
*/
%module example
%include "std_string.i"
%{
class StringPtr{
private:
stdString str;
public:
StringPtr(){
}
StringPtr(const stdString & str){
this->str = stdString(str);
}
stdString & getStrRef(){
return (this->str);
}
stdString getStrVal(){
return stdString(this->str);
}
~StringPtr(){
}
};
%}
/////////////////// Export StringPtr to Java
class StringPtr{
public:
StringPtr();
StringPtr(const stdString & str);
stdString getStrVal();
~StringPtr();
};
// I think this is nor necessary
%rename ("$ignore", fullname=1) "StringPtr::getStrRef";
%extend MyClass {
void method1(cons std::string & name, StringPtr & result){
$self->method1(name, result.getStrRef());
}
bool method2(cons std::string & name, cons std::string & alias, StringPtr & returnValue, StringPtr & returnType){
$self->method2(name, alias, returnValue.getStrRef(), returnType.getStrRef());
}
};
%rename ("$ignore", fullname=1) "MyClass::method1";
%rename ("$ignore", fullname=1) "MyClass::method2";
%include "MyClass.h"
Поэтому мне интересно, с точки зрения производительности, witch лучше, решение structs (по Flexo), строковый массив по Flexo или этот указатель (так же, как структура только с одним членом.
3 ответа:
Предполагая, что вы хотите обернуть это без изменения существующего файла заголовка, есть два способа, которые приходят на ум. Учитывая заголовочный файл, который я использовал для тестирования:
#include <string> inline bool method2(const std::string & name, const std::string & alias, std::string & resurnValue, std::string & returnType) { resurnValue = name; returnType = alias; return true; }
Самый простой способ обернуть его-использовать
%inline
для создания перегрузки, которая оборачивает все выходы в один тип:%module test %include <std_string.i> %{ #include "test.h" %} %inline %{ struct Method2Result { bool b; std::string s1; std::string s2; }; Method2Result method2(const std::string& in1, const std::string& in2) { Method2Result ret; ret.b = method2(in1,in2,ret.s1,ret.s2); return ret; } %} // Optional: don't wrap the original form of method2 at all: %ignore method2; %include "test.h"
Это работает с:
public class run { public static void main(String[] args) { System.loadLibrary("test"); Method2Result ret = test.method2("foo", "bar"); System.out.println(ret.getB() + " - " + ret.getS1() + ", " + ret.getS2()); } }
Вы могли бы использовать
std::pair
илиboost::tuple
с%template
, но обертываниеboost::tuple
нетривиально, я подозреваю, и таким образом вы получаете возможность назвать членов что-то подходящее что пользователи вашей библиотеки поймут, а не простоfirst
иsecond
, без использования%rename
, что становится более подробным, чем просто написание пользовательской структуры в%inline
.
Альтернативно SWIG предоставляет выходные карты типов, которые можно использовать с
%apply
для создания выходных аргументов. Они упаковываются в массив из 1 элемента-семантика передаваемых массивов совпадает с семантикой выходных аргументов. К сожалению, дляstd::string
в типографских картах такого нет.я, так что мы должны написать свои собственные. В идеале я бы повторно использовал макросOUTPUT_TYPEMAP
из этого файла и просто немного изменил типовую карту argout, но она получает#undef
ined без этого. К счастью, это довольно просто, чтобы просто дублировать и изменять для этого случая:%module test %{ #include "test.h" %} %typemap(jstype) std::string& OUTPUT "String[]" %typemap(jtype) std::string& OUTPUT "String[]" %typemap(jni) std::string& OUTPUT "jobjectArray" %typemap(javain) std::string& OUTPUT "$javainput" %typemap(in) std::string& OUTPUT (std::string temp) { if (!$input) { SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "array null"); return $null; } if (JCALL1(GetArrayLength, jenv, $input) == 0) { SWIG_JavaThrowException(jenv, SWIG_JavaIndexOutOfBoundsException, "Array must contain at least 1 element"); } $1 = &temp; } %typemap(argout) std::string& OUTPUT { jstring jvalue = JCALL1(NewStringUTF, jenv, temp$argnum.c_str()); JCALL3(SetObjectArrayElement, jenv, $input, 0, jvalue); } %apply std::string& OUTPUT { std::string & resurnValue } %apply std::string& OUTPUT { std::string & returnType } %include "test.h"
Это можно использовать так:
Оба они были протестированы и работали на моей системе. В данном случае мне нравится подходpublic class run { public static void main(String[] args) { String[] out1 = new String[1]; String[] out2 = new String[1]; boolean retb = test.method2("foo", "bar", out1, out2); System.out.println(retb + " - " + out1[0] + ", " + out2[0]); } }
%inline
. (Если бы это была функция-член, Вы бы использовали%extend
вместо этого). В общем случае выходные типовые карты могут быть однако применяется без написания какого-либо дополнительного кода.
Если у вас есть поддержка C++11, вы можете вернуть std:: tuple
bool
,std::string
иstd::string
.В противном случае можно создать вложенные пары std:: .
Просто для удовольствия, вот как мы могли бы сделать это с JavaCPP:
public static native boolean method2(@StdString String name, @StdString @Cast("char*") BytePointer alias, @StdString @Cast("char*") BytePointer returnValue, @StdString @Cast("char*") BytePointer returnType); public static void main(String[] args) { BytePointer alias = new BytePointer(); BytePointer returnValue = new BytePointer(); BytePointer returnType = new BytePointer(); method2("Unknown", alias, returnValue, returnType); alias.getString(); returnValue.getString(); returnType.getString(); }
Я хотел бы услышать, как глоток является лучшим решением!