Passing Java Map <String, String> to C ++ Method Using SWIG

I have a method defined in C ++:

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

I want to use this method in Java. So, I have to write a shell using Swig through which I can pass the Java Map as an STL map to the C ++ method.

Please let me know how I should define the .i file for swig to make this work.

+6
source share
2 answers

To do this, you will need to tell SWIG to use java.util.Map for the input argument using %typemap(jstype) . You will also need to provide code to convert from a Java map type to a C ++ type std::map , which SWIG will enter at the appropriate points. I put together a small (compiled but untested) example to illustrate this:

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

The pgcppname part ensures that the std::map we pass does not receive garbage collection too early. See this example in the SWIG documentation for more details on how this works.

To support the return from std::map from C ++ to Java, a little more work is required, but it is possible. java.util.Map is an interface, so we need to adapt the default packaging of std::map to match this interface. In practice, it is easier to use java.util.AbstractMap and inherit from it, although I still left most of the functions in this. All this solution is similar to my answer for std::vector .

There are many moving parts in my final version. I will present it here with annotated notes:

 %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 is not intended to implement any interface / abstract class. To do this, we need to rename some of them.
  • Since we are doing our type of implementation of Map (via AbstractMap ), it is foolish to end the conversion from MapType โ†’ MapType when it is literally just a copy operation. The convertMap method convertMap checks this case as an optimization.
  • EntrySet is a basic requirement of AbstractMap . We defined (later) MapTypeEntry to implement the Map.Entry interface for us. Subsequently, it uses another code inside %extend to efficiently get an enumeration of all keys as an array. Please note that this is not thread safe if we change the map while this census continues, strange bad things will happen and may not be detected.
  • remove is one of the methods we must implement in order to be mutable. Both remove and put should return the old value, so there is a bit of extra Java here to make this happen, since C ++ maps do not.
  • Even size() incompatible due to the required long / integral conversion. Indeed, we must detect a loss of accuracy somewhere for very large maps and do something normal for overflow.
  • I got bored typing java.util.Map everywhere, so this makes the necessary SWIG code necessary for importing.
  • This sets MapType to inherit from AbstractMap , so we are proxies and meet the requirements of a Java map, rather than making an extra copy to convert back.
  • The definition of a C ++ class that will act as our entry. It has only a key, and then a pointer to the card in which it belongs. The value is not stored in the Entry object itself and always refers to the basemap. This type is also unchanged, we cannot change the ownership card or key ever.
  • This is what SWIG sees. We provide an optional get / setValue function that accesses the map from which it comes. The pointer to the ownership card is not displayed, since we are not required to do this, and this is really just an implementation detail.
  • java.util.Map.Entry<String,String> .
  • This is a trick that automatically populates the jenv argument of some code inside %extend , that we need to make some JNI calls inside this code.
  • These two methods inside %extend put all the keys and values โ€‹โ€‹respectively in the output array. It is expected that the array will have the right size when passing it. They claim to test this, but in fact it should be an exception. Both of these are internal implementation details, which should probably be private anyway. They are used by all functions requiring mass access to keys / values.
  • The actual implementation of foo to test the health of my code.

Memory management is free here, as it remains the property of C ++ code. (So, you have yet to decide how to manage the memory for the C ++ container, but nothing new). Since an object that returns in Java is only a wrapper around a C ++ map, container elements should not survive it. Here they are also Strings , which are special in that they are returned as new objects, if they were smart pointers using SWIG support std::shared_ptr , then everything will work as expected. The only case that would be difficult is map pointers to objects. In this case, the Java programmer must save the card and its contents at least until any Java proxies are returned.

Finally, I wrote the following Java for testing:

 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); } } 

What I compiled and worked like:

 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} 
+8
source

Or we could do it completely in Java (assuming that the declaration of your function can be found in the MapTest.h header MapTest.h ) using 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")); } } 

I find it easier, clearer than SWIG ...

+1
source

Source: https://habr.com/ru/post/916982/


All Articles