Sep
13
2011

 

This guide shows how to create an sample (Hello World type) Android JNI Application. Using Eclipse and Sequoyah, you can do everything inside the Eclipse IDE (there’s no need to run annoying command lines from Console or DOS prompt).

You should do the previous guide first in order to follow this guide. There is a bit of overlap from the previous guide (which is here: http://www.permadi.com/blog/2011/09/setting-up-android-jni-projects-in-windows-eclipse-and-sequoyah/) so you can skip to step 4 if you have done the previous guide.

Step 1. Create a New Android project.

Select API level of at least 9.  Do not use <space> in the project name or Android NDK may complain later.

Step 2. Add Native Support

This is where Sequoyah do its action.

Right click the project on Eclipse Workspace, then select Android Tools -> Add Native Support.

This dialog should come up:

Just leave the default values as default.  (But if the NDK location is wrong then set it to the path where you installed the Android NDK.)

What this process does is create a Makefile and a stub C++ file for you.  You should see a folder named jni which has been automatically created for you, with an Android.mk and .cpp file.

Step 3. Build Project

The cpp file is empty right now, but we’re are finally ready to test building something.  So do Build Project and hold your breath.

Examine the Eclipse Console window.  You should see something like this:

[c]
**** Build of configuration Default for project TestJNI ****
bash C:\android-ndk-r5c\ndk-build V=1 
cygwin warning:
MS-DOS style path detected: C:\PERMADI_WORKSPACE\TestJNI
Preferred POSIX equivalent is: /cygdrive/c/PERMADI_WORKSPACE/TestJNI
CYGWIN environment variable option "nodosfilewarning" turns off this warning.
Consult the user's guide for more details about POSIX paths:

http://cygwin.com/cygwin-ug-net/using.html#using-pathnames

rm -f /cygdrive/c/PERMADI_WORKSPACE/TestJNI/libs/armeabi/lib*.so /cygdrive/c/PERMADI_WORKSPACE/TestJNI/libs/armeabi-v7a/lib*.so /cygdrive/c/PERMADI_WORKSPACE/TestJNI/libs/x86/lib*.so
rm -f /cygdrive/c/PERMADI_WORKSPACE/TestJNI/libs/armeabi/gdbserver /cygdrive/c/PERMADI_WORKSPACE/TestJNI/libs/armeabi-v7a/gdbserver /cygdrive/c/PERMADI_WORKSPACE/TestJNI/libs/x86/gdbserver
rm -f /cygdrive/c/PERMADI_WORKSPACE/TestJNI/libs/armeabi/gdb.setup /cygdrive/c/PERMADI_WORKSPACE/TestJNI/libs/armeabi-v7a/gdb.setup /cygdrive/c/PERMADI_WORKSPACE/TestJNI/libs/x86/gdb.setup
Install        : libTestJNI.so =&gt; libs/armeabi/libTestJNI.so
mkdir -p /cygdrive/c/PERMADI_WORKSPACE/TestJNI/libs/armeabi
install -p /cygdrive/c/PERMADI_WORKSPACE/TestJNI/obj/local/armeabi/libTestJNI.so /cygdrive/c/PERMADI_WORKSPACE/TestJNI/libs/armeabi/libTestJNI.so
/cygdrive/c/android-ndk-r5c/toolchains/arm-linux-androideabi-4.4.3/prebuilt/windows/bin/arm-linux-androideabi-strip --strip-unneeded C:/PERMADI_WORKSPACE/TestJNI/libs/armeabi/libTestJNI.so
**** Build Finished ****
[/c]

If there’s any error, make sure that of your Android NDK environment is set-up correctly first before continuing.  

Step 5. Open up the Activity class.

Should look like below:

[c]
package com.permadi.testJNI;

import android.app.Activity;
import android.os.Bundle;

public class TestJNIActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}
[/c]

Step 6. Add a function which we will implement natively in C++.

Let’s call it stringFromJNICPP() just because I feel like it.

[c]
package com.permadi.testJNI;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class TestJNIActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    
    public native String  stringFromJNICPP();
}
[/c]

Step 7. Load the native library.

[c]
package com.permadi.testJNI;

import android.app.Activity;
import android.os.Bundle;

public class TestJNIActivity extends Activity {
    //... same as above    
    static {
        System.loadLibrary("TestJNI");
    }    
}
[/c]

How did I came up with that name (TestJNI)? This name is arbitrary but should match the LOCAL_MODULE specified in jni/Android.mk and it should not contain special-characters, not even a .  In this example, mine is named: TestJNI because my Android.mk looks like this:

[c]
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := TestJNI
### Add all source file names to be included in lib separated by a whitespace
LOCAL_SRC_FILES := TestJNI.cpp
include $(BUILD_SHARED_LIBRARY)
[/c]

Step 8. Add a TextView to display the message so that we can see that the native method is actually being called.

Here’s my main.xml, note the addition of TextView with id=myTextField:

[c]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello" android:id="@+id/myTextField"/>
</LinearLayout>
[/c]

Then I set the content of the TextView to the string returned by stringFromJNICPP() function.
My Activity class looks like this now (line 15 is where I call the CPP function and print the return value into the text field):

[c]
package com.permadi.testJNI;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class TestJNIActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        TextView myTextField = (TextView)findViewById(R.id.myTextField);
        myTextField.setText(stringFromJNICPP());   
    }
    
    public native String  stringFromJNICPP();
    
    static {
        System.loadLibrary("TestJNI");
    }    
}
[/c]

Step 9. Add the native C++ code.
Open jni/TestJNI.cpp (this file should have been created for you by Step 2) and add this code.

[c]
#include <string.h>
#include <jni.h>
#include <android/log.h>

 extern "C" {
     JNIEXPORT jstring JNICALL Java_com_permadi_testJNI_TestJNIActivity_stringFromJNICPP(JNIEnv * env, jobject obj);
 };

 JNIEXPORT jstring JNICALL Java_com_permadi_testJNI_TestJNIActivity_stringFromJNICPP(JNIEnv * env, jobject obj)
 {
	 return env->NewStringUTF("Hello From CPP");
 }
[/c]

The code simply returns a string saying Hello From CPP.

Notice how the function is named and how it needs to be exported because Android NDK mostly works with C.
Now, you might think that the function is insanely long, but this isn’t an arbitrary name because it actually follows a convention that tells the JNI to resolve which Java code references it. The convention is something like… the word Java_, followed by <your Java package name> with all the <dot> replaced with <underscores>, followed by <underscore>, then class name, followed by <underscore> and the function name. Confused? Here’s an excerpt from Java documentation at http://download.oracle.com/javase/1,5.0/docs/guide/jni/spec/design.html

Resolving Native Method Names

Dynamic linkers resolve entries based on their names. A native method name is concatenated from the following components:

the prefix Java_
a mangled fully-qualified class name
an underscore (“_”) separator
a mangled method name
for overloaded native methods, two underscores (“__”) followed by the mangled argument signature

Step 10. Create a Test Device.
Before testing, I recommend crating a new AVD that is compatible with API level 9 (if you don’t already have one) since the latest NDK recommend this level. Head over here if you don’t know how to create an AVD: http://developer.android.com/guide/developing/devices/index.html. You can also test on a real device (I personally have ran this example on Nexus One phone).

When running, make sure that you select this AVD via Run->Run Configurations->Target.

Step 11. Build it.
Build the project from Eclipse (from the menu: Project->Build Project). This will build both the java and C/C++ source files as well as installing the resulting C/C++ into the package in one step. If you’re expecting to have to deal with command lines, it’s is a nice surprise that you don’t need to!

Make sure that the Eclipse Console is open (menu: Window->Show View->Console). There should be no error, with much luck. If there are, then head over below to the Common Errors section below.

Step 12. Run it.
Voila, here is it.

Download example project.

Common Errors


Error

[c]
Multiple markers at this line
- Method 'NewStringUTF' could not be resolved
- base operand of '-&gt;' has non-pointer type '_JNIEnv'
[/c]

Solution: You are probably using C convention inside CPP file. In general, the difference is below:

C:

[c]
(*env)->NewStringUTF(env, "Hello From C");
[/c]

C++

[c]
env->NewStringUTF("Hello From CPP");
[/c]


Error

[c]
07-09 07:47:31.103: ERROR/AndroidRuntime(347): FATAL EXCEPTION: main
07-09 07:47:31.103: ERROR/AndroidRuntime(347): java.lang.UnsatisfiedLinkError: stringFromJNI_CPP</pre>
[/c]

Solution:
- Do not use underscore in JNI function names.
- Are you loading the right library name?


Error

[c]
first defined here	TestJNI		line 5, external location: C:\PERMADI_WORKSPACE\TestJNI\obj\local\armeabi\objs\TestJNI\com_permadi_testJNI_TestJNIActivity.o:C:\PERMADI_WORKSPACE\TestJNI\jni\com_permadi_testJNI_TestJNIActivity.c	C/C++ Problem
make: *** [/cygdrive/c/PERMADI_WORKSPACE/TestJNI/obj/local/armeabi/libTestJNI.so] Error 1	TestJNI		 	C/C++ Problem
multiple definition of `Java_com_permadi_testJNI_TestJNIActivity_stringFromJNI'	com_permadi_testJNI_TestJNIActivity.c	/TestJNI/jni	line 5	C/C++ Problem
[/c]

or

[c]
C:/PERMADI_WORKSPACE/TestJNI/obj/local/armeabi/objs/TestJNI/com_permadi_testJNI_TestJNIActivity.o: In function `Java_com_permadi_testJNI_TestJNIActivity_stringFromJNI':
C:/PERMADI_WORKSPACE/TestJNI/jni/com_permadi_testJNI_TestJNIActivity.c:5: multiple definition of `Java_com_permadi_testJNI_TestJNIActivity_stringFromJNI'
C:/PERMADI_WORKSPACE/TestJNI/obj/local/armeabi/objs/TestJNI/com_permadi_testJNI_TestJNIActivity.o:C:/PERMADI_WORKSPACE/TestJNI/jni/com_permadi_testJNI_TestJNIActivity.c:5: first defined here
collect2: ld returned 1 exit status
make: *** [/cygdrive/c/PERMADI_WORKSPACE/TestJNI/obj/local/armeabi/libTestJNI.so] Error 1
[/c]

Possible solution:
- Make sure there’s no duplicate function names.
- Check the Android.mk to ensure that no source file is being compiled multiple times. For instance, at some point my Makefile was messed up like this, with one of the source file (com_permadi_testJNI_TestJNIActivity.c) being added twice, which caused the error.

[c]
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := TestJNI
### Add all source file names to be included in lib separated by a whitespace
LOCAL_SRC_FILES := TestJNI.cpp com_permadi_testJNI_TestJNIActivity.c com_permadi_testJNI_TestJNIActivity.c
include $(BUILD_SHARED_LIBRARY)
[/c]


Error

[c]
Description	Resource	Path	Location	Type
Method 'NewStringUTF' could not be resolved	com_permadi_testJNI_TestJNIActivity.c	/TestJNIC/jni	line 6	Semantic Error
Type 'JNIEnv' could not be resolved	com_permadi_testJNI_TestJNIActivity.c	/TestJNIC/jni	line 4	Semantic Error
Type 'jobject' could not be resolved	com_permadi_testJNI_TestJNIActivity.c	/TestJNIC/jni	line 4	Semantic Error
Type 'jstring' could not be resolved	com_permadi_testJNI_TestJNIActivity.c	/TestJNIC/jni	line 4	Semantic Error
[/c]

Solution:
This is actually a very strange error because I have seen it suddenly creeping up on projects that had compiled fine before. There must be a bug somewhere (in Eclipse, NDK, SDK?) which caused this to happen in some situations and intermittently — and I don’t know what triggers it. The remedy (hack) is to add the android-ndk include path into the Project Property->C/C++ paths.

Open the Project Properties. Expand the C/C++ General section, then select the Paths and Symbols sub-section. In the Includes tab, select GNU C, enter the \platforms\android-9\arch-arm\usr\include path. See below (click image to zoom).

Do the same to the GNU C++ section. Click Apply. Agree when asked to rebuild index. Then rebuild the project. The error is magically gone. What’s odd is that once the build is successful, you can remove the paths that you have just added, make code changes that triggers a recompile, and the error usually won’t come back.

Where to go from here

Examine the NDK sample codes and see how things work. There are Open GL samples if you’re into game programming. See this guide on how to compile them within Eclipse: http://www.permadi.com/blog/2011/12/running-android-ndk-examples-using-eclipse-and-sequoyah/

5 Responses to “Creating your first Android JNI/NDK Project in Windows Eclipse with Sequoyah”

  1. [...] Creating your first Android JNI/NDK Project in Eclipse with Sequoyah [...]

  2. [...] that you need the environment described here to use this example: Bookmark on Delicious Digg this post Recommend on Facebook Share with [...]

  3. Sorry, I meant to ask, is there a particular reason for the “Select API level of at least 9″?

    Does something bad happen is leve 8 is used?

  4. According to the Google Doc (http://developer.android.com/sdk/ndk/overview.html), “Applications that use native activities must be run on Android 2.3 or later.” This is why on the example, we use api level 9.

  5. Nice tutorial, I had the same problem with jobject/JNIEXPORT/etc. and I’m glad I found the answer here.

Leave a Reply

*