我需要用 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 个或更多输出参数的方法,可能具有不同的类型。
最后我对接口(interface)有了一点控制,我还在开发一个类作为库的入口点,但它只是将调用传递给真正的实现。
例如,我已经设法将 method3(std::string & s)
之类的方法更改为 method3(const std::string & s)
,所以我可以通过普通的 String
从 Java 中使用它。
所以稍微修改方法签名是可能的,但是如果 native 方法返回 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"
所以我想知道,从性能的角度来看,结构解决方案(由 Flexo 提供)、由 Flexo 提供的字符串数组或此指针(就像只有一个成员的结构一样)更好。
假设您想在不修改现有头文件的情况下包装它,可以想到两种方法。给定我用于测试的头文件:
#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 提供了 OUTPUT 类型映射,您可以将其与 %apply
一起使用来创建输出 argumnet。这些被包装为 1 个元素的数组 - 传递数组的语义与输出参数的语义相匹配。不幸的是,在 typemaps.i 中没有用于 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
代替)。不过,在一般情况下,无需编写任何额外代码即可应用 OUTPUT 类型映射。