tags:

views:

181

answers:

1

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.

+2  A: 

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-Cramer