2

The Problem

I'm working on an Android application in which I have to pass a OpenCV Mat between the Java code an the C++ code. For this purpose I created the following SWIG typemaps which are working fine:

%include "std_map.i"
%include "std_shared_ptr.i"
...
%shared_ptr(cv::Mat)
...
// normal typemaps for cv::Mat w/o shared_ptr
%typemap(jstype) cv::Mat, cv::Mat& "org.opencv.core.Mat"
%typemap(javain) cv::Mat, cv::Mat& "$javainput.getNativeObjAddr()"
%typemap(jtype) cv::Mat, cv::Mat& "long"
%typemap(jni) cv::Mat, cv::Mat& "jlong" 

%typemap(in) cv::Mat, cv::Mat& {
    $1 = *(cv::Mat **)&$input;
}
%typemap(javaout) cv::Mat, cv::Mat& {
    return new org.opencv.core.Mat($jnicall);
}

// rather hacky javaout typemap override for the shared_ptr
%typemap(javaout) std::shared_ptr< cv::Mat > {
    long cPtr = $jnicall;
    return (cPtr == 0) ? null : new org.opencv.core.Mat(cPtr);
}

Anyhow, at some point I have to return a std::map<std::string, std::shared_ptr<cv::Mat>> to Java. I did this using the map template with

%template(Map_String_Shared_ptr_Mat) std::map<std::string, std::shared_ptr<cv::Mat>>;

which produces the following getmethod in Java:

public org.opencv.core.Mat get(String key) {
    long cPtr = xyJNI.Map_String_Shared_ptr_Mat_get(swigCPtr, this, key);
    return (cPtr == 0) ? null : new org.opencv.core.Mat(cPtr, true);

}

which is not using the javaout typemap provided earlier. (It works on a function returning a std::shared_ptr<cv::Mat> but not in the map template)

What I've tried so far

I tried to insert my own get method via

%typemap(javacode) std::map<std::string, std::shared_ptr<cv::Mat>> %{
   public org.opencv.Mat get{
      ...
   }
%};

but this results in a conflict since two get methods exist.

Also, when I tried to %ignore the get method first, the corresponding xyJNI.Map_String_Shared_ptr_Mat_get(swigCPtr, this, key); is not created, and therefor I can't provide my own implementation of the get

The Question

Now I need either a way to tell SWIG to apply that javaout typemap. But I would also be fine with a way of overriding the get method body, to use the correct constructor for a Mat.

I hope someone can help me with this issue

Note: I don't care about Map_String_Shared_ptr_Mat not being a real Map in Java. That's not a problem for me

Edit: Added the shared_ptr javaout typemap

super-qua
  • 3,148
  • 1
  • 23
  • 30

1 Answers1

0

Unfortunately I was not able to find a solution to either have the javaout typemap applied to the Mat_String_Shared_ptr_Mat.get(), nor to substitute the method content of Mat_String_Shared_ptr_Mat.get().

What I ended up with is a workaround which returns the referenced cv::Mat (so not the shared_ptr) in a getPlain() method, to which the typemap is applied.

This is certainly not the perfect solution I was looking for, but it might help others facing a similar problem.


Solution

%include "std_map.i"

// 1.
%typemap(javaout) cv::Mat, cv::Mat& {
    return new org.opencv.core.Mat($jnicall);
}

...

// 2.
%ignore std::map<std::string, std::shared_ptr<cv::Mat>>::get;

// 3.
%template(Map_String_Shared_ptr_Mat) std::map<std::string, std::shared_ptr<cv::Mat>>;

// 4.
%extend std::map<std::string, std::shared_ptr<cv::Mat>>{
    const cv::Mat& getPlain(const std::string& key) throw (std::out_of_range) {
        std::map<std::string,std::shared_ptr< cv::Mat > >::iterator i = self->find(key);
        if (i != self->end())
            return *i->second;
        else
            throw std::out_of_range("key not found");
    }
}
  1. The typemap for the cv::Mat which is (unlike the shared_ptr typemap) applied to the wrapped map
  2. Ignore the get function (which will be self-implemented in step 4)
  3. Have SWIG create the wrapper via the template defined in std_map.i
  4. Extend the wrapper by adding a getPlain function to the map. This function returns the referenced cv::Mat object itself, rather than the shared_ptr. The javaout typemap of step 1 is applied when creating the Java code

Output

After running SWIG, the Java class Map_String_Shared_ptr_Mat contains the required getPlain method:

public org.opencv.core.Mat getPlain(String key) {
     return new org.opencv.core.Mat(xyJNI.Map_String_Shared_ptr_Mat_getPlain(swigCPtr, this, key));
}

Update

Note: This part is specific to cv::Mat

Because of the implementation of cv::Mat in Java, I had to change the implementation to perform call by reference in C++.

This is because the Java Mat implementation stores a pointer to the C++ Mat. In the finalize method, the C++ object is freed. In the previous implementation, the object may have been deleted already, which would result in a SIGSEGV crash.

Therefore the implementation was changed to the following:

%extend std::map<std::string, std::shared_ptr<cv::Mat>>{
    void getPlain(const std::string& key, cv::Mat& dest) throw (std::out_of_range) {
        std::map<std::string,std::shared_ptr< cv::Mat > >::iterator i = self->find(key);
        if (i != self->end()){
             (*i->second).copyTo(dest);
        } else{
            throw std::out_of_range("key not found");
        }
    }

which copies the data to the Mat in the second parameters, which itself is created in Java (and thus does not produce seg faults wenn freed).


The solution can be adjusted to create/wrap implementations for setand other functions respectively.

Note: I also ended up using some of the methods presented in this post: Passing Java Map<String, String> to C++ method using SWIG, which is definitely worth to have a look at.

Community
  • 1
  • 1
super-qua
  • 3,148
  • 1
  • 23
  • 30