7

C++ で定義されたメソッドがあります。

std::map<std::string, std::string> validate(
                                   std::map<std::string, std::string> key, 
                                   std::map<std::string, std::string> value
                                   );

このメソッドを Java で使用したい。そのため、Swig を使用して C++ メソッドにJava MapSTL として渡すことができるラッパーを作成する必要があります。map

この作業を行うには、swig の .i ファイルをどのように定義すればよいか教えてください。

4

3 に答える 3

8

これを行うには、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++ から Java への復帰をサポートするにstd::mapは、かなり多くの作業が必要ですが、可能です。java.util.Mapはインターフェイスであるため、そのインターフェイスに合わせて のデフォルトのラッピングを調整する必要がありstd::mapます。java.util.AbstractMapとにかく、ほとんどの関数をオーバーライドすることになりましたが、実際には、それを使用して継承する方が簡単です。このソリューション全体は 、 に対する私の答えに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から変換するのはばかげています。このメソッドは、最適化としてこのケースをチェックするようになりました。MapTypeMapTypeconvertMap
  3. EntrySetの主な要件ですAbstractMap。インターフェイスMapTypeEntryを実装するために(後で)定義しました。これは、後でMap.Entry内部でいくつかのコードを使用して、すべてのキーを配列として効率的に列挙します。%extendこれはスレッドセーフではないことに注意してください。この列挙の進行中にマップを変更すると、奇妙な悪いことが起こり、検出されない可能性があります。
  4. removeミュータブルにするために実装しなければならないメソッドの 1 つです。removeとの両方putが古い値を返す必要があるため、C++ マップではそれができないため、これを実現するために Java が少し追加されています。
  5. long size()/int 変換が必要なため、互換性がありません。本当に、非常に大きなマップのどこかで精度の低下を検出し、オーバーフローに対して適切な処理を行う必要があります。
  6. どこでも入力するのに飽きjava.util.Mapたので、生成された SWIG コードにインポートが必要になります。
  7. MapTypeこれにより、を から継承するように設定されるAbstractMapため、元に戻すために余分なコピーを行うのではなく、Java マップの要件をプロキシして満たすことができます。
  8. エントリとして機能するクラスの C++ 定義。これには、キーと、それが所有するマップへのポインターがあります。値はEntryオブジェクト自体には保存されず、常に基になるマップに参照されます。この型も不変です。所有するマップやキーを変更することはできません。
  9. これはSWIGが見ているものです。元のマップにコールバックする追加の get/setValue 関数を提供します。所有しているマップへのポインターは、公開する必要がなく、実際には実装の詳細にすぎないため、公開されていません。
  10. java.util.Map.Entry<String,String>。_
  11. これは、そのコード内でいくつかの JNI 呼び出しを行う必要がjenvあるコード内の引数を自動入力するトリックです。%extend
  12. 内部のこれら 2 つのメソッド%extendは、すべてのキーと値をそれぞれ出力配列に配置します。The array is expected to be the right size when passed in. これを検証するアサートがありますが、実際には例外である必要があります。これらはどちらも内部実装の詳細であり、とにかく非公開にする必要があります。これらは、キー/値への一括アクセスを必要とするすべての関数で使用されます。
  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}
于 2012-05-31T06:44:29.757 に答える
1

または、 JavaCPPMapTest.hの助けを借りて、完全に Java で行うこともできます (関数宣言がヘッダー ファイルにあると仮定します) 。

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よりも簡単で明確だと思います...

于 2012-09-18T12:41:03.810 に答える