使用SWIG将Java Map <String,String>传递给C++方法 - java

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

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文件以使其正常工作。

参考方案

为此,您需要使用java.util.Map告诉SWIG将%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文档中的this example。

要支持从C++的std::map返回Java,还需要做很多工作,但有可能。 java.util.Map是一个接口,因此我们需要调整std::map的默认包装以适应该接口。实际上,使用java.util.AbstractMap并从中继承更容易,尽管无论如何我最终还是重写了大多数功能。整个解决方案类似于my answer for std::vector

我的最终版本中有很多可动部分。我将在此处提供完整的注释,并带有注释:

%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 << "\n";
    }

    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()也不兼容,因为需要进行长/整数转换。确实,对于大型地图,我们应该检测到某处的精度损失,并对溢出进行合理的处理。
  • 我无聊在任何地方键入java.util.Map,所以这使得生成的SWIG代码具有所需的导入。
  • 这将MapType设置为从AbstractMap继承,以便我们代理并满足Java映射的要求,而不是进行额外的复制以转换回原位。
  • 将用作我们条目的类的C++定义。它只有一个键,然后有一个指向它所拥有的地图的指针。该值不存储在Entry对象本身中,并且始终被引用回基础映射。此类型也是不可变的,我们永远无法更改拥有的地图或键。
  • 这是SWIG看到的。我们提供了一个额外的get / setValue函数,该函数可回调其来源的地图。没有公开指向拥有地图的指针,因为我们不需要这样做,它实际上只是一个实现细节。
  • java.util.Map.Entry<String,String>
  • 这个技巧可以自动填充jenv中某些代码的%extend参数,我们需要在该代码中进行一些JNI调用。
  • %extend中的这两个方法分别将所有键和值放入输出数组中。传入数组时,该数组的大小应正确。有一个断言可以对此进行验证,但实际上应该是一个例外。这两个都是内部实施细节,无论如何都应该是私有的。它们被需要批量访问键/值的所有功能所使用。
  • 合理执行foo来检查我的代码。
  • 内存管理在这里免费进行,因为它仍然归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<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}
    

    无法从ArrayList <String>转换为List <Comparable> - java

    当我写下面的代码时,编译器说 无法从ArrayList<String>转换为List<Comparable>private List<Comparable> get(){ return new ArrayList<String>(); } 但是当我用通配符编写返回类型时,代码会编译。private List&l…

    Java:我可以在Hashmaps中使用数组吗? - java

    我可以在Hashmaps中使用数组吗?如果是这样,则声明这种哈希图的确切语法是什么?谢谢 参考方案 数组也是对象。甚至像int[]这样的原始数组。Map<String,String[]> map = new HashMap<String,String[]>();

    Java中的<<或>>>是什么意思? - java

    This question already has answers here: Closed 7 years ago. Possible Duplicate: What does >> and >>> mean in Java?我在一些Java代码中遇到了一些陌生的符号,尽管代码可以正确编译和运行,但对于括号在此代码中的作用却感…

    菱形运算符<>是否等于<?> - java

    我在util.TreeSet类中发现,其中一个构造函数正在使用具有空泛型类型的新TreeMap调用另一个构造函数。 public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); } new TreeMap<>是什么意思…

    将scala.collection.Seq <String>的Java对象转换为python列表 - java

    在pyspark sparkSession中,以sc作为我的sparkContext,调用getRDDStorageInfo() Java sparkContext _jsc.sc(),该方法返回一个我可以在python中进行迭代的RDDInfo[]的JavaObject实例。sc._jsc.sc().getRDDStorageInfo()返回JavaObj…