【问题标题】:C std::string as output parameters in Java with SWIG [duplicate]C std::string 作为带有 SWIG 的 Java 中的输出参数 [重复]
【发布时间】:2012-08-14 15:40:55
【问题描述】:

我需要用 SWIG 包装一个 C++ 库,以便在 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 * 输出参数 (Passing multiple parameters and allocating strings in C using Swig/Python) 的裁判的问题,但在我的情况下应该是 std::string 并且 SWIG 的文档没有提到这种情况 enter link description here。此外,我可能会遇到更多返回 3 个或更多输出参数的方法,可能具有不同的类型。

最后我对接口有了一点控制,我也在开发一个类作为库的入口点,但它只是将调用传递给真正的实现。

例如,通过这种方式,我设法将method3(std::string & s) 之类的方法更改为method3(const std::string & s),因此我可以在Java 中将其与普通String 一起使用。

所以稍微修改方法签名是可能的,但是如果本地方法返回 n 个输出参数,我应该返回所有参数(我不能创建新方法来返回每个参数)。

更新: 我一直在研究 Flexo 提供的解决方案并且效果很好,但是我正在考虑做一个类来包装 std::string 并使用它与返回的字符串进行交互,这是与 Flexo 的第二种解决方案非常相似的方法,但是使用这个 StringWrapper 而不是使用 java String 数组,基本上看起来像这样:

/*
* 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 更好,结构解决方案(通过 Flexo),通过 Flexo 的字符串数组或 this 指针(就像只有一个成员的结构。

【问题讨论】:

  • 你真的需要使用 SWIG 吗?我认为使用JavaCPP... 可以更轻松地完成此操作
  • @SamuelAudet 实际上我必须使用 SWIG。我们在 JNA 和 SWIG 之间做了一点比较,SWIG 的速度要快得多。 JavaCPP 的性能如何?
  • “更快”是什么意思?更快的编码还是更快的性能?对于前者,我不确定,但对于后者,JavaCPP 可能具有更好的性能,因为与 SWIG 不同,它使用直接 NIO 缓冲区之类的东西。

标签: java c++ swig


【解决方案1】:

假设您想在不修改现有头文件的情况下包装它,有两种方法可以想到。给定我用于测试的头文件:

#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::pairboost::tuple%template 一起使用,但我怀疑包装boost::tuple 并非易事,并且像这样您可以为您的图书馆用户理解的适当名称命名成员,而不仅仅是firstsecond,不使用 %rename,这比在 %inline 中编写自定义结构更加冗长。


另外,SWIG 提供了 OUTPUT 类型映射,您可以将其与 %apply 一起使用来创建输出参数。这些被包装为一个包含 1 个元素的数组——传递数组的语义与输出参数的语义相匹配。不幸的是,typemaps.i 中没有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 代替)。在一般情况下,无需编写任何额外代码即可应用 OUTPUT 类型映射。

【讨论】:

  • 感谢您的详细回答@Flexo。我目前正在尝试这个并且效果很好。事实上,我忘了在问题中提到它,这些函数是类的成员函数,所以我使用 %extend,也使用 %ignore 指令。我有什么疑问,根据 swig 文档,它不会复制返回的变量,所以在第一个解决方案中,当创建结构时,不应该创建内存保留吗?
  • @JavierMr - 第一个示例运行正常 - 它使用 Method2Result 的复制构造函数在堆上分配一个新实例。 (如果您仍然不相信,请查看生成的包装器代码)。如果你愿意,你可以通过自己使用new 并返回一个指针而不是按值来避免这种情况。在这种情况下,您需要使用 %newobject 让 SWIG 知道它仍应从 Java 端管理内存。
  • @Flexo- 谢谢,我仍然不习惯复制构造函数和重载运算符。我已经用其他方法更新了我的问题,包括使用指向字符串的指针,类似于您的第二个解决方案。我认为比第一个解决方案更好,因为这样做我不必为每个返回值声明一个 typedef 结构。与第二种解决方案非常相似,但避免使用数组,希望使其更直观。
  • 您的类型图效果很好。多谢。这正是我正在寻找的。​​span>
  • 作为旁注,当您使用 %inline 或 %extend 时,请确保在单词 inline 或 extend 之后添加空格。使用 %inline%{ 将导致错误。而 %inline %{ 不会。
【解决方案2】:

如果您支持 C++11,则可以返回 boolstd::stringstd::string 中的 std::tuple

否则,您可以创建嵌套的std::pairs

【讨论】:

  • 谢谢,我会看看 std::tuple 是如何工作的,以及它是否在 swig 中支持 C++11。
  • 不,我没有 C11 支持。 boost 可能是一个选项,但我宁愿不包含与其他库的更多依赖项。
  • @JavierMr 然后你可以使用嵌套的std::pairs
  • 据我所知,自c++0x以来,c++中添加的元组。
  • @raypixar C++0x 不是标准。
【解决方案3】:

只是为了好玩,这就是我们使用 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();
}

我很想听听 SWIG 如何成为更好的解决方案!

【讨论】:

  • 谢谢。如果有机会我会看的,但现在我必须继续痛饮。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-22
  • 2018-09-15
  • 1970-01-01
  • 2011-07-26
相关资源
最近更新 更多