swig get return 从结构中的变量键入 java 中的字符串数组

swig get return type from variable in struct as string array in java

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

遇到的第一个问题: 返回 NULL 分隔字符串的 C 函数导致返回字符串的包装代码,仅包含第一个值 由 Flexo 提供的 3(!)种可能解决方案解决:

在继续开发这个项目的过程中,我遇到了第二个问题(类似?),这让我很困惑: 头文件包含一个结构 "PROJECTDETAILS",它(反过来)包含一个变量 itemList,该变量(应该)包含一个 NULL 分隔的字符串。 swig 为 itemList returns 生成了 getter 作为字符串的第一个结果,就像我原来的链接问题中的 GetProjects 函数一样 (包含第一个结果的字符串) 我也尝试将 Flexo 提供的答案应用于此问题,但是我无法 "typemap" 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[=10=]Item 2[=10=]Item 3[=10=]";
    }
    else {
      out->infoType = INFOTYPE_PROJECTNAME;
      out->info.projectName = "HELLO WORLD";
    }
    return 0;
}

这与我在 中给出的答案相结合足以让 itemList 成员工作,尽管以一种笨拙的方式(从 Java 开发人员的角度来看)。

要正确使用上一个答案中的类型映射,我们需要做的就是弄清楚如何调用它们(或为 %apply 编写什么)。 SWIG 有一个非常方便的方法来帮助我们解决这个问题,-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 是我们要将之前的类型映射应用到的 PROJECTDETAILS::info::itemList 成员的最紧密匹配。所以我们可以在这个答案中使用之前的类型映射并进行不同的匹配(或者甚至使用 %apply 将它们匹配到多种用法),比如:

%module test

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

// 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 = ;
  while (*pos) {
    while (*pos++); // SKIP
    ++count;
  }
  $result = JCALL3(NewObjectArray, jenv, count, JCALL1(FindClass, jenv, "java/lang/String"), NULL);
  pos = ;
  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(); // Iff you need to free the C function's return value
}

%include "test.h"

这从之前的答案中选择了方法 2,主要是因为它完全基于类型映射,因此是 -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 *.

的具有不寻常语义的成员变量之外,您还需要做很多事情

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

接下来我们需要决定如何包装成员本身。 C 中的一个典型设计模式是使用 int 来指示联合的哪个成员是给定对象的正确类型。在 Python 中,我只是 return 为每种情况使用不同的类型并依赖鸭子类型。对于 Java 有几个不同的选择是明智的:

  1. 您可以定义一个 class 层次结构并使用 instanceof(或仅使用 int 类型)来确定如何转换为正确的类型。
  2. 您可以保持原样并反映 C 语义。 (从技术上讲,访问 'wrong' 成员是未定义的行为,这对 Java 开发人员来说不是很直观)。
  3. 如果您尝试访问 'wrong' 成员,您可以 return 引发异常的类型或 return NULL。

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

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

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

然后我在头文件中将 GetProjectDetails 的版本设为私有并添加 Impl 后缀以表明除包装器内部之外的任何人都不能接触它。

在模块本身内部,我使用 %pragma 添加另一个 GetProjectDetails 的 public 版本,它隐藏了一个新对象是为仅输出参数构造的事实,更改了return 输入 return 这个。它还将 'use int to indicate success' C 编码风格切换为 Java 'throw an exception if it goes wrong' 机制。 (在 SWIG 中有更多方法可以做到这一点,而不仅仅是像这样,但它最大限度地减少了 C/JNI 学习曲线来做到这一点)。

然后我们将两个额外的只读成员变量添加到包装的 PROJECTDETAILS 结构中。这些实际上并不直接存在于 C 中,因此它们是由一些额外的 C 代码在中间接口胶水代码中实现的。这段代码的要点是它使用指示类型的额外 int 成员检查 union 实际处于什么情况。如果类型不正确,它们 return 为 null(但 C 或 Java 胶水代码可以使它成为一个例外)。

我们所要做的就是重新使用我之前的回答中的类型映射,让 itemList 语义能够跨语言边界工作。我在这里再次使用了方法 2,除了函数的小问题 returning null 它没有改变。

%module test

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

%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 (!) return NULL; // This fixes a possible bug in my previous answer
  size_t count = 0;
  const char *pos = ;
  while (*pos) {
    while (*pos++); // SKIP
    ++count;
  }
  $result = JCALL3(NewObjectArray, jenv, count, JCALL1(FindClass, jenv, "java/lang/String"), NULL);
  pos = ;
  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(); // Iff you need to free the C function return value
}

%pragma(java) modulecode=%{
  public static PROJECTDETAILS GetProjectDetails(PROJECT p, int a) {
    PROJECTDETAILS out = new PROJECTDETAILS();
    final int ret = GetProjectDetailsImpl(p,a,out);
    if (0!=ret) {
      // assuming this is an error throw something
    }
    return out;
  }
%}

%extend _PROJECTDETAILS {
  const char *itemList const {
    if ($self->infoType != 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());
  }
}

(注意:任何以 _ 开头后跟大写字母的都是 reserved name,可能不是你的错,但不是很好 C)