使用SWIG将Java Map 传递给C ++方法

Passing Java Map to C++ method using SWIG

我有一个用C ++定义的方法:

1
2
3
4
std::map<std::string, std::string> validate(
                                   std::map<std::string, std::string> key,
                                   std::map<std::string, std::string> value
                                   );

我想在Java中使用此方法。 所以,我必须使用Swig编写一个包装器,通过它我可以将Java Map作为STL map传递给c ++方法。

请告诉我如何为swig定义.i文件以使其正常工作。


为了做到这一点,你需要告诉SWIG使用java.util.Map作为输入参数,使用%typemap(jstype)。您还需要提供一些代码以从Java地图类型转换为C ++ std::map类型,SWIG将在适当的点注入。我已经整理了一个小的(编译但未经测试的)示例来说明这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
%module test

%include <std_map.i>
%include <std_string.i>

%typemap(jstype) std::map<std::string, std::string>"java.util.Map<String,String>"
%typemap(javain,pre="    MapType temp$javainput = $javaclassname.convertMap($javainput);",pgcppname="temp$javainput") std::map<std::string, std::string>"$javaclassname.getCPtr(temp$javainput)"
%typemap(javacode) std::map<std::string, std::string> %{
  static $javaclassname convertMap(java.util.Map<String,String> in) {
    $javaclassname out = new $javaclassname();
    for (java.util.Map.Entry<String, String> entry : in.entrySet()) {
      out.set(entry.getKey(), entry.getValue());      
    }
    return out;
  }    
%}

%template(MapType) std::map<std::string, std::string>;

void foo(std::map<std::string, std::string>);

pgcppname部分确保我们传入的std::map不会过早收集垃圾。有关其工作原理的更多详细信息,请参阅SWIG文档中的此示例。

支持从std::map从C ++返回到Java需要花费更多的工作,但这是可能的。 java.util.Map是一个接口,因此我们需要调整std::map的默认包装以满足该接口。在实践中,它更容易使用java.util.AbstractMap并继承,尽管我最终还是覆盖了大部分功能。整个解决方案类似于我对std::vector的回答。

我的最终版本中有相当多的活动部分。我将在这里完整介绍,附带注释说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
%module test
%{
#include <cassert>
#include <iostream>
%}

%include <std_map.i>

// 1.
%rename (size_impl) std::map<std::string,std::string>::size;
%rename (isEmpty) std::map<std::string,std::string>::empty;
%include <std_string.i>

%typemap(jstype) std::map<std::string, std::string>"java.util.Map<String,String>"
%typemap(javain,pre="    MapType temp$javainput = $javaclassname.convertMap($javainput);",pgcppname="temp$javainput") std::map<std::string, std::string>"$javaclassname.getCPtr(temp$javainput)"
%typemap(javacode) std::map<std::string, std::string> %{
  static $javaclassname convertMap(Map<String,String> in) {
    // 2.
    if (in instanceof $javaclassname) {
      return ($javaclassname)in;
    }

    $javaclassname out = new $javaclassname();
    for (Map.Entry<String, String> entry : in.entrySet()) {
      out.set(entry.getKey(), entry.getValue());
    }
    return out;
  }

  // 3.
  public Set<Map.Entry<String,String>> entrySet() {
    HashSet<Map.Entry<String,String>> ret = new HashSet<Map.Entry<String,String>>(size());
    String array[] = new String[size()];
    all_keys(array);
    for (String key: array) {
      ret.add(new MapTypeEntry(key,this));
    }
    return ret;
  }

  public Collection<String> values() {
    String array[] = new String[size()];
    all_values(array);
    return new ArrayList<String>(Arrays.asList(array));
  }

  public Set<String> keySet() {
    String array[] = new String[size()];
    all_keys(array);
    return new HashSet<String>(Arrays.asList(array));
  }

  // 4.
  public String remove(Object key) {
    final String ret = get(key);
    remove((String)key);
    return ret;
  }

  public String put(String key, String value) {
    final String ret = has_key(key) ? get(key) : null;
    set(key, value);
    return ret;
  }

  // 5.
  public int size() {
    return (int)size_impl();
  }
%}

// 6.
%typemap(javaimports) std::map<std::string, std::string>"import java.util.*;";
// 7.
%typemap(javabase) std::map<std::string, std::string>"AbstractMap<String, String>";

// 8.
%{
template <typename K, typename V>
struct map_entry {
  const K key;
  map_entry(const K& key, std::map<K,V> *owner) : key(key), m(owner) {
  }
  std::map<K,V> * const m;
};
%}

// 9.
template <typename K, typename V>
struct map_entry {
  const K key;
  %extend {
    V getValue() const {
      return (*$self->m)[$self->key];
    }

    V setValue(const V& n) const {
      const V old = (*$self->m)[$self->key];
      (*$self->m)[$self->key] = n;
      return old;
    }
  }
  map_entry(const K& key, std::map<K,V> *owner);
};

// 10.
%typemap(javainterfaces) map_entry<std::string, std::string>"java.util.Map.Entry<String,String>";
// 11.
%typemap(in,numinputs=0) JNIEnv * %{
  $1 = jenv;
%}

// 12.
%extend std::map<std::string, std::string> {
  void all_values(jobjectArray values, JNIEnv *jenv) const {
    assert((jsize)$self->size() == jenv->GetArrayLength(values));
    jsize pos = 0;
    for (std::map<std::string, std::string>::const_iterator it = $self->begin();
         it != $self->end();
         ++it) {
       jenv->SetObjectArrayElement(values, pos++, jenv->NewStringUTF(it->second.c_str()));
    }
  }

  void all_keys(jobjectArray keys, JNIEnv *jenv) const {
    assert((jsize)$self->size() == jenv->GetArrayLength(keys));
    jsize pos = 0;
    for (std::map<std::string, std::string>::const_iterator it = $self->begin();
         it != $self->end();
         ++it) {
       jenv->SetObjectArrayElement(keys, pos++, jenv->NewStringUTF(it->first.c_str()));
    }
  }
}

%template(MapType) std::map<std::string, std::string>;
%template(MapTypeEntry) map_entry<std::string, std::string>;

// 13.
%inline %{
  std::map<std::string, std::string> foo(std::map<std::string, std::string> in) {
    for (std::map<std::string, std::string>::const_iterator it = in.begin();
         it != in.end(); ++it) {
      std::cout << it->first <<":" << it->second <<"
"
;
    }

    return std::map<std::string, std::string>(in);
  }
%}
  • std_map.i并不意味着实现任何接口/抽象类。我们需要重命名一些暴露的内容才能这样做。
  • 由于我们将类型实现Map(通过AbstractMap),因此当它只是一个复制操作时,最终从MapType - > MapType进行转换是愚蠢的。 convertMap方法现在将此情况作为优化进行检查。
  • EntrySetAbstractMap的主要要求。我们已经定义(稍后)MapTypeEntry来为我们实现Map.Entry接口。这将在%extend中使用更多代码,以便有效地将所有键枚举为数组。请注意,这不是线程安全的,如果我们更改地图,而此枚举正在进行中,则会发生奇怪的错误,并且可能无法检测到。
  • remove是我们必须实现的方法之一才能变得可变。 removeput都必须返回旧值,所以这里有一些额外的Java来实现这一点,因为C ++映射不会这样做。
  • 即使size()也不兼容,因为需要进行long / int转换。真的,我们应该检测到非常大的地图的精度损失,并为溢出做一些理智的事情。
  • 我无聊地在任何地方输入java.util.Map,这使得生成的SWIG代码需要导入。
  • 这将MapType设置为从AbstractMap继承,以便我们代理并满足Java映射的要求,而不是执行额外的副本以进行转换。
  • 作为我们的条目的类的C ++定义。这只是一个键,然后是指向它所拥有的地图的指针。该值不存储在Entry对象本身中,并始终返回到底层映射。这种类型也是不可变的,我们无法改变拥有的地图或密钥。
  • 这就是SWIG所看到的。我们提供了一个额外的get / setValue函数,可以回调它所源自的地图。没有公开指向拥有地图的指针,因为我们不需要这样做,它实际上只是一个实现细节。
  • java.util.Map.Entry
  • 这是一个自动填充%extend内部某些代码的jenv参数的技巧,我们需要在该代码中进行一些JNI调用。
  • %extend中的这两个方法将所有键和值分别放入输出数组中。传入时,数组的大小应该是正确的。有一个断言来验证这个,但实际上它应该是一个例外。这两个都是内部实现细节,可能应该是私有的。它们被需要批量访问键/值的所有函数使用。
  • foo的实际实现,以便检查我的代码。
  • 内存管理在这里免费发生,因为它仍归C ++代码所有。 (所以你仍然需要决定如何管理C ++容器的内存,但这并不是什么新鲜事)。由于返回到Java的对象只是C ++映射的包装器,因此容器的元素不必比它更长。在这里它们也是Strings,它们是特殊的,它们作为新对象返回,如果它们是使用SWIG的std::shared_ptr支持的智能指针,那么一切都将按预期工作。唯一棘手的情况是指向对象的指针。在这种情况下,Java程序员有责任使映射及其内容保持活动,至少与返回的任何Java代理一样长。

    最后我编写了以下Java来测试它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import java.util.Map;

    public class run {
      public static void main(String[] argv) {
        System.loadLibrary("test");

        Map<String,String> m = new MapType();
        m.put("key1","value1");
        System.out.println(m);
        m = test.foo(m);
        System.out.println(m);
      }
    }

    我编译并运行为:

    1
    2
    3
    4
    5
    6
    7
    swig2.0 -Wall -java -c++ test.i
    gcc -Wall -Wextra -shared -o libtest.so -I/usr/lib/jvm/default-java/include -I/usr/lib/jvm/default-java/include/linux test_wrap.cxx
    javac run.java
    LD_LIBRARY_PATH=. java run
    {key1=value1}
    key1: value1
    {key1=value1}

    好。


    或者我们可以完全使用Java(假设您的函数声明可以在头文件MapTest.h中找到),在JavaCPP的帮助下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import com.googlecode.javacpp.*;
    import com.googlecode.javacpp.annotation.*;

    @Platform(include={"<string>","<map>","MapTest.h"})
    public class MapTest {
        static { Loader.load(); }

        @Name("std::map<std::string, std::string>")
        public static class StringStringMap extends Pointer {
            static { Loader.load(); }
            public StringStringMap() { allocate(); }
            public StringStringMap(Pointer p) { super(p); }
            private native void allocate();

            @Index @ByRef public native String get(String x);
            public native StringStringMap put(String x, String y);
        }

        public static native @ByVal StringStringMap validate(
                @ByVal StringStringMap key, @ByVal StringStringMap value);

        public static void main(String[] args) {
            StringStringMap m = new StringStringMap();
            m.put("foo","bar");
            System.out.println(m.get("foo"));
        }
    }

    我发现这比SWIG更容易,更清晰......