繁体   English   中英

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

[英]SWIG get returntype from String as String array in java

对于一个小型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中的此问题。 我从一个解决方案开始,该解决方案只需要您在SWIG接口内编写更多的Java,然后自动应用该函数以使函数以所需的语义返回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数组的好方法,它有两个主要优点:

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

还有一点需要注意:为了正确设置$owner并指示是否期望free()从C函数返回的内存,您需要使用%newobject 请参阅docs中对$owner讨论


解决方案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是工作发生的地方。 我们在本机代码中分配一个Java的String[]数组。 这是通过第一步来简单地计算出有多少元素来完成的。 (在C中,没有一种整齐的方法可以做到这一点)。 然后在第二遍中,我们调用NewStringUTF并将其存储到我们先前创建的输出数组对象中的正确位置。 所有JNI调用都使用SWIG特定的JCALLx宏 ,这些使它们可以在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不允许用户使用%nodefaultctor构造StrArrHandle对象。 SWIG将为StrArrHandlestr成员生成一个吸气剂。 %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几乎相同,但方式却截然不同。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM