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