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 3

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, но она получает #undefined без этого. К счастью, это довольно просто, чтобы просто дублировать и изменять для этого случая:

%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();
}

Я хотел бы услышать, как глоток является лучшим решением!