gpt4 book ai didi

java - SWIG从String作为Java中的String数组获取returntype

转载 作者:行者123 更新时间:2023-11-30 08:01:52 26 4
gpt4 key购买 nike

对于一个小型Java项目,我需要与用C编写的现有代码进行交互,以便使事情变得容易(不幸的是,我不是C / C ++程序员。)我决定使用swig。

生成的包装器代码似乎可以正常工作。但是,当我调用一个应该为我提供以NULL分隔的字符串列表的函数时(如果我没有记错的话,这就是C函数应该返回的内容)包装的代码仅返回预期的第一个String值值列表。我假设Java中正确的返回数据类型将是字符串数组而不是字符串?此假设是否正确,是否可以通过在swig接口文件中指定typemap来解决?还是我走错了路?

C头文件中的函数指出:

DllImport char *GetProjects dsproto((void));


生成的JNI Java文件:

public final static native String GetProjects();


任何帮助/指针将不胜感激!

最佳答案

解决方案1-Java

您可以通过多种方法来解决SWIG中的此问题。我从一个解决方案开始,该解决方案仅要求您编写一些Java(在SWIG接口内),然后自动应用该函数以使函数以所需的语义返回String[]

首先,我编写了一个小的test.h文件,使我们可以练习正在努力的类型图:

static const char *GetThings(void) {
return "Hello\0World\0This\0Is\0A\0Lot\0Of Strings\0";
}


没什么特别的,只有一个函数将多个字符串拆分为一个并以双精度 \0终止(最后一个隐含在C中的字符串常量中)。

然后,我编写了以下SWIG接口来包装它:

%module test
%{
#include "test.h"
%}

%include <carrays.i>

%array_functions(signed char, ByteArray);

%apply SWIGTYPE* { const char *GetThings };

%pragma(java) moduleimports=%{
import java.util.ArrayList;
import java.io.ByteArrayOutputStream;
%}

%pragma(java) modulecode=%{
static private String[] pptr2array(long in, boolean owner) {
SWIGTYPE_p_signed_char raw=null;
try {
raw = new SWIGTYPE_p_signed_char(in, owner);
ArrayList<String> tmp = new ArrayList<String>();
int pos = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (ByteArray_getitem(raw, pos) != 0) {
byte c;
while ((c = ByteArray_getitem(raw, pos++)) != 0) {
bos.write(c);
}
tmp.add(bos.toString());
bos.reset();
}
return tmp.toArray(new String[tmp.size()]);
}
finally {
if (owner && null != raw) {
delete_ByteArray(raw);
}
}
}
%}

%typemap(jstype) const char *GetThings "String[]";
%typemap(javaout) const char *GetThings {
return pptr2array($jnicall, $owner);
}

%include "test.h"


本质上,这样做是使用 carrays.i SWIG库文件来公开一些函数,这些函数使我们能够像C语言中的原始指针那样完全获取,设置和删除数组。由于默认情况下SWIG特殊情况 char *,尽管我们在函数 %apply的情况下不得不打破它,因为我们不希望这种情况发生。对数组函数使用 signed char可以得到我们想要的:Java中的 Byte而不是 String的映射。

jstype typemap只是将结果函数返回类型更改为我们想要的类型: String[]。 javaout typemap解释了我们如何根据JNI调用返回的内容( long,因为我们故意停止将其包装为普通的以null结尾的字符串)进行转换,而是使用了一些我们在模块内部编写的额外Java ( pptr2array)为我们完成这项工作。

pptr2array内部,我们实际上是在每个String中逐字节构建输出数组。我使用 ArrayList是因为我宁愿动态增长它,也不愿对输出进行两次传递。使用 ByteArrayOutputStream是一种逐字节构建Byte数组的好方法,它有两个主要优点:


多字节unicode可以像这样正常工作。这与将每个字节强制转换为char并分别附加到String(Builder)形成对比。
我们可以为每个字符串重复使用相同的ByteArrayOutputStream,这样就可以重用缓冲区。在这个规模上,这并不是真正的破坏交易的行为,但是从第一天开始这样做就不会造成任何伤害。


还有一点需要注意:为了正确设置 $owner并指示是否希望我们使用 free()从C函数返回的内存,请使用 %newobject。请参见 discussion of $owner in docs



解决方案2-JNI

如果愿意,您可以编写几乎相同的解决方案,但是完全可以在typemap中编写一些JNI调用:

%module test
%{
#include "test.h"
#include <assert.h>
%}

%typemap(jni) const char *GetThings "jobjectArray";
%typemap(jtype) const char *GetThings "String[]";
%typemap(jstype) const char *GetThings "String[]";
%typemap(javaout) const char *GetThings {
return $jnicall;
}
%typemap(out) const char *GetThings {
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<count);
JCALL3(SetObjectArrayElement, jenv, $result, idx++, str);
while (*pos++); // SKIP
}
//free($1); // Iff you need to free the C function's return value
}

%include "test.h"


在这里,我们基本上做了相同的事情,但是又添加了3个typemap。 jtype和jnitype类型映射告诉SWIG生成的JNI代码和对应的 native函数将返回什么返回类型,分别是Java和C(JNI)类型。 javaout类型映射变得更简单,它所做的就是将 String[]作为 String[]直接传递。

但是in typemap是工作发生的地方。我们在本机代码中分配 String[]的Java数组。这是通过第一步来简单地计算出有多少元素来完成的。 (在C中,没有一种整齐的方法可以做到这一点)。然后在第二遍中,我们调用 NewStringUTF并将其存储到我们先前创建的输出数组对象中的正确位置。所有JNI调用都使用SWIG特定的 JCALLx macros,这使得它们可以在C和C ++编译器中工作。此处实际上没有必要使用它们,但是进入它并不是一个坏习惯。

然后剩下的所有工作都是免费的,结果将在需要时返回函数。 (在我的示例中,这是一个 const char*字符串文字,因此我们不会释放它)。



解决方案3-C

当然,如果您只想编写C,也可以找到解决方案。我在这里概述了一种这样的可能性:

%module test
%{
#include "test.h"
%}

%rename(GetThings) GetThings_Wrapper;
%immutable;
%inline %{
typedef struct {
const char *str;
} StrArrHandle;

StrArrHandle GetThings_Wrapper() {
const StrArrHandle ret = {GetThings()};
return ret;
}
%}
%extend StrArrHandle {
const char *next() {
const char *ret = $self->str;
if (*ret)
$self->str += strlen(ret)+1;
else
ret = NULL;
return ret;
}
}

%ignore GetThings;
%include "test.h"


请注意,在这种情况下,解决方案更改了包装代码中公开的 GetThings()返回类型。现在,它返回一个中间类型,该中间类型仅存在于包装器 StrArrHandle中。

这种新类型的目的是公开使用实际功能给出的所有答案所需的额外功能。我通过使用 %inline声明和定义了一个额外的函数来包装对 GetThings()的实际调用,并使用一个额外的类型来保存它返回的指针供以后使用。

我使用 %ignore%rename仍然声称我的包装函数称为 GetThings(即使这不是为了避免在生成的C代码内部出现名称冲突)。我本可以跳过 %ignore而不只是在文件底部添加 %include,但基于这样的假设:在现实世界中,您也想包装此示例的头文件中可能有更多东西是可能更有用。

然后,可以使用 %extend将方法添加到创建的包装器类型中,该方法将返回当前字符串(如果不在末尾)并前进光标。如果您有责任释放原始函数的返回值,则还希望保留该函数的副本,并使用 %extend添加一个“析构函数”供SWIG在对象被垃圾回收时调用。

我告诉SWIG不允许用户使用 StrArrHandle构造 %nodefaultctor对象。 SWIG将为 strStrArrHandle成员生成一个吸气剂。 %immutable阻止它生成设置器,这在这里根本没有意义。您可能只是使用 %ignore忽略了它,或者将 StrArrHandle拆分了出来,而不是使用 %inline,而只是不告诉SWIG该成员。

现在,您可以使用以下类似方法从Java调用它:

StrArrHandle ret = test.GetThings();
for (String s = ret.next(); s != null; s = ret.next()) {
System.out.println(s);
}


如果您愿意的话,可以将其与解决方案#1的一部分结合起来以返回Java数组。您想要为此在顶部附近添加两个类型图:

%typemap(jstype) StrArrHandle "String[]";
%typemap(javaout) StrArrHandle {
$javaclassname tmp = new $javaclassname($jnicall, $owner);
// You could use the moduleimports pragma here too, this is just for example
java.util.ArrayList<String> out = new java.util.ArrayList<String>();
for (String s = tmp.next(); s != null; s = tmp.next()) {
out.add(s);
}
return out.toArray(new String[out.size()]);
}


其结果与解决方案1几乎相同,但方式却截然不同。

关于java - SWIG从String作为Java中的String数组获取returntype,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37253529/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com