Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
94 views
in Technique[技术] by (71.8m points)

c++ - Wrapping a function with two bi-directional parameters using SWIG to use in java [foo(char ** strs, unsigned int& size)]

I'm using SWIG 4.0.2 to wrap a C++ library to use from Java code. One of the classes has a tricky function that I'm having difficulties to wrap:

class Bar {
...
public:
    Status foo(char **strs, unsigned int& size);
    ...
}

The way "foo" is intended to be used is to allocate an array of strings and call the function giving it the array and the number of elements in it. The function will fill the array with null terminated strings and modify the "size" parameter to match the number of elements it filled (if it wrote less than the maximum number of elements).

Usage example:

#define MAX_STR_LENGTH 16 // Max string length is known

unsigned int numElements = 5;
char** strs= new char*[numElements];
for (unsigned int i=0; i<numElements; ++i) {
    strs[i] = new char[MAX_STR_LENGTH];
}

Status status = bar.foo(strs, numElements);

if (status == Success) {
    for (unsigned int i=0; i<numElements; ++i) {
        std::cout << strs[i] << std::endl;
    }
}

I'm flexible regarding how the Java function signature will look like, but I need to be able to extract the strings somehow after calling foo().

P.S: "Bar" is actually a big class that so far SWIG did a great job wrapping for me, so I would hate to have to manually wrap it in JNI code.

question from:https://stackoverflow.com/questions/65893646/wrapping-a-function-with-two-bi-directional-parameters-using-swig-to-use-in-java

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I've finally found the solution to this. The full solution is actually a mix of code taken from SWIG's own various.i file with a little tweak to handle the "char **strs" parameter and applying an existing typemap for the other parameter.

Here is the relevant code from the .i file:

%typemap(jni) char **STRING_IN_OUT "jobjectArray"
%typemap(jtype) char **STRING_IN_OUT "String[]"
%typemap(jstype) char **STRING_IN_OUT "String[]"
%typemap(in) char **STRING_IN_OUT (jint size) {
  int i = 0;
  if ($input) {
    size = JCALL1(GetArrayLength, jenv, $input);
#ifdef __cplusplus
    $1 = new char*[size+1];
#else
    $1 = (char **)malloc((size+1) * sizeof(char *));
#endif
    for (i = 0; i<size; i++) {
      jstring j_string = (jstring)JCALL2(GetObjectArrayElement, jenv, $input, i);
      const char *c_string = JCALL2(GetStringUTFChars, jenv, j_string, 0);
#ifdef __cplusplus
      $1[i] = new char [strlen(c_string)+1];
#else
      $1[i] = (char *)malloc((strlen(c_string)+1) * sizeof(const char *));
#endif
      strcpy($1[i], c_string);
      JCALL2(ReleaseStringUTFChars, jenv, j_string, c_string);
      JCALL1(DeleteLocalRef, jenv, j_string);
    }
    $1[i] = 0;
  } else {
    $1 = 0;
    size = 0;
  }
}

%typemap(argout) char **STRING_IN_OUT {
  for (int i=0; i< (int) size$argnum; i++) {
    jstring jnewstring = NULL;
    jnewstring = JCALL1(NewStringUTF, jenv, $1[i]);
    JCALL3(SetObjectArrayElement, jenv, $input, i, jnewstring); 
  }
}

%typemap(freearg) char **STRING_IN_OUT {
  int i;
  for (i=0; i<size$argnum; i++)
#ifdef __cplusplus
    delete[] $1[i];
  delete[] $1;
#else
  free($1[i]);
  free($1);
#endif
}

%typemap(out) char **STRING_IN_OUT {
  if ($1) {
    int i;
    jsize len=0;
    jstring temp_string;
    const jclass clazz = JCALL1(FindClass, jenv, "java/lang/String");

    while ($1[len]) len++;
    $result = JCALL3(NewObjectArray, jenv, len, clazz, NULL);
    /* exception checking omitted */

    for (i=0; i<len; i++) {
      temp_string = JCALL1(NewStringUTF, jenv, *$1++);
      JCALL3(SetObjectArrayElement, jenv, $result, i, temp_string);
      JCALL1(DeleteLocalRef, jenv, temp_string);
    }
  }
}

%typemap(javain) char **STRING_IN_OUT "$javainput"
%typemap(javaout) char **STRING_IN_OUT {
    return $jnicall;
  }

%apply char **STRING_IN_OUT { char** strs}

%include "typemaps.i"
%apply unsigned int& INOUT { unsigned int& size}

And here is how I call it from Java:

// For now, this is how I tell the JNI how many buffers to allocate and their length
// I Will probably look for a way to work around this useless allocation in the future.
String[] strs = new String[MAX_NUMBER_STRINGS];
for (int i = 0 ; i < strs.length ; ++i) {
  strs[i] = new String(new char[BUFFER_LENGTH]);
}

long[] numOfIds = new long[]{MAX_NUMBER_STRINGS};
Status s = m_faceAuthenticator.QueryUserIds(strs, numOfIds);

Took me a lot of time to solve it.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...