General
Creating your first Android JNI/NDK Project in Windows Eclipse with Sequoyah
September 13, 2011
0

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: https://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:

**** Build of configuration Default for project TestJNI ****
bash C:android-ndk-r5cndk-build V=1
cygwin warning:
MS-DOS style path detected: C:PERMADI_WORKSPACETestJNI
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 ****

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:

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);
    }
}

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

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

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();
}

Step 7. Load the native library.

package com.permadi.testJNI;

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

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

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:

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)

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:

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

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):

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");
    }
}

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.

#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");
 }

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

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

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

C:

(*env)->NewStringUTF(env, "Hello From C");

C++

env->NewStringUTF("Hello From CPP");

Error

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>

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


Error

first defined here	TestJNI		line 5, external location: C:PERMADI_WORKSPACETestJNIobjlocalarmeabiobjsTestJNIcom_permadi_testJNI_TestJNIActivity.o:C:PERMADI_WORKSPACETestJNIjnicom_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

or

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

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.

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)

Error

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

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 platformsandroid-9arch-armusrinclude 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: https://www.permadi.com/blog/2011/12/running-android-ndk-examples-using-eclipse-and-sequoyah/