swig从struct中的变量获取返回类型作为java中的字符串数组

对于一个小型Java项目,我需要与用C编写的现有代码进行交互,所以为了简单起见(不幸的是我不是C / C ++程序员……)我决定使用swig。

我遇到的第一个问题:返回NULL分隔字符串的C函数导致返回String的包装代码,只包含第一个值,由Flexo提供的3(!)可能解决方案解决: SWIG从String获取返回类型java中的字符串数组

在继续开发这个项目时,我遇到了第二个问题(类似?)模式让我感到困惑:headerfile包含一个结构“PROJECTDETAILS”,它反过来包含一个变量itemList(应该)包含一个NULL分隔的String。 用于itemList的swig生成的getter将第一个结果作为String返回,就像我原始链接问题中的GetProjects函数一样(包含第一个结果的String)我试图将Flexo提供的答案应用于此问题,但是我’无法“键入”itemList变量

C头文件中的函数指出:

typedef struct _PROJECT { int version; unsigned char vM; unsigned char fM; } * PROJECT; typedef struct _PROJECTDETAILS { int infoType; union { char *itemList; /* Returns a NULL-delimited string */ char *projectName; } info; } PROJECTDETAILS; DllImport PROJECT OpenProject dsproto((int, char *)); DllImport int GetProjectDetails dsproto((PROJECT, int, PROJECTDETAILS *)); 

首先,我写了一个虚拟实现,我假设你引用的两个函数的语义是:

 typedef struct _PROJECT { int version; unsigned char vM; unsigned char fM; } * PROJECT; #define INFOTYPE_ITEMLIST 0 #define INFOTYPE_PROJECTNAME 1 typedef struct _PROJECTDETAILS { int infoType; union { char *itemList; /* Returns a NULL-delimited string */ char *projectName; } info; } PROJECTDETAILS; static PROJECT OpenProject(int a, char *b) { (void)a;(void)b; static struct _PROJECT p = {100, 1, 2}; return &p; } static int GetProjectDetails(PROJECT p, int a, PROJECTDETAILS *out) { (void)p; // No idea what real impl does here if (a == 1) { out->infoType = INFOTYPE_ITEMLIST; out->info.itemList="Item 1\0Item 2\0Item 3\0"; } else { out->infoType = INFOTYPE_PROJECTNAME; out->info.projectName = "HELLO WORLD"; } return 0; } 

这与我在之前的回答中给出的答案相结合,足以使itemList成员工作,尽管这是一种笨重的(对于Java开发人员的观点)方式。

要正确使用上一个答案中的类型映射,我们需要做的就是弄清楚要调用它们的内容(或者为%apply )。 SWIG有一个非常方便的方法来帮助我们解决这个问题, -debug-tmsearch命令行参数,对于每个发生-debug-tmsearch搜索,打印所有被考虑和忽略的候选项,因为没有为它们输入任何内容。 所以我跑了:

 swig3.0 -java -Wall -debug-tmsearch test.i 

然后,它显示了我们可以将其与前一个答案中的类型图匹配的方式。

 test.h:16: Searching for a suitable 'out' typemap for: char *_PROJECTDETAILS::_PROJECTDETAILS_info::itemList Looking for: char *_PROJECTDETAILS::_PROJECTDETAILS_info::itemList Looking for: char *itemList Looking for: char * Using: %typemap(out) char * 

这表明char *_PROJECTDETAILS::_PROJECTDETAILS_info::itemList是我们要将之前的typemap应用到的PROJECTDETAILS::info::itemList成员的最紧密匹配。 因此,我们可以在此答案中使用以前的类型映射并进行不同的匹配(甚至使用%apply将它们与多个用法匹配),例如:

 %module test %{ #include "test.h" #include  %} // See part 2 for discusson of these %rename("%(strip:[_])s") ""; %immutable _PROJECTDETAILS::infoType; %typemap(jni) char * _PROJECTDETAILS::_PROJECTDETAILS_info::itemList "jobjectArray"; %typemap(jtype) char * _PROJECTDETAILS::_PROJECTDETAILS_info::itemList "String[]"; %typemap(jstype) char * _PROJECTDETAILS::_PROJECTDETAILS_info::itemList "String[]"; %typemap(javaout) char * _PROJECTDETAILS::_PROJECTDETAILS_info::itemList { return $jnicall; } %typemap(out) char * _PROJECTDETAILS::_PROJECTDETAILS_info::itemList { 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 

这是从前一个答案中挑选的方法2,主要是因为它完全基于-debug-tmsearch ,因此是-debug-tmsearch SWIG参数的更好示例。

这足以让我们将其用作:

 import java.util.Arrays; public class run { public static void main(String[] argv) { System.loadLibrary("test"); PROJECT p = test.OpenProject(1,"???"); PROJECTDETAILS pd1 = new PROJECTDETAILS(); test.GetProjectDetails(p, 1, pd1); System.out.println(Arrays.toString(pd1.getInfo().getItemList())); } } 

但是我们可以为Java用户做得更好,创建一个新的PROJECTDETAILS对象只是为了传入,因为GetProjectDetails的参数有点奇怪。


要将它整齐地包装到Java中,除了只是具有char *exception语义的成员变量之外,还有很多事情要做。

首先,我们可能想要重命名你已经获得的一些结构。 您可以使用高级重命名条带运算符在SWIG 2.0及更高版本中执行此操作。

接下来我们需要决定如何自己包装成员。 C中的典型设计模式是使用int来指示union的哪个成员是给定对象的正确类型。 在Python中,我只为每个案例返回一个不同的类型,并依赖于duck-typing。 对于Java,有几个不同的选项是明智的:

  1. 您可以定义类层次结构并使用instanceof (或仅使用int类型)来确定如何转换为正确的类型。
  2. 您可以保持原样并镜像C语义。 (技术上访问'错误'成员是未定义的行为,这对Java开发人员来说不是很直观)。
  3. 如果您尝试访问“错误”成员,则可以返回引发exception的类型或返回NULL。

选项2是这个答案的前一部分所做的。 从我的角度来看,选项3可能是Java程序员最可预测的行为,所以这就是我在这里所做的。

我们需要做的第三个有趣的决定是如何处理输出函数参数。 在这种情况下,我将选择有利于更多C / JNI的更多Java代码的解决方案,但是同样的权衡也适用于此前的答案。

所以我所做的就是告诉SWIG完全忽略PROJECTDETAILS::info ,以及重命名下划线前缀结构。

然后我将头部文件中的GetProjectDetails版本设为私有,并添加了Impl后缀,以表明它不适用于除了包装器内部之外的任何人触摸它。

在模块本身内部,我使用%pragma添加另一个公共版本的GetProjectDetails ,它隐藏了为仅输出参数构造新对象的事实,更改返回类型以返回此值。 它还将'use int表示成功'C编码风格切换到Java'如果出错就抛出exception'机制。 (在SWIG中有更多的方法可以做到这一点,而不是像这样,但它最大限度地减少了C / JNI学习曲线,就像这样做)。

然后我们将两个额外的只读成员变量添加到包装的PROJECTDETAILS结构中。 这些并不直接存在于C中,因此它们通过一些额外的C代码在中间接口粘合代码中实现。 这段代码的重点在于它使用指示类型的额外int成员来检查union实际处于什么情况。 如果类型不正确,则返回null(但C或Java粘合代码可能会使其成为exception)。

我们所做的就是重新使用我之前的答案中的类型itemList以使itemList语义在语言边界上正常工作。 我在这里再次使用方法2,除了函数返回null的小问题,它没有改变。

 %module test %{ #include "test.h" #include  %} %rename("%(strip:[_])s") ""; %immutable _PROJECTDETAILS::infoType; %ignore info; // Ignore the member %ignore _PROJECTDETAILS_info; // Ignore the anonymous type %javamethodmodifiers GetProjectDetails "private"; %rename(GetProjectDetailsImpl) GetProjectDetails; %typemap(jni) char *_PROJECTDETAILS::itemList "jobjectArray"; %typemap(jtype) char *_PROJECTDETAILS::itemList "String[]"; %typemap(jstype) char *_PROJECTDETAILS::itemList "String[]"; %typemap(javaout) char *_PROJECTDETAILS::itemList { return $jnicall; } %typemap(out) char *_PROJECTDETAILS::itemList { if (!$1) return NULL; // This fixes a possible bug in my previous answer 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(idxinfoType != INFOTYPE_ITEMLIST) { // Throw a Java exception here instead? That is another question... return NULL; } return $self->info.itemList; } const char *projectName const { if ($self->infoType != INFOTYPE_PROJECTNAME) { // Throw exception? return NULL; } return $self->info.projectName; } } %include "test.h" 

然后使用:

 import java.util.Arrays; public class run { public static void main(String[] argv) { System.loadLibrary("test"); PROJECT p = test.OpenProject(1,"???"); System.out.println("PD1"); PROJECTDETAILS pd1 = test.GetProjectDetails(p, 1); System.out.println(Arrays.toString(pd1.getItemList())); System.out.println(pd1.getProjectName()); System.out.println("PD2"); PROJECTDETAILS pd2 = test.GetProjectDetails(p, 2); System.out.println(Arrays.toString(pd2.getItemList())); System.out.println(pd2.getProjectName()); } } 

(注意:以_开头后跟大写字母的任何内容都是保留名称 ,可能不是你的错,但不是很好C)