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

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

std::map validate( std::map key, std::map value ); 

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

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

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

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

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

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

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

 %module test %{ #include  #include  %} %include  // 1. %rename (size_impl) std::map::size; %rename (isEmpty) std::map::empty; %include  %typemap(jstype) std::map "java.util.Map" %typemap(javain,pre=" MapType temp$javainput = $javaclassname.convertMap($javainput);",pgcppname="temp$javainput") std::map "$javaclassname.getCPtr(temp$javainput)" %typemap(javacode) std::map %{ static $javaclassname convertMap(Map in) { // 2. if (in instanceof $javaclassname) { return ($javaclassname)in; } $javaclassname out = new $javaclassname(); for (Map.Entry entry : in.entrySet()) { out.set(entry.getKey(), entry.getValue()); } return out; } // 3. public Set> entrySet() { HashSet> ret = new HashSet>(size()); String array[] = new String[size()]; all_keys(array); for (String key: array) { ret.add(new MapTypeEntry(key,this)); } return ret; } public Collection values() { String array[] = new String[size()]; all_values(array); return new ArrayList(Arrays.asList(array)); } public Set keySet() { String array[] = new String[size()]; all_keys(array); return new HashSet(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 "import java.util.*;"; // 7. %typemap(javabase) std::map "AbstractMap"; // 8. %{ template  struct map_entry { const K key; map_entry(const K& key, std::map *owner) : key(key), m(owner) { } std::map * const m; }; %} // 9. template  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 *owner); }; // 10. %typemap(javainterfaces) map_entry "java.util.Map.Entry"; // 11. %typemap(in,numinputs=0) JNIEnv * %{ $1 = jenv; %} // 12. %extend std::map { void all_values(jobjectArray values, JNIEnv *jenv) const { assert((jsize)$self->size() == jenv->GetArrayLength(values)); jsize pos = 0; for (std::map::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::const_iterator it = $self->begin(); it != $self->end(); ++it) { jenv->SetObjectArrayElement(keys, pos++, jenv->NewStringUTF(it->first.c_str())); } } } %template(MapType) std::map; %template(MapTypeEntry) map_entry; // 13. %inline %{ std::map foo(std::map in) { for (std::map::const_iterator it = in.begin(); it != in.end(); ++it) { std::cout << it->first << ": " << it->second << "\n"; } return std::map(in); } %} 
  1. std_map.i并不意味着实现任何接口/抽象类。 我们需要重命名一些暴露的内容才能这样做。
  2. 因为我们使用类型实现Map (通过AbstractMap ),所以当它只是一个复制操作时,最终从MapType – > MapType转换是愚蠢的。 convertMap方法现在将此情况作为优化进行检查。
  3. EntrySetAbstractMap的主要要求。 我们已经定义了(稍后) MapTypeEntry来为我们实现Map.Entry接口。 这将在稍后的%extend使用更多代码,以有效地将所有键枚举为数组。 请注意,这不是线程安全的,如果我们在此枚举正在进行时更改地图,则会发生奇怪的错误,并且可能无法检测到。
  4. remove是我们必须实现的方法之一才能变得可变。 removeput都必须返回旧的值,所以这里有一些额外的Java来实现,因为C ++ map不会这样做。
  5. 即使size()也不兼容,因为需要进行long / int转换。 真的,我们应该检测到非常大的地图的精度损失,并为溢出做一些理智的事情。
  6. 我无聊地在任何地方输入java.util.Map ,这使得生成的SWIG代码需要导入。
  7. 这将MapType为从AbstractMapinheritance,以便我们代理并满足Java映射的要求,而不是执行额外的副本以进行转换。
  8. 作为我们的条目的类的C ++定义。 这只是一个键,然后是指向它所拥有的地图的指针。 该值不存储在Entry对象本身中,并始终返回到底层映射。 这种类型也是不可变的,我们无法改变拥有的地图或密钥。
  9. 这就是SWIG所看到的。 我们提供了一个额外的get / setValue函数,可以回调它所源自的地图。 没有公开指向拥有地图的指针,因为我们不需要这样做,而且它实际上只是一个实现细节。
  10. java.util.Map.Entry
  11. 这是一个自动填充%extend中的一些代码的jenv参数的技巧,我们需要在该代码中进行一些JNI调用。
  12. %extend这两个方法将所有键和值分别放入输出数组中。 传入时,数组的大小应该是正确的。有一个断言来validation这一点,但实际上它应该是一个例外。 这两个都是内部实现细节,可能应该是私有的。 它们被需要批量访问键/值的所有函数使用。
  13. foo到sanity的实际实现检查我的代码。

内存管理在这里免费发生,因为它仍归C ++代码所有。 (所以你仍然要决定如何管理C ++容器的内存,但这并不是什么新鲜事)。 由于返回到Java的对象只是C ++映射的包装器,因此容器的元素不必比它更长。 在这里,他们也是特殊的Strings ,它们作为新对象返回,如果它们是使用SWIG的std::shared_ptr支持的智能指针,那么一切都会按预期工作。 唯一棘手的情况是指向对象的指针。 在这种情况下,Java程序员有责任使映射及其内容保持活动,至少与返回的任何Java代理一样长。

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

 import java.util.Map; public class run { public static void main(String[] argv) { System.loadLibrary("test"); Map m = new MapType(); m.put("key1", "value1"); System.out.println(m); m = test.foo(m); System.out.println(m); } } 

我编译并运行为:

 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的帮助下:

 import com.googlecode.javacpp.*; import com.googlecode.javacpp.annotation.*; @Platform(include={"", "", "MapTest.h"}) public class MapTest { static { Loader.load(); } @Name("std::map") 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更容易,更清晰……