在痛饮从结构变量得到的返回类型在Java中字符串数组 [英] swig get return type from variable in struct as string array in java

查看:182
本文介绍了在痛饮从结构变量得到的返回类型在Java中字符串数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于一个小的Java项目,我需要与现有的code用C写的互动,
所以让一切变得简单(我不是一个C / C ++程序员可惜..)我决定使用痛饮。

我遇到的第一个问题:
即返回NULL分隔的字符串C函数导致在返回的字符串包裹code,只包含第一个值
(!):<一个被提供的3可能的解决方案解决了柔印href=\"http://stackoverflow.com/questions/37253529/swig-get-returntype-from-string-as-string-array-in-java\">SWIG从字符串得到的返回类型在Java中 String数组

虽然在这个项目上继续发展我遇到了是困扰我的一个(类似于?)模式的第二个问题:
该headerfile包含结构PROJECTDETAILS即(反过来)包含ITEMLIST一个变量(应该)包含NULL分隔字符串。
对于ITEMLIST的痛饮生成的getter返回的第一个结果为字符串,就像在我原来的链接问题GetProjects功能
(包含第一个结果的字符串)
我试着申请柔印提供给这个问题,WEL答案,但我不能为类型映射的ITEMLIST变量

在C头文件状态的功能:

  typedef结构_PROJECT
{
    INT版本;
    unsigned char型虚拟机;
    unsigned char型调频;
} *工程;typedef结构_PROJECTDETAILS
{
    INT信息类型;
    联盟
    {
        字符* ITEMLIST; / *返回一个空分隔的字符串* /
        字符* PROJECTNAME;
    }信息;
} PROJECTDETAILS;项目的DllImport OpenProject dsproto((INT,CHAR *));
INT的DllImport GetProjectDetails dsproto((PROJECT,INT,PROJECTDETAILS *));


解决方案

要开始了我写了一个虚拟的实施是我认为你提到的两个函数的语义是:

  typedef结构_PROJECT
{
    INT版本;
    unsigned char型虚拟机;
    unsigned char型调频;
} *工程;#定义INFOTYPE_ITEMLIST 0
#定义INFOTYPE_PROJECTNAME 1typedef结构_PROJECTDETAILS
{
    INT信息类型;
    联盟
    {
        字符* ITEMLIST; / *返回一个空分隔的字符串* /
        字符* PROJECTNAME;
    }信息;
} PROJECTDETAILS;静态项目OpenProject(int类型的,字符* B){
    (无效)一个;(无效)B:
    静态结构_PROJECT p值= {100,1,2};
    返回&安培; P;
}静态INT GetProjectDetails(PROJECT P,int类型的,PROJECTDETAILS *总分){
    (无效)P;
    //不知道什么是真正的IMPL在这里所做的
    如果(一个== 1){
      输出&GT;信息类型= INFOTYPE_ITEMLIST;
      输出&GT; info.itemList =项目1 \\ 2 0Item \\ 0Item 3 \\ 0;
    }
    其他{
      输出&GT;信息类型= INFOTYPE_PROJECTNAME;
      输出&GT; info.projectName =HELLO WORLD;
    }
    返回0;
}

这与我在<一个答曰结合href=\"http://stackoverflow.com/questions/37253529/swig-get-returntype-from-string-as-string-array-in-java\">my previous答案是足以让 ITEMLIST 成员笨重的工作,虽然(用于Java开发人员的角度)的方式。

要正确地使用从previous答案typemaps所有我们需要做的是找出打电话给他们(或写什么了%适用 )。痛饮有帮助我们算出这个一个非常方便的方式,在 -debug-tmsearch 命令行参数,对于这种情况打印出所有被认为忽略了每个考生类型映射搜索因为没有输入了他们。所以,我跑:

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

然后显示的方式,我们可以匹配我们从previous答案typemaps。

test.h:16:寻找合适的出类型映射为:字符* _PROJECTDETAILS :: _ PROJECTDETAILS_info :: ITEMLIST
  寻找:字符* _PROJECTDETAILS :: _ PROJECTDETAILS_info :: ITEMLIST
  寻找:字符* ITEMLIST
  寻找:字符*
  使用:%类型映射(出)的char *

这都说明的char * _PROJECTDETAILS :: _ PROJECTDETAILS_info :: ITEMLIST PROJECTDETAILS ::信息:: ITEMLIST ,我们希望我们的previous类型映射适用于成员。因此,我们可以只使用previous typemaps在这个答案和匹配不同(甚至可以使用%适用来匹配他们以多种用途),是这样的:

%模块的测试%{
#包括test.h
#包括LT&;&ASSERT.H GT;
%}//为这些商榷见第2部分
重命名%(%(条:[_])的);
%不变_PROJECTDETAILS ::信息类型;%类型映射(JNI)的char * _PROJECTDETAILS :: _ PROJECTDETAILS_info :: ITEMLISTjobjectArray
%类型映射(jtype的)的char * _PROJECTDETAILS :: _ PROJECTDETAILS_info :: ITEMLIST的String [];
%类型映射(jstype)的char * _PROJECTDETAILS :: _ PROJECTDETAILS_info :: ITEMLIST的String [];
%类型映射(javaout)的char * _PROJECTDETAILS :: _ PROJECTDETAILS_info :: {ITEMLIST
  返回$ JNICALL;
}
%类型映射(出)的char * _PROJECTDETAILS :: _ PROJECTDETAILS_info :: {ITEMLIST
  为size_t计数= 0;
  为const char * POS = $ 1;
  而(* POS){
    而(* POS ++); // SKIP
    ++计数;
  }
  $结果= JCALL3(NewObjectArray,jenv,计数,JCALL1(FindClass后面,jenv,爪哇/郎/字符串),NULL);
  POS = $ 1;
  为size_t IDX = 0;
  而(* POS){
    jobject海峡= JCALL1(NewStringUTF,jenv,POS)
    断言(IDX&LT;计数);
    JCALL3(SetObjectArrayElement,jenv,$结果,IDX ++,STR);
    而(* POS ++); // SKIP
  }
  //自由($ 1); //当且仅当你需要释放C函数的返回值
}%包括test.h

这从previous答案采摘方法2,主要是因为它是完全基于类型映射,所以更好的 -debug-tmsearch 痛饮论点的一个例子。

这就够了,我们可以使用它作为:

 进口java.util.Arrays中;公共类运行{
  公共静态无效的主要(字符串[] argv的){
    的System.loadLibrary(测试);
    PROJECT P = test.OpenProject(1,???);
    PROJECTDETAILS PD1 =新PROJECTDETAILS();
    test.GetProjectDetails(第1,PD1);
    的System.out.println(Arrays.toString(pd1.getInfo()getItemList())。);
  }
}

但是,我们可以做的比,对于Java用户更好,创建一个新的 PROJECTDETAILS 对象只是在作为参数传递给 GetProjectDetails 有点奇怪。


要整齐地包装成Java这个有相当你想要做的,除了刚才不寻常的语义成员变量的char * 几件事情。

首先我们可能要重新命名一些你有结构的。你可以这样做,在痛饮2.0及以上采用先进的重命名条经营者。

接下来,我们需要决定如何来包装成员自己。 C中的一个典型的设计模式是使用int来表示其成员的工会是正确的类型给定对象。在Python我刚刚返回不同类型的每一种情况下,靠鸭打字。对于Java有几个不同的选择,这将是明智的:


  1. 您可以定义一个类层次结构,并使用的instanceof (或只是int型),以弄清楚如何转换为正确的类型。

  2. 您可以保留原样和反映的C语义。 (技术上访问错误的成员是不确定的行为,这是不是很直观的Java开发人员)。

  3. 您可以返回引发异常或返回NULL,而是如果您尝试访问错误成员的类型。

选项2是这个答案的previous一部分一样。从我的角度选择3可能是最predictable行为一个Java程序员,所以这就是我在这里做了。

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

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

这时我犯了在头文件私有的 GetProjectDetails 的版本,添加了默认地将Impl 后缀以表明它是不打算超过包装的内部以外的任何人去动它。

我使用的模块本身内部%杂来添加另一个公众版本 GetProjectDetails 隐藏的事实,一个新的对象被构​​造为输出唯一参数,改变了返回类型退货。它也可切换使用 INT 来表示成功'C编程风格到Java抛出一个异常,如果它出错的机制。 (还有夜风中更多的方式来做到这一点的不仅仅是这样,但它最大限度地减少了C / JNI学习曲线那样做)。

然后我们添加两个额外的,只读成员变量的包裹 PROJECTDETAILS 结构。这些并不真的存在C直接,所以他们通过一些额外的C code在中间界面胶code语言实现的。这code的一点​​是,它会检查什么情况下,联盟实际上是使用额外的 INT 件指示的类型。如果类型是不正确的,他们返回null(但C或Java的胶水code可以使一个例外,而不是)。

我们所有的与做的,然后再使用typemaps从我的previous答案得到正确跨越语言边界的工作 ITEMLIST 语义。我再次使用的方法2这里,除了具有功能返回null这是不变的小问题。

%模块的测试%{
#包括test.h
#包括LT&;&ASSERT.H GT;
%}重命名%(%(条:[_])的);
%不变_PROJECTDETAILS ::信息类型;
%不理信息; //忽略成员
%无视_PROJECTDETAILS_info; //忽略匿名类型
%javamethodmodifiers GetProjectDetails私人;
重命名%(GetProjectDetailsImpl)GetProjectDetails;%类型映射(JNI)的char * _PROJECTDETAILS :: ITEMLISTjobjectArray
%类型映射(jtype的)的char * _PROJECTDETAILS :: ITEMLIST的String [];
%类型映射(jstype)的char * _PROJECTDETAILS :: ITEMLIST的String [];
%类型映射(javaout)的char * _PROJECTDETAILS :: {ITEMLIST
  返回$ JNICALL;
}
%类型映射(出)的char * _PROJECTDETAILS :: {ITEMLIST
  如果($ 1!)返回NULL; //这个修复了可能的错误在我的previous答案
  为size_t计数= 0;
  为const char * POS = $ 1;
  而(* POS){
    而(* POS ++); // SKIP
    ++计数;
  }
  $结果= JCALL3(NewObjectArray,jenv,计数,JCALL1(FindClass后面,jenv,爪哇/郎/字符串),NULL);
  POS = $ 1;
  为size_t IDX = 0;
  而(* POS){
    jobject海峡= JCALL1(NewStringUTF,jenv,POS)
    断言(IDX&LT;计数);
    JCALL3(SetObjectArrayElement,jenv,$结果,IDX ++,STR);
    而(* POS ++); // SKIP
  }
  //自由($ 1); //当且仅当你需要释放C函数的返回值
}%附注(JAVA)模块code = {%
  公共静态PROJECTDETAILS GetProjectDetails(PROJECT P,诠释一){
    PROJECTDETAILS出=新PROJECTDETAILS();
    最终诠释RET = GetProjectDetailsImpl(P,A,淘汰);
    如果(0!= RET){
      //假设这是一个错误扔东西
    }
    返回的;
  }
%}%扩展_PROJECTDETAILS {
  为const char * const的ITEMLIST {
    如果(自$&GT;!=信息类型INFOTYPE_ITEMLIST){
     //此处抛出一个Java异常呢?这是另外一个问题...
     返回NULL;
    }
    返回$自我&GT; info.itemList;
  }
  为const char * const的项目名{
    如果(自$&GT;!=信息类型INFOTYPE_PROJECTNAME){
      //抛出异常?
      返回NULL;
    }
    返回$自我&GT; info.projectName;
  }
}%包括test.h

然后与作品:

 进口java.util.Arrays中;公共类运行{
  公共静态无效的主要(字符串[] argv的){
    的System.loadLibrary(测试);
    PROJECT P = test.OpenProject(1,???);    的System.out.println(PD1);
    PROJECTDETAILS PD1 = test.GetProjectDetails(对,1);
    的System.out.println(Arrays.toString(pd1.getItemList()));
    的System.out.println(pd1.getProjectName());    的System.out.println(PD2);
    PROJECTDETAILS PD2 = test.GetProjectDetails(对,2);
    的System.out.println(Arrays.toString(pd2.getItemList()));
    的System.out.println(pd2.getProjectName());
  }
}

(注意:任何以_跟着一个大写字母开头是的保留名称,可能不是你的错,但不完全大C)

For a small Java project I needed to interact with existing code written in C, so to make things easy (I'm not a C/C++ programmer unfortunately..) I decided to use swig.

The first problem I encountered: a C function that returned a NULL-delimited string resulted in wrapped code that returned a String, containing only the first value was solved by the 3(!) possible solutions Flexo provided in: SWIG get returntype from String as String array in java

While continuing development on this project I encountered a second problem with a (similar?) pattern that puzzles me: The headerfile contains a struct "PROJECTDETAILS" that (in turn) contains a variable itemList that (should) contain a NULL-delimited String. The swig generated getter for itemList returns the first result as a String, just like the GetProjects function in my original linked question (a String containing the first result) I've tried to apply the answer Flexo provided to this problem as wel, however I'm not able to "typemap" the itemList variable

The function in the C header file states:

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 *));

解决方案

To start out I wrote a dummy implementation of what I assume the semantics of the two functions you referenced are:

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

This, combined with the answer I gave in my previous answer is sufficient to get the itemList member working, albeit in a clunky (for a Java developer's perspective) way.

To make use of the typemaps from the previous answer correctly all we need to do is figure out what to call them (or what to write for %apply). SWIG has a really handy way of helping us figure this out, the -debug-tmsearch command line argument that for every typemap search that happens prints all the candidates which are considered and ignored because nothing was entered for them. So I ran:

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

Which then shows the ways we could match that with our typemaps from the previous answer.

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 *

Which shows that char *_PROJECTDETAILS::_PROJECTDETAILS_info::itemList is the tightest match for the PROJECTDETAILS::info::itemList member that we want to apply our previous typemap to. So we could just use the previous typemaps in this answer and match differently (or even use %apply to match them to multiple usages), something like:

%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"

This picked method 2 from the previous answer, mainly because it was entirely typemap based, so a better example of the -debug-tmsearch SWIG argument.

That's enough that we can use it as:

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

But we can do much better than that for Java users, creating a new PROJECTDETAILS object just to pass in as the argument to GetProjectDetails is a little odd.


To wrap this neatly into Java there are quite a few things you want to do, besides just the member variable with unusual semantics for char *.

First up we probably want to rename some of the structs you've got. You can do that in SWIG 2.0 and above using the advanced renaming strip operator.

Next we need to decide how to wrap the members themselves. A typical design pattern in C is to use an int to indicate which member of a union is the correct type for a given object. In Python I'd just return a different type for each case and rely on duck-typing. For Java there are a couple of different options that would be sensible:

  1. You could define a class hierarchy and use instanceof (or just the int type) to figure out how to cast to the right type.
  2. You could leave it as is and mirror the C semantics. (Technically accessing the 'wrong' member is undefined behaviour, which isn't very intuitive for Java developers).
  3. You could return a type that raise an exception or return NULL instead if you try to access the 'wrong' member.

Option 2 is what the previous part of this answer did. From my perspective option 3 is probably the most predictable behaviour to a Java programmer, so that's what I've done here.

The third fun decision we need to make is how to handle the output function argument. In this instance I'm going to pick the solution that favours more Java code over more C/JNI, but the same trade off as with previous answers applies here too.

So what I did is to tell SWIG to ignore PROJECTDETAILS::info completely, as well as renaming the underscore prefixed structs.

Then I made the version of GetProjectDetails in the header file private and added the Impl suffix to show that it's not intended for anyone other than internals of the wrapper to touch it.

Inside the module itself I used %pragma to add another, public version of GetProjectDetails that hides the fact that a new object is constructed for an output only argument, changes the return type to return this. It also switches the 'use int to indicate success' C coding style into the Java 'throw an exception if it goes wrong' mechanism. (There are more ways in SWIG to do this than just like this, but it minimizes the C/JNI learning curve to do it like that).

Then we add two extra, read-only member variables to the wrapped PROJECTDETAILS struct. These don't really exist in C directly, so they're implemented in the intermediate interface glue code by some extra C code. The point of this code is that it checks what case the union is actually in, using the extra int member that indicates the type. If the type isn't right they return null (but either C or Java glue code could make that an exception instead).

All we have to do with that is then re-use the typemaps from my previous answer to get the itemList semantics working right across the language boundary. Again I used method 2 here, other than the minor issue with functions returning null it's unchanged.

%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"

Which then works with:

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

(Note: anything that starts with _ followed by a capital letter is a reserved name, probably not your fault, but not exactly great C)

这篇关于在痛饮从结构变量得到的返回类型在Java中字符串数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆