为了做到这一点,您需要告诉SWIG使用
java.util.Map
作为输入参数,使用
%typemap(jstype)
。您还需要提供一些代码来将Java映射类型转换为C++的
std::map
类型,SWIG会在适当的位置注入此代码。我编写了一个小例子(已编译,但未经测试)来说明这一点:
%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文档中的此示例。
支持从C++返回std::map
到Java需要进行更多的工作,但是这是可能的。 java.util.Map
是一个接口,因此我们需要调整std::map
的默认包装以满足该接口。实际上,使用java.util.AbstractMap
并从中继承更容易,尽管我最终还是重写了其中大多数函数。整个解决方案类似于我的std::vector
答案的这个问题。
在最终版本中有相当多的组成部分。我将在此完整呈现它,并附有注释说明:
%module test
%{
#include <cassert>
#include <iostream>
%}
%include <std_map.i>
%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) {
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;
}
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));
}
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;
}
public int size() {
return (int)size_impl();
}
%}
%typemap(javaimports) std::map<std::string, std::string> "import java.util.*;";
%typemap(javabase) std::map<std::string, std::string> "AbstractMap<String, String>";
%{
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;
};
%}
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);
};
%typemap(javainterfaces) map_entry<std::string, std::string> "java.util.Map.Entry<String,String>";
%typemap(in,numinputs=0) JNIEnv * %{
$1 = jenv;
%}
%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>;
%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 << "\n";
}
return std::map<std::string, std::string>(in);
}
%}
std_map.i
并不是用来实现任何接口/抽象类的。我们需要重命名一些暴露出来的内容才能这样做。
- 由于我们让我们的类型实现了
Map
(通过AbstractMap
),所以当这只是一个复制操作时,从MapType
-> MapType
转换是愚蠢的。现在,convertMap
方法检查这种情况作为优化。
EntrySet
是AbstractMap
的主要要求。我们定义了(稍后) MapTypeEntry
来为我们实现Map.Entry
接口。这使用了一些更多的代码在%extend
内部来高效地将所有键枚举为数组。请注意,如果我们在此枚举正在进行时更改映射,则不是线程安全的,会发生奇怪的坏事情,可能无法被检测到。
remove
是我们必须实现的方法之一,以便变得可变。由于C++映射不执行此操作,因此remove
和put
都必须返回旧值,因此需要一些额外的Java代码来实现这一点。
- 甚至
size()
也不兼容,因为需要长/整数转换。实际上,我们应该在非常大的映射中检测精度损失,并对溢出做出明智的处理。
- 我厌倦了到处输入
java.util.Map
,所以这使得生成的SWIG代码具有所需的导入。
- 这设置了
MapType
继承自AbstractMap
,以便我们代理并满足Java map的要求,而不是进行额外的复制以进行转换。
- C++类的定义将作为我们条目的类。这只有一个键,然后是指向拥有它的地图的指针。值不存储在
Entry
对象本身中,并且始终参考底层地图。这种类型也是不可变的,我们无法更改所拥有的地图或键。
- 这是SWIG看到的。我们提供了一个额外的get/setValue函数,它回调到它来自的地图。拥有地图的指针没有暴露出来,因为我们没有必要这样做,这实际上只是一个实现细节。
java.util.Map.Entry<String,String>
。
- 这是一个技巧,可以自动填充
%extend
内部一些代码的jenv
参数,我们需要在该代码内部进行一些JNI调用。
%extend
内部的这两个方法分别将所有键和值放入输出数组中。传递时,预期数组具有正确的大小。有一个assert来验证这一点,但实际上它应该是一个异常。这两个都是内部实现细节,可能应该是私有的。它们被所有需要批量访问键/值的函数使用。
foo
的实际实现以检查我的代码是否正确。
内存管理在这里是免费的,因为它仍然由C++代码拥有。(所以您仍然需要决定如何管理C++容器的内存,但这并不新奇)。由于返回给Java的对象只是一个围绕C++ map的包装器,容器的元素不必存在于其之后。在这里它们还是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<String,String> 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}
const
也不是引用,而我假设这是一个非变异函数? - Flexo