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

我有一个在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映射传递给c方法.

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

解决方法

为了做到这一点,你需要告诉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> %{
  static $javaclassname convertMap(Map<String,String> in) {
    // 2.
    if (in instanceof $javaclassname) {
      return ($javaclassname)in;
    }

    $javaclassname out = new $javaclassname();
    for (Map.Entry<String,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,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,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方法现在将此情况作为优化进行检查.
> EntrySet是AbstractMap的主要要求.我们已经定义了(稍后)MapTypeEntry来为我们实现Map.Entry接口.这将在稍后的%extend中使用更多代码,以有效地将所有键枚举为数组.请注意,这不是线程安全的,如果我们更改地图,而此枚举正在进行中,则会发生奇怪的错误,并且可能无法检测到.
> remove是我们必须实现的方法之一才能变得可变. remove和put都必须返回旧值,所以这里有一些额外的Java来实现,因为C map不会这样做.
>即使size()也不兼容,因为需要进行long / int转换.真的,我们应该检测到非常大的地图的精度损失,并为溢出做一些理智的事情.
>我无聊地在任何地方输入java.util.Map,这使得生成的SWIG代码需要导入.
>这将MapType设置为从AbstractMap继承,以便我们代理并满足Java映射的要求,而不是执行额外的副本以进行转换.
>将作为我们的条目的类的C定义.这只是一个键,然后是指向它所拥有的地图的指针.该值不存储在Entry对象本身中,并始终返回到底层映射.这种类型也是不可变的,我们无法改变拥有的地图或密钥.
>这就是SWIG所看到的.我们提供了一个额外的get / setValue函数,可以回调它所源自的地图.没有公开指向拥有地图的指针,因为我们不需要这样做,它实际上只是一个实现细节.
> java.util.Map.Entry< String,String>.
>这是一个自动填充%extend内部某些代码的jenv参数的技巧,我们需要在该代码中进行一些JNI调用.
>%extends中的这两个方法将所有键和值分别放入输出数组中.传入时,数组的大小应该是正确的.有一个断言来验证这个,但实际上它应该是一个例外.这两个都是内部实现细节,可能应该是私有的.它们被需要批量访问键/值的所有函数使用.
> foo实际执行完整性检查我的代码.

内存管理在这里免费发生,因为它仍归C代码所有. (所以你仍然需要决定如何管理C容器的内存,但这并不是什么新鲜事).由于返回到Java的对象只是C映射的包装器,因此容器的元素不必比它更长.在这里,它们也是特殊的字符串,它们作为新对象返回,如果它们是使用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简介:ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增...
一、进程与线程 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。 线程...
本文为博客园作者所写:&#160;一寸HUI,个人博客地址:https://www.cnblogs.com/zsql/ 简单的一个类...
#############java面向对象详解#############1、面向对象基本概念2、类与对象3、类和对象的定义格式4、...
一、什么是异常? 异常就是有异于常态,和正常情况不一样,有错误出错。在java中,阻止当前方法或作用域...
Collection接口 Collection接口 Collection接口 Collection是最基本的集合接口,一个Collection代表一组...