【问题标题】:Passing Java Map<String, String> to C++ method using SWIG使用 SWIG 将 Java Map<String, String> 传递给 C++ 方法
【发布时间】:2012-05-30 08:26:29
【问题描述】:

我有一个用 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 文件来完成这项工作。

【问题讨论】:

  • 我很好奇为什么您的输入映射既不是 const 也不是我认为是非变异函数的引用?

标签: java c++ swig


【解决方案1】:

为此,您需要告诉 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 文档中的 this example

要支持从std::map 从 C++ 返回到 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);
  }
%}
  1. std_map.i 并不意味着实现任何接口/抽象类。为此,我们需要重命名一些公开的内容。
  2. 因为我们让我们的类型实现了Map(通过AbstractMap),所以最终从MapType 转换到MapType 是很愚蠢的,而这实际上只是一个复制操作。 convertMap 方法现在检查这种情况作为优化。
  3. EntrySetAbstractMap 的主要要求。我们(稍后)定义了MapTypeEntry 来为我们实现Map.Entry 接口。这稍后会在%extend 中使用更多代码来有效地将所有键枚举为一个数组。请注意,这不是线程安全的,如果我们在此枚举过程中更改映射,则会发生奇怪的坏事并且可能不会被检测到。
  4. remove 是我们必须实现的可变方法之一。 removeput 都必须返回旧值,因此这里有一些额外的 Java 来实现这一点,因为 C++ 映射不这样做。
  5. 甚至 size() 也不兼容,因为需要进行 long/int 转换。真的,我们应该在某个地方检测到非常大的地图的精度损失,并为溢出做一些理智的事情。
  6. 我厌倦了在任何地方输入 java.util.Map,所以这使得生成的 SWIG 代码具有所需的导入。
  7. 这会将MapType 设置为从AbstractMap 继承,这样我们就可以代理并满足Java 映射的要求,而不是进行额外的复制来转换回来。
  8. 将作为我们的条目的类的 C++ 定义。这只有一个键,然后是一个指向它所拥有的地图的指针。该值不存储在 Entry 对象本身中,并且始终被引用回底层映射。这种类型也是不可变的,我们永远无法更改拥有的地图或密钥。
  9. 这就是 SWIG 所看到的。我们提供了一个额外的 get/setValue 函数,该函数回调它源自的地图。指向拥有地图的指针没有公开,因为我们不需要这样做,它实际上只是一个实现细节。
  10. java.util.Map.Entry&lt;String,String&gt;
  11. 这是一个自动填充 %extend 中某些代码的 jenv 参数的技巧,我们需要在该代码中进行一些 JNI 调用。
  12. %extend 中的这两个方法将所有的键和值分别放入一个输出数组中。该数组在传入时应该是正确的大小。有一个断言来验证这一点,但实际上它应该是一个例外。这两个都是内部实现细节,无论如何可能应该是私有的。它们被所有需要批量访问键/值的功能所使用。
  13. 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}

【讨论】:

  • 为了完整起见,您能否发布一个 javaout 的示例代码?我是 SWIG 的新手,这并不那么明显。 “相反”代码中的内存管理是如何处理的?
  • @aberaud 当然,我会在周末尝试做。我认为这里的 OP 方法不一定是正确的方法,因为它涉及大量与临时人员之间的复制。类似:stackoverflow.com/a/12551108/168175 但对于地图而不是矢量可能更好。 (或者改用 C++ 来使用 Java 映射)
  • @aberaud - 结果比我意识到的要多得多,因为唯一的方法是正确的方法,而不是我最初计划展示的快速破解。
【解决方案2】:

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

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 更容易、更清晰......

【讨论】:

    【解决方案3】:

    快速评论@Flexo 的建议:它有效,但 SWIG 需要 C++ 类型和 Java 类型之间的 &s 用于类型映射。我必须将它们添加到代码中的前两个类型映射中:

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

    希望这可以避免我在试图让它正常工作时所经历的困惑。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多