13

I am using SWIG to access C++ code from Java.

What is the easiest way to expose a std::string parameter passed by non-const reference?

I have primitives passed by reference exposed as Java arrays, thanks to typemaps.i, and const std::string&s exposed as java.lang.String, thanks to std_string.i. But a non-const std::string& is exposed as opaque pointer type SWIGTYPE_p_std__string.

Current:

// C++ method                     -> // Java wrapper of C++ method
void foo( int & i )               -> public void foo( int[] i );    // OK
void bar( const std::string & s ) -> public void bar( String s );   // OK
void baz( std::string & s )       -> public void baz( SWIGTYPE_p_std__string s ); // :(

Desired:

void foo( int & i )               -> public void foo( int[] i );    // OK
void bar( const std::string & s ) -> public void bar( String s );   // OK
void baz( std::string & s )       -> public void baz( String[] s ); // OK

UPDATE: I found a solution, described below. However, it took more effort than a few seconds. I'm still interested in hearing about easy approaches.

Andy Thomas
  • 84,978
  • 11
  • 107
  • 151
  • Because the Java String class is immutable. So even if you pass a fake array of String you can not modify it like you are implying by passing a reference. – Martin York Sep 20 '10 at 17:16
  • @Martin York - String objects are immutable. The members of a String array, however, are mutable references to String objects. – Andy Thomas Sep 20 '10 at 19:01
  • @Andy Thomas-Cramer: But that's not the same as passing by reference. You are passing an object you can manipulate. But with an array of string the object is still not changeable. Unless you are suggesting that the called function can change the string by replacing it! Which would then require the Glue code to take the returned Java String object and put it into the passed C++ string object. – Martin York Sep 20 '10 at 19:05
  • @Martin York - I come to wrap pass-by-reference, not implement it. The goal is to send and receive data. The workaround for primitive types is to convert an array member before function entry and after function exit. I seek the same for std::string&, with a String[]. In Java, a String[] contains references -- pointers -- to String objects. The native glue code can easily replace the singleton array member with a new reference to a Java String converted from the local std::string it passed by reference. I know that the original Java String object has not been modified, but I don't need that. – Andy Thomas Sep 20 '10 at 19:56
  • @Andy Thomas-Cramer: I see what you are trying to do. But the problem is that int/String are fundamentally different in Java. Note that int is un-boxed and not an object. – Martin York Sep 20 '10 at 20:16
  • @Martin York - Java ints and String *references* are also different, but share the similarity that both can be members of mutable arrays. If you can show infeasibility, post it as an answer, and I will accept it. But first, take a look at http://ideone.com/Fndze. Others: I am very interested in hearing from someone with experience with SWIG. – Andy Thomas Sep 20 '10 at 20:56

1 Answers1

9

The best approach I could find was to write my own typemap. I had been hoping for a few trivial SWIG instructions.

In case anyone else needs this, here's how I did it. Bear in mind that I am not a SWIG expert.

First, you need to define some typemaps to be applied to std::string& arguments. You only have to define these once. (Note: there are additional typemaps that may be required in some configurations.)

%typemap(jni) std::string *INOUT, std::string &INOUT %{jobjectArray%}
%typemap(jtype) std::string *INOUT, std::string &INOUT "java.lang.String[]"
%typemap(jstype) std::string *INOUT, std::string &INOUT "java.lang.String[]"
%typemap(javain) std::string *INOUT, std::string &INOUT "$javainput"

%typemap(in) std::string *INOUT (std::string strTemp ), std::string &INOUT (std::string strTemp ) {
  if (!$input) {
    SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "array null");
    return $null;
  }
  if (JCALL1(GetArrayLength, jenv, $input) == 0) {
    SWIG_JavaThrowException(jenv, SWIG_JavaIndexOutOfBoundsException, "Array must contain at least 1 element");
    return $null;
  }

  jobject oInput = JCALL2(GetObjectArrayElement, jenv, $input, 0); 
  if ( NULL != oInput ) {
    jstring sInput = static_cast<jstring>( oInput );

    const char * $1_pstr = (const char *)jenv->GetStringUTFChars(sInput, 0); 
    if (!$1_pstr) return $null;
    strTemp.assign( $1_pstr );
    jenv->ReleaseStringUTFChars( sInput, $1_pstr);  
  }

  $1 = &strTemp;
}

%typemap(freearg) std::string *INOUT, std::string &INOUT ""

%typemap(argout) std::string *INOUT, std::string &INOUT
{ 
  jstring jStrTemp = jenv->NewStringUTF( strTemp$argnum.c_str() );
  JCALL3(SetObjectArrayElement, jenv, $input, 0, jStrTemp ); 
}

Next, for each C++ argument pattern like this ...

void foo( std::string & xyzzy );
void bar( std::string & xyzzy );
void baz( ..., std::string & xyzzy, ... );

... you apply the typemaps above with this SWIG directive:

%apply std::string &INOUT { std::string & xyzzy };

The resulting bindings look like this:

public void foo( java.lang.String[] xyzzy );
public void bar( java.lang.String[] xyzzy );
public void baz( ..., java.lang.String[] xyzzy, ... );

They each require a one-element String array. On entry, the first element may be null. If non-null, it is converted to a UTF-8 std::string value and passed to the C++ function. On exit, the value of the std::string passed by reference is converted back from UTF-8 to a Java String.

Andy Thomas
  • 84,978
  • 11
  • 107
  • 151
  • Back fromt he dead - i think I am asking this same question here: http://stackoverflow.com/questions/4652638/how-to-pass-strings-to-c-function-from-java-using-swig-generated-interface -can you point me to some starting documentation? Where do you define the typemaps? How do you make SWIG use it? I tried to put it in typemaps.i and %include it to my module file, and my outputs looked exactly the same – Derek Jan 10 '11 at 23:57
  • I think you should change ``INPUT`` to ``OUTPUT`` because ``c++`` code is returning the ``string`` as output. check this post : http://stackoverflow.com/a/11967859/365229 – Behrouz.M Jun 17 '15 at 09:43
  • @raypixar - Is it possible that you're misreading `INOUT` above as "INPUT"? INOUT parameters can be used as both input and output. In general, a function like `void baz( std::string & s )` might use the parameter for input and/or output. See more detail on INOUT at http://www.swig.org/Doc2.0/SWIGDocumentation.html#Arguments_nn6 – Andy Thomas Jun 17 '15 at 19:24
  • @AndyThomas OMG! I am blind! – Behrouz.M Jun 18 '15 at 09:37