Using Eclipse for develop Android app mixed Java and C/C++ code

Eclipse is the "official" IDE proposed by Google for Android development. Usually Android app are written using Java code but for some specific task could be necessary to use native C/C++ code to interface with the Java side. Here a tutorial about how to configure Eclipse for a mixed Java and C/C++ code and how to develop it.

Please note, this tutorial will focus for developing under Windows. Basically 99% of the information will be the same for Linux and Mac too. In case of difference will put a short note.

Get the development tools

It' very probably you'll already have all the necessary tools but in case, at first, you need to get the Google Android SDK from here and NDK from here. The SDK package already have inside Eclipse with ADT plugin installed than no additional operations will be required. The only important note is to "install" these two packages in a path not containing spaces. For make and example install the packages in a path like "C:\Program Files\Android\" will cause some problem since there are a lot of command line tools that, in case of spaces, will "interpret" the space as params separator and return error. I suggest something like "C:\Android\" just for avoid problems. SDK contain basic Android tools and NDK contain the tools for allow communication between Java and C/C++ native code called JNI. Instructions about how to configure these tools can be found in the Google site itself.


Create Android app project


We are now ready to start. Eclipse main executable is in the path "SDK\eclipse". Start it and select new project through menu:

File => New => Project...


Then select Android application project and fill all the information required by the wizard. We'll name our example project as "AndroidTest". 



App interface will be very simple. Just two buttons making basic operations for demonstration.




Create java interface to native code

Now the most interesting part. At first we need a java class to use as interface to native C/C++ functions to call. The syntax is very simple as you can see below:

public class NativeCodeInterface
{
  static 
  {
    System.loadLibrary("NativeCode");
  }
  
  public native int get_int(int param);
  public native String get_string();
}

This is a standard java class with only two important points. The function loadLibrary, as the name suggest, will load our native code dynamics library (libNativeCode.so) at program startup. If the library will not be available the app will not start than keep in mind this "problem" in case you note this behaviour (check logcat debug). Available mean the library have to stay in the same folder of Android app or in some system path accessible by all apps. The second point to note is the keyword native used for define functions.

Get C/C++ functions format

Function to export from C/C++ side must have a specific format for allow java to invoke them. The rules of this format can be found in the documentation but, fortunately, we don't need to know it since the java provide a tool able to "convert" the java code to the corresponding C/C++ side. Open a command prompt and go to the project folder subdirectory path:

[My Eclispe workspace path]\AndroidTest\bin\classes

Here execute the following command:


javah -jni com.example.androidtest.NativeCodeInterface

Please note the "com.example.androidtest" is the name of our example app. In your case you'll have to type the name of your app package and the name of the class you chose as interface. If the command executed successfully you'll get a new file in the same current path called (following our name example):


com_example_androidtest_NativeCodeInterface.h

This is the C/C++ header file containing the format of the functions to export for allow java interfacing. The content will be similar to following:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_androidtest_NativeCodeInterface */

#ifndef _Included_com_example_androidtest_NativeCodeInterface
#define _Included_com_example_androidtest_NativeCodeInterface
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_androidtest_NativeCodeInterface
 * Method:    get_int
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_example_androidtest_NativeCodeInterface_get_1int
  (JNIEnv *, jobject, jint);

/*
 * Class:     com_example_androidtest_NativeCodeInterface
 * Method:    get_string
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_androidtest_NativeCodeInterface_get_1string
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

As you can see here will have the same functions defined in the java class just created but in C/C++ format. You have to use these functions as they are without any changes since this is the only way for allow automatic "interface" between java and C/C++. For reach same result is there a more flexible way but also more complex since involve to know how C/C++ code have to be "configured" for allow java to call it. In this tutorial will use only this most easy way.

Add native C/C++ code

Here we'll add some native C/C++ file to be integrated into the whole app project. Before start just a short explanation. Java language used in Android app can communicate to native code using a mechanism called JNI. However with "native code" we mean a dynamics library written in C/C++ language and compiled using a toolchain (provided inside NDK). This dynamics library will export the functions with specific syntax  just generated that allow to be called from Java side. Only dynamic libraries can be used, no static library and no standalone application. If you want to use functions exported by a precompiled static library you have to develop a dynamics library linked to the static library you need to use as "wrapper" between java and the functions you require. Another very important note to know is JNI is able to interface java with native code compiled in C format only. This mean if your code is C++ you need to create a wrapper code in C format to work with JNI and use this wrapper as interface to C++ code. In alternative you can use the extern "C" declaration before each C++ function you want to export to JNI. Now we have the functions prototype we need to add the functions body. In your project folder create a new folder called "jni" and move the header file just generated there. Once done create inside the jni folder a new file called "NativeCode.c" with the following content:

#include "com_example_androidtest_NativeCodeInterface.h"

JNIEXPORT jint JNICALL Java_com_example_androidtest_NativeCodeInterface_get_1int(JNIEnv *jenv, jobject jobj, jint param)
{
 return (param + 1);
}

JNIEXPORT jstring JNICALL Java_com_example_androidtest_NativeCodeInterface_get_1string(JNIEnv *jenv, jobject jobj)
{
 return (*jenv)->NewStringUTF(jenv, "String from native code");
}

The body is very simple. The first function return the same integer param passed from java side increased by 1. The second function return a string converted from C/C++ format to java format. The last step is to create an additional file (always inside the jni folder) called Android.mk with the following content:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := NativeCode
LOCAL_SRC_FILES := NativeCode.c

include $(BUILD_SHARED_LIBRARY)

This file is needed by the toolchain C/C++ compiler for know which file include in the compilation and the compilation result (shared library mean dynamics library). Once done all the work your Eclipse project folder should appear like the following:


Configure Eclipse for compilation

Now we have the native code ready but we need an additional step before. Currently we created an Android project. This mean Eclipse is configured for compile java code only and we need to "instruct" the IDE to compile native C/C++ code also. For reach such result we need the CDT (C/C++ Development Tools) component. Browse the Eclipse main menu and select this item:

Help => Install New Software...

In the "Install" window just showed, into the field "Work with" type the following URL:

http://download.eclipse.org/releases/galileo

Then press enter and wait for data load (please note, if your network have a proxy for Internet access follow this guide to configure Eclipse to go through a proxy). In case you have a different Eclipse version than Galileo you need to use the correct URL. Check Eclipse main site for additional info. In any case, once data load done, open the "Programming Languages" section and select the "Eclipse C/C++ development tools" item as below:


Then click "Next" and proceed with installation. In case your Eclipse will already have installed this component the wizard will advise you and no additional actions will be required. Now we have to convert our java project in a mixed java and C/C++ project. Select the following menu item in the Eclipse:

File => New => Other...

In the window showed open the C/C++ folder and select the "Convert to a C/C++ project" item as below (don't worry about the name, in this case "conversion" mean simply to add C/C++ management part):


Click Next and select the Android project name to convert ("AndroidTest" in our case). In the "Project Type" field select "Makefile project" and "-- Other Toolchain --".


Click Finish. Now Eclipse will ask you if you want to open this perspective now. Click to Yes for have both java and C/C++ compilation profile available.

Configure NDK

Next step is to configure Ecplise for invoke NDK compiler to use for C/C++ code. Into the Eclipse main window right click on the project name ("Project Explorer" subwindow) and in the popup menu select "Properties". Select "C/C++ Build" section. In the "Builder Settings" tab uncheck the "Use default build command" item and insert the full path of your NDK installation plus the name of the build script file "ndk-build.cmd" (in case of Linux and Mac system the name is only "ndk-build") as follow:


Then move to "Behaviour" tab, uncheck "Clean" field and clear all text from "Build" params field as below:


Click on the "Apply" button once done. Now is necessary to set the path where platform header files are located. In the same property window move to "C/C++ General" section and "Paths and Symbols" subsection. Here add the path where found header files to "GNU C" or "GNU C++" depends on your choice. In this example we'll use C files. The header file are inside the NDK installation folder. To be more precise the header files are under the "platform" subfolder but, as you can note, many different choices are available. Basically it depends by the Android target platform you selected for your project. In our example we use the API level 18 than the path to include will be

C:\Android\NDK\platforms\android-18\arch-arm\usr\include

as in image below:


Click to "Apply" and then "OK". This is the basic include, however if Eclipse report the error "Invalid arguments Candidates are:" for some common functions like memcpy, memset, strcpy and so on you have to insert an additional include path as follow:

C:\Android\NDK\toolchains\arm-linux-androideabi-4.8\prebuilt\windows\lib\gcc\arm-linux-androideabi\4.8\include

In case if development under Linux instead of windows subfolder we'll have linux-x86 as only difference.

Develop main activity code

Now the last step. We need to develop the java code into main activity for call our external native code functions. The body of activity should be like the following:

public class MainActivity extends Activity {

 NativeCodeInterface NativeCode = new NativeCodeInterface();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button GetStringBtn = (Button) findViewById(R.id.get_string);

        GetStringBtn.setOnClickListener(new OnClickListener()
        {
          public void onClick(View v)
          {
           String NativeCodeStr = NativeCode.get_string();
           Toast.makeText(getApplicationContext(), NativeCodeStr, Toast.LENGTH_LONG).show();
          }
        });
        
        Button GetIntBtn = (Button) findViewById(R.id.get_int);

        GetIntBtn.setOnClickListener(new OnClickListener()
        {
          public void onClick(View v)
          {
           int NativeCodeInt = NativeCode.get_int(0);
           Toast.makeText(getApplicationContext(), "Passed 0 and returned " + NativeCodeInt, Toast.LENGTH_LONG).show();
          }
        });
    }    
}

As you can see java code call native functions and show result in a toast widget. The first get the string from native code and the second pass 0 as function param and get return of 1. The resource activity_main.xml file containing the app window used in this example is the following:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/get_string"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="81dp"
        android:text="Get string" />

    <Button
        android:id="@+id/get_int"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/get_string"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="26dp"
        android:text="Get int" />

</RelativeLayout>

During compilation into Eclispe main window you should see (in "console" subwindow") something like this:

**** Build of configuration Default for project AndroidTest ****

C:\Android\NDK\ndk-build.cmd

"Compile thumb : NativeCode <= NativeCode.c
SharedLibrary  : libNativeCode.so
Install        : libNativeCode.so => libs/armeabi/libNativeCode.so

**** Build Finished ****

This will inform you about the compilation result of native code part. If no error occurred the generated libNativeCode.so (in our example) will be placed into project subfolder libs\armeabi. All the binary files present in this subfolder path will be automatically included inside the generated .apk file by Eclipse. This mean following this way you'll have the native code library already included in the package and placed in the same path of Android app. The result of all this work will be the following:


Press the two button will show expected result. Hope this tutorial will help you.

Comments

  1. Lots of thanks for the post :) .. It help me much & my project run successfully :D

    ReplyDelete
  2. it s great thanks for post but it gives me one error would u like to help solve it
    07-28 17:46:31.842: E/AndroidRuntime(16430): java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.example.hellojni.NativeCodeInterface.get_string() (tried Java_com_example_hellojni_NativeCodeInterface_get_1string and Java_com_example_hellojni_NativeCodeInterface_get_1string__)

    ReplyDelete
  3. Hi
    Sorry but this error is too much generic for try to know the problem. Case could be many. Are you compiling your native code file in .c format instead of .cpp?

    ReplyDelete
  4. thanks it resolved now. would u like tell me simple example with image
    processing with opencv. i need to increase pdf image clearity . thanks in advance.

    ReplyDelete
  5. and how to generate .h file again with update code

    ReplyDelete
  6. Sorry, still never used opencv so I can not help you. About generate .h the procedure is the same, simply repeat it with update interface calls.

    ReplyDelete
  7. Thank u vary much for reply.
    great blog
    thanks again.

    ReplyDelete
  8. Good post. I've made this project. It works. The only problem when I closed project and reopen it, I've got the errors in NativCode.c like "JNICALL could not be resolved"

    ReplyDelete
  9. Hi, sorry but never faced a problem like your than don't know how to help you, sorry. In any case remember when you close an android app the OS simply switch it in pause state, this mean if you need some initialization step it will not "executed" if the app switch again from pause to active state.

    ReplyDelete
  10. This blog awesome and i learn a lot about programming from here.The best thing about this blog is that you doing from beginning to experts level.

    Love from

    ReplyDelete

Post a Comment

Popular posts from this blog

Access GPIO from Linux user space

Launch an app from Android shell terminal