Skip to content

On the wrapping of Android NDK code with SWIG

October 30, 2012

In this post, I would like to detail a bit more the use of SWIG to wrap the android NDK C or C++ code in Java so that apps can use it.  This simplifies the integration of native code, since the Java Native Interface is a bit more longwinded and trickier to use.

All we need to have at hand is:

  1. The header file(s) containing the functions, data structures (if any) and/or classes (if in C++) that we want to wrap and expose to Java.
  2. An interface file for SWIG to find the relevant headers and wrapped them.

With this SWIG will generate the following

  1. A C or C++ wrapper code that will act as the interface between your C or C++code and Java. This, together with your original C or C++ code, will have to be compiled as a dynamic loadable module, the JNI library.
  2. Java classes representing your C code, which you will use in your app.

So let’s look at an example from this blog, a basic echo effect. In it, we have decided to have a minimal exposure to Java, so here’s the header (echo.c):


#ifdef __cplusplus
extern "C" {
#endif
 void main_process(float delay, float fdb);
 void stop_process();
#ifdef __cplusplus
};
#endif

Two functions, one to start the process and another one to close it. Now we have to define our interface file (echo_interface.i):


%module echo
%{
#include "echo.h"
%}

// Enable the JNI class to load the required native library.
%pragma(java) jniclasscode=%{
 static {
 try {
 java.lang.System.loadLibrary("echo");
 } catch (UnsatisfiedLinkError e) {
 java.lang.System.err.println("native code library failed to load.\n" + e);
 java.lang.System.exit(1);
 }
 }
%}

%include "echo.h"

The lines starting with % are SWIG commands. The first defines the module name, ‘echo’.  Then we tell SWIG to add a C #include statement at the top of the C wrapper code, which is obviously required if you want the JNI library to access your functions.

In the specific case of Java, we need to add some boilerplate code to one of the generated Java classes so that it can load the JNI library we will create. This is done next in lines 7-16. Finally, we tell swig to wrap the code in echo.h using the %include command.

Now it is time to invoke the swig command to do its magic. This is OSX, so the options reflect the location of the JDK headers, which you will need to adjust to match your system.


$ swig -java -package echo -includeall -verbose -outdir src/echo         -I/System/Library/Frameworks/JavaVM.framework/Headers -I./jni     -o jni/java_interface_wrap.c echo_interface.i

The option -package defines the Java package name in the generated files, and -outdir places these files in src/echo. The -o option defines the output C file from the SWIG compilation. If we were using C++, then we would require C++ processing with the option -c++. With a C source file, we have the option of just generating C, or if we want, C++ by adding that option. In any case, the generated source code is pretty much unreadable, so it’s not useful to show it here.

The generated code is then compiled, together will our other C sources into a JNI library. For Android, this means creating an Android.mk file like this in our ./jni directory :


LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := echo
LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_CFLAGS := -O3
LOCAL_CPPFLAGS :=$(LOCAL_CFLAGS)
###

LOCAL_SRC_FILES := echo.c \
opensl_io.c \
java_interface_wrap.c

LOCAL_LDLIBS := -llog -lOpenSLES

include $(BUILD_SHARED_LIBRARY)

The NDK build also asks for a minimal Application.mk in the same directory as above:


APP_STL := gnustl_static
APP_CPPFLAGS += -fexceptions -frtti
APP_ABI := armeabi-v7a

Then we can build the JNI library with the ndk-build command (we assume you have the NDK tools directory in your path):


$ ndk-build  TARGET_PLATFORM=android-9 V=1

Ok, now in your app you can use the wrapped C code in java, like this (see the functions being used in lines 16 and 24):


package com.echo;
import echo.echo;
import android.app.Activity;
import android.os.Bundle;

public class EchoActivity extends Activity {
Thread t;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
t = new Thread() {
public void run() {
setPriority(Thread.MAX_PRIORITY);
echo.main_process(0.5f, 0.7f);
}
};
t.start();
}

public void onDestroy(){
super.onDestroy();
echo.stop_process();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
 }
}
t = null;
}
}

That’s all folks, you’ve been wrapped. If you need to add another function to the interface, say to control a parameter etc., all you need is to add it to your C header file (and of course implement it somewhere). Then run the swig command and re-build. Your new function will be then available to your application.

Advertisements

From → Android

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: