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

时间:2016-05-19 08:22:55

标签: java c arrays string swig

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

我遇到的第一个问题: 返回NULL分隔字符串的C函数导致返回String的包装代码,该字符串仅包含第一个值 由Flexo提供的3(!)可能解决方案解决:SWIG get returntype from String as String array in java

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

C头文件中的函数声明:

import statsmodels.api as sm

model = sm.OLS(series_1, mmmm, window=50).fit()

print(model.summary())

1 个答案:

答案 0 :(得分:1)

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

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;
}

这与我在my previous answer中给出的答案相结合,足以使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是我们要将之前的typemap应用到的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 = $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"

从前一个答案中挑选出来的方法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来指示union的哪个成员是给定对象的正确类型。在Python中,我只是为每个案例返回一个不同的类型,并依赖于duck-typing。对于Java,有几个不同的选项是明智的:

  1. 您可以定义类层次结构并使用instanceof(或仅使用int类型)来确定如何转换为正确的类型。
  2. 你可以保持原样并反映C语义。 (技术上访问'错误'成员是未定义的行为,这对Java开发人员来说不是很直观。)
  3. 如果您尝试访问“错误”成员,则可以返回引发异常的类型或返回NULL。
  4. 选项2是本答案的前一部分所做的。从我的角度来看,选项3可能是Java程序员最可预测的行为,所以这就是我在这里所做的。

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

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

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

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

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

    然后我们所做的只是重复使用我之前的答案中的类型映射,以使itemList语义在语言边界上正常工作。我在这里再次使用方法2,除了函数返回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 (!$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(idx<count);
        JCALL3(SetObjectArrayElement, jenv, $result, idx++, str);
        while (*pos++); // SKIP
      }
      //free($1); // 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)