VolAnd
VolAnd

Reputation: 6407

Using SWIG for setting types in overloaded C++ method

I am writing C++ library that will be used by applications on different platforms, including Android. And for Android build SWIG is used (I cannot change this choice, and I have never worked with swig). Automatic type conversion for Java-to-C++ worked fine until I was assigned to task of init method overloading.

For old init C++ interface is:

        /**
        * Initialize classifier from file
        * @param modelPath std::string  Full path to file with model
        * @return Classifier            Possible values:
        *                               pointer to Classifier instance in case of success
        *                               nullptr otherwise
        */
        static Classifier* init(const std::string& modelPath);

the following code is generating:

  public static Classifier init(String modelPath) {
    long cPtr = CLJNI.Classifier_init__SWIG_0(modelPath);
    return (cPtr == 0) ? null : new Classifier(cPtr, true);
  }

For new init C++ is:

        /**
        * Initialize classifier from memory block
        * @param modelMemory char*       Pointer to the memory block where model definition is stored
        * @param modelMemorySize size_t  Size of memory block allocated for model storage
        * @return Classifier             Possible values:
        *                                pointer to Classifier instance in case of success
        *                                nullptr otherwise
        */
        static Classifier* init(char* modelMemory, const size_t modelMemorySize);

and Java code generated by SWIG is:

  public static Classifier init(String modelMemory, long modelMemorySize) {
    long cPtr = CLJNI.Classifier_init__SWIG_1(modelMemory, modelMemorySize);
    return (cPtr == 0) ? null : new Classifier(cPtr, true);
  }

But I need Java method as:

Classifier init(byte[] modelMemory, long modelMemorySize);

i.e. char* have to be converted to byte[] instead of String.

What changes should I do in Classifier.i file (that now looks as follows) to have two init methods available for call from Java?

%{
#include "Group.h"
#include "Classifier.h"
%}

//Java programmers are afraid of 'delete' method, so we have to rename it.
//Yes, bloody stupid, I know.
%typemap(javadestruct, methodname="dispose", methodmodifiers="public synchronized") CL::Classifier {
    if(swigCPtr != 0 && swigCMemOwn) {
      swigCMemOwn = false;
      $jnicall;
    }
    swigCPtr = 0;
}

%typemap(javafinalize) CL::Classifier %{
    protected void finalize() {
      dispose();
    }
%}

%newobject CL::Classifier::init;

%include "Classifier.h"

Upvotes: 1

Views: 420

Answers (1)

VolAnd
VolAnd

Reputation: 6407

The following solution (based on Flexo's example) checked on SWIG 2.0.11 and SWIG 3.0.8:

%module Classifier

%{
#include "Group.h"
#include "Classifier.h"
%}

%typemap(jtype) (signed char* modelMemory, const size_t modelMemorySize) "byte[]"
%typemap(jstype) (signed char* modelMemory, const size_t modelMemorySize) "byte[]"
%typemap(jni) (signed char* modelMemory, const size_t modelMemorySize) "jbyteArray"
%typemap(javain) (signed char* modelMemory, const size_t modelMemorySize) "$javainput"

%typemap(in,numinputs=1) (signed char* modelMemory, const size_t modelMemorySize) {
  $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL);
  $2 = JCALL1(GetArrayLength, jenv, $input);
}

%typemap(freearg) (signed char* modelMemory, const size_t modelMemorySize) {
  // Or use  0 instead of ABORT to keep changes if it was a copy
  JCALL3(ReleaseByteArrayElements, jenv, $input, $1, JNI_ABORT);
}

%typemap(javadestruct, methodname="dispose", methodmodifiers="public synchronized") CL::Classifier {
    if(swigCPtr != 0 && swigCMemOwn) {
      swigCMemOwn = false;
      $jnicall;
    }
    swigCPtr = 0;
}

%typemap(javafinalize) CL::Classifier %{
    protected void finalize() {
      dispose();
    }
%}

%newobject CL::Classifier::init;

%include "Classifier.h"

%include "arrays_java.i" and %apply (written in original example) found unnecessary.

This works good and provides two init methods:

  public static Classifier init(String modelPath) {
    long cPtr = CLJNI.Classifier_init__SWIG_0(modelPath);
    return (cPtr == 0) ? null : new Classifier(cPtr, true);
  }

  public static Classifier init(byte[] modelMemory) {
    long cPtr = CLJNI.Classifier_init__SWIG_1(modelMemory);
    return (cPtr == 0) ? null : new Classifier(cPtr, true);
  }

But requires usage of signed char* type instead of char*. Otherwise

 public static Classifier init(String modelMemory, long modelMemorySize)

is produced.

Also I have found that for SWIG 3.0.8 solution can be in only one extra line:

 %apply(char *STRING, size_t LENGTH) { (signed char *modelMemory, size_t modelMemorySize) };

I.e. Classifier.i is:

%module Classifier

%{
#include "Group.h"
#include "Classifier.h"
%}

%apply(char *STRING, size_t LENGTH) { (signed char *modelMemory, size_t modelMemorySize) };

%typemap(javadestruct, methodname="dispose", methodmodifiers="public synchronized") CL::Classifier {
    if(swigCPtr != 0 && swigCMemOwn) {
      swigCMemOwn = false;
      $jnicall;
    }
    swigCPtr = 0;
}

%typemap(javafinalize) CL::Classifier %{
    protected void finalize() {
      dispose();
    }
%}

%newobject CL::Classifier::init;

%include "Classifier.h"

With SWIG 2.0.11 this way leads to compilation error because incorrect wrapper (CLJAVA_wrap.cxx) generation:

SWIGEXPORT jlong JNICALL Java_impl_tools_CLJNI_Classifier_1init_1_1SWIG_11(JNIEnv *jenv, jclass jcls, jbyteArray jarg1) {
  jlong jresult = 0 ;
  signed char *arg1 = (signed char *) 0 ;
  size_t arg2 ;
  CL::Classifier *result = 0 ;

  (void)jenv;
  (void)jcls;
  {
    if (jarg1) {
      arg1 = (char *) jenv->GetByteArrayElements(jarg1, 0);     // ERROR HERE: invalid conversion from 'char*' to 'signed char*'
      arg2 = (size_t) jenv->GetArrayLength(jarg1);
    } else {
      arg1 = 0;
      arg2 = 0;
    }
  }
  result = (CL::Classifier *)CL::Classifier::init(arg1,arg2);
  *(CL::Classifier **)&jresult = result; 
  {
    if (jarg1) jenv->ReleaseByteArrayElements(jarg1, (jbyte *)arg1, 0);
  }

  return jresult;
}

Upvotes: 1

Related Questions