Pages

Friday, July 4, 2014

SWIG, callbacks and Java memory management

The quest for multi platform

I'm helping one of my customers implement a multi-platform system, which must run on both Android and Windows. Due to all sort of reasons (not all of them purely technical) the Android UI is implemented in Java (obvious) and the Windows UI in WPF (C#).
 To avoid duplicating the code, We (the customer and I) came up with the following stack:
  • We write the BL (Business Layer) code in C++.
  • We utilize Poco for cross-platform issues (threading, files, etc.)
  • We wrap the code with SWIG and access it from C#/Java
This way, at least, all the code that perform the same actions (non UI) is shareable between the platforms.

SWIG interface files

We like very much the fact that SWIG utilizes cpp (c pre processor) so that we can keep the same syntax and actually #include our .h files. Unfortunately, we had to include some SWIG directives that cannot be part of c++ .h files. Moreover, some of those directives are not the same for Java and CSharp. Still we would like to keep a single interface file.

so in those cases we have:
1:  #ifdef SWIGCSHARP  
2:  // do csharp related things like:  
3:     %include "arrays_csharp.i"  
4:  #endif  
5:  #ifdef SWIGJAVA  
6:  // do Java related things like  
7:     %include "enumtypeunsafe.swg"  
8:     %javaconst(1);  
9:  #endif  
For the rest of this post I'll only describe the Java related parts (but if anyone is interested in the csharp counterpart, please let me know)

Reactive Callbacks

One of our favorite paradigm is reactive callbacks, where we allow the UI to (re)act to changes. Usually those changes arise from C++ code and we want them to be acted upon in UI layers (in Java/C#). So we need to implement the reacting classes in those higher languages.

to do that, some more trickery was needed on the SWIG part:
1:  /* File : Swig.i */
2:  %module(directors="1") ImageRetrivalAPI  
3:    
4:  %apply signed char [ANY] {unsigned char* imageBuf}  
5:    
6:  /* turn on director wrapping Callback */  
7:  %feature("director") ICallback;  

in line 2: We Declare this module have directors. director classes' job is to route method calls correctly between java and C++.
in line 4:  We Creates mapping between C++ and Java/CSharp types. the syntax is based off swig/java/typemaps.i file. so that in this generic example, unsigned char* will be mapped to Java byte array.
in line 7: We Declare the name of the director (callback) class

Java directly-allocated nio buffers

If you didn't know that already, the JVM can move your arrays and buffers around underneath your neat Java code. Actually arrays/buffers doesn't have to be consistent in memory and thus a single buffer can look consistent to the Java code but be fragmented in real memory.
That means that if you pass an array from Java to C++ (through SWIG) and keep it's pointer, it might not be there when you want to use it.

Moreover, SWIG's default wrapping uses GetByteArrayElements() to get a pointer to the array. JVM might give you a direct access to the array or (in case it is fragmented) copy it to a new temporary, continuous memory area and give you a pointer to that area. SWIG then loops through the array elements and marshals each of them. At the other end of the function, it does the reverse operation ending with ReleaseByteArrayElements().

Not only this is slow, it is also not good for us, as our C++ call would receive and save that temporary pointer. then SWIG would free it and later on, when we will want to use it, it will not be there (and even if it would have been, it would have had nothing to do with the actual Java byte Array)

Even if you use GetByteArrayElements() you can't know if that will cause a memory copy or return an actual pointer. So we cannot use that. (as changes won't be reflected back to Java)

The solution is to use java.nio.buffer. and to allocate the buffer with allocateDirect, like this:
 
1:  java.nio.ByteBuffer = ByteBuffer.allocateDirect(<size>); 

SWIG counterpart

As I previously said - 'Normal' SWIG buffer handling won't work for us, so we need to create our own swig typemap to wrap this:
1:  //define new typemap  
2:     %typemap(jni) unsigned char* BUFFER "jobject"  
3:     %typemap(jtype) unsigned char* BUFFER "java.nio.ByteBuffer"  
4:     %typemap(jstype) unsigned char* BUFFER "java.nio.ByteBuffer"  
5:     %typemap(javain,  
6:        pre="  assert $javainput.isDirect() : \"Buffer must be allocated direct.\";") unsigned char* BUFFER "$javainput"  
7:     %typemap(javaout) unsigned char* BUFFER {  
8:       return $jnicall;  
9:     }  
10:     %typemap(in) unsigned char* BUFFER {  
11:      $1 = (unsigned char*) jenv->GetDirectBufferAddress($input);  
12:      if ($1 == NULL) {  
13:       SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, "Unable to get address of direct buffer. Buffer must be allocated direct.");  
14:      }  
15:     }  
16:     %typemap(memberin) unsigned char* BUFFER {  
17:      if ($input) {  
18:       $1 = $input;  
19:      } else {  
20:       $1 = 0;  
21:      }  
22:     }  
23:     %typemap(freearg) unsigned char* BUFFER ""  
24:  //define end  

and add an %apply statement to use it:
1:  %apply unsigned char* BUFFER {unsigned char* pFrameBuffer}  

This will just get the direct buffer address (line 11) and represent it as unsigned char * to the c++ code.

*NOTE: this is based off here http://permafrost.googlecode.com/svn/wiki/HowToUseNIOBuffersInSwig.wiki with some modifications to suite our C++ needs.

Putting it all together

all can be found at github https://github.com/yuvalk/SWIGNIO 
 
Since there's quite a lot of concepts into it, I thought that a working example might be nice (well, isn't it always nice?). To keep with our project multi-platformity, this code utilize Poco even though this post didn't cover it (maybe I'll do that in a separate post if there's demand to it)
Note: this example is also not thread safe and might need some additional work.
But is should demonstrate the concept.

Low level: C++ Implementation


 interface.h

 This is simply a normal c++ .h file.

impl.cpp

Thread is created here to simulate the reactive part. In a real world code, this might be listening to network events, or DB changes, or anything else. For the sake of the example, I just created a simple thread that utilize a random sleep loop and call the callback function with yet another random image, from a pre defined set of imagery).
This is using Poco::Thread and Poco::Random.

SWIG

swig.i

in here we include all definitions which are SWIG only related. mainly you can find our typemap in this.

Java

Interface.java

This class implement the callback class from the .h file!

Program.java

This is the main program.

all can be found at github https://github.com/yuvalk/SWIGNIO

No comments:

Post a Comment

Post a Comment