http://www.drdobbs.com/cpp/accessing-c11-features-of-the-android-nd/240168385
Accessing C++11 Features of the Android NDK
Once you start working with some of the handy new C++11 features, it is a bit difficult to stop using them simply because you want to work on an Android project. Fortunately, the Android NDK supports C++11 features, although they are not enabled by default. The default Android NDK configuration provides only a minimal C++ runtime support library and doesn't include the necessary flags to activate available C++11 features. In this article, I explain how set up a project to use C++11 features, relying on Eclipse and the latest available Android NDK, version r9d.
Consider a very common scenario in which you want to create a new Android app by reusing existing C++ code. In these cases, a typical solution is to develop the UI in Java and use the Java Native Interface (JNI) to make calls to the C++ code from Java (and the other way around if necessary). If your existing C++ code has been written taking advantage of C++11 features, you certainly would not want to create a new version removing all these C++11 features just to make it fit with the default Android NDK configuration. Fortunately, you can activate the NDK's C++11 features to allow you to work with modern C++ amenities, and you can go on using the auto
keyword, lambda expressions, and other useful C++11 features.
I'll assume that you have basic experience working with Eclipse, Android Development Tools (ADT), and the Android NDK; hence, I won't provide specific instructions for the basic setup of these tools. You will need ADT and NDK installed on your system in order to test the examples. Because ADT and NDK have important changes in each new release, it's important to note that I am using ADT Build v22.6.2-1085508 and NDK Revision 9d. This way, I can focus on the necessary configurations and tweaks related to C++11 and the related features. I'll use a simple example Android app that employs a few C++11 features combined with some use of the Standard Templates Library (STL). Finally, I'll explain additional options and configurations that you might need to consider.
Example Project
Create a new project in Eclipse and select the Android Application Project wizard. I use Cplusplus11
for both Application Name and Project Name, and the package name is com.example
. I leave the default options for the different steps within the wizard, and I select the Blank Activity option with the Create Activity checkbox activated. I use the default activity name, MainActivity
. This way, the wizard will create a Cplusplus11/src/com.example.cplusplus11/MainActivity.java file that defines the MainActivity
Java class as a subclass of ActionBarActivity
. I'll go back to the Java code later.
Now, it is necessary to add native support to the project. Right-click on the recently created project in the Project Explorer and select Android Tools | Add Native Support…. I use libCplusplus11.so
as the library name. Notice that you simply need to enter cplusplus11
in the dialog box and the Eclipse plugin will add the lib
prefix. The Eclipse plugin will add a new jni
folder to the project with the following two elements (see Figure 1): cplusplus11.cpp and Android.mk
Figure 1: The sample Android application project structure in the Project Explorer after the native support has been added.
Right-click on the project in the Project Explorer again, and select Properties | C/C++ Build. Deactivate the Use Default Build Command checkbox and enter ndk-build NDK_DEBUG=1
in the Build Command textbox. This way, the NDK debugger is enabled.
The default contents for the Cplusplus11.cpp
C++ source file is just a line that includes the jni.h
header:
1
|
#include <jni.h>
|
The default contents for the Android.mk
build file includes the following lines:
1
2
3
4
5
6
7
8
|
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := cplusplus11
LOCAL_SRC_FILES := cplusplus11.cpp
include $(BUILD_SHARED_LIBRARY)
|
It is often helpful to use the Android logging features when you call C++ code from Java through the JNI, so I add the following line to pass the name of the specific system library that provides Android logging features and tell the linker to generate a module that links to /system/lib/liblog.so
at load time. Notice that -llog
is decomposed in the -l
option followed by log
; that is, the library name with neither the lib
prefix nor the .so
extension.
1
|
LOCAL_LDLIBS := -llog
|
The following lines show the new contents of the Android.mk
build file:
1
2
3
4
5
6
7
8
9
10
11
|
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL
_MODULE := Cplusplus11
LOCAL_SRC_FILES := Cplusplus11.cpp
# Generate a module that links to /system/lib/liblog.so at load time to enable logging
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
|
Now, it is necessary to add a new build file to the jni
folder named Application.mk
. This file describes which native modules the Android application requires. The following lines show the initial content that I use for this build file:
1
2
3
4
5
6
7
|
NDK_TOOLCHAIN_VERSION := 4.8
APP_ABI := armeabi armeabi-v7a x86 mips
# Enable C++11. However, pthread, rtti and exceptions aren’t enabled
APP_CPPFLAGS += -std=c++11
# Instruct to use the static GNU STL implementation
APP_STL := gnustl_static
LOCAL_C_INCLUDES += ${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.8/include
|
The default compiler is GCC 4.6. The NDK_TOOLCHAIN_VERSION
key specifies the 4.8
value to define the toolchain to use GCC 4.8 as the compiler. The default configuration of the NDK build system generates the machine code for the armeabi
ABI; that is, the ARMv5TE-
based CPU. The APP_ABI
value allows you to select different ABIs.
If you are developing on Windows and you meet the necessary hardware requirements, it is usually a good idea to install the Intel x86 Emulator Accelerator (HAXM), which speeds up Android emulation on Windows. If you are working with this accelerated emulator, the CPU/ABI of your target Android Virtual Device (AVD) will be Intel Atom (x86)
, so you will need to include x86
in the APP_ABI
values. In this example, I've included all the target ABIs in APP_ABI
, but you can specify only the ones you need. Notice that you can achieve the same effect by using APP_ABI := all
. There are many additional options for APP_ABI
, but I wanted to focus on the importance of this setting because when you use different CPU/ABI configurations in your target AVD, the Java code will raise an exception if you try the load the library and you didn't specify the right values for APP_ABI
(since the appropriate library won't be generated).
The APP_CPPFLAGS
key specifies the set of C++ compiler flags passed when building C++ source. The following line enables the supported C++11 features for all your modules. However, you must take into account that pthread, RTTI, and exceptions aren't enabled by default. The C++11 support includes the auto
keyword and lambda expressions. There is also support for std::thread
, but note that there are still some problems with the usage of std::thread
that lead to unexpected app crashes.
1
|
APP_CPPFLAGS += -std=c++11
|
As I explained before, I want to use some STL features, so the following line specifies the static GNU STL implementation. There are other alternatives, such as the STLport library and the experimental libc++ implementation introduced in NDK r9d. You can read more information about libc++ here.
1
|
APP_STL := gnustl_static
|
Finally, it is necessary to specify the path for the GNU STL implementation headers. The following line specifies the path to these headers included in the NDK folders. Because I've specified that the toolchain use GCC 4.8, I make sure the headers path corresponds to version 4.8. Notice that you must have the Android NDK path configured in Eclipse. To do this, select Window | Preferences | Android | NDK, and make sure that the NDK location value is properly configured.
1
|
LOCAL_C_INCLUDES += ${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.8/include
|
Working with C++11 Features and the STL
Now, it is time to add the C++ code that uses C++11 features and STL. The following lines show the contents of the cplusplus11.cpp
file that defines the Java_com_example_MainActivity_sumArray
JNI function. In this case, I haven't created the header file (in order to keep things simple) and I just use a single cpp file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#include <jni.h>
#include <algorithm>
#include <iterator>
#include <vector>
// In order to log
#include <android/log.h>
#define LOG_TAG "CPlusPlus11_sumArray"
using
namespace
std;
extern
"C"
{
JNIEXPORT jint JNICALL
Java_com_example_MainActivity_sumArray
(JNIEnv *env, jobject obj, jintArray arr)
{
auto len = env->GetArrayLength(arr);
__android_log_print(ANDROID_LOG_INFO, LOG_TAG,
"arr length is %d"
, len);
jint *body = env->GetIntArrayElements(arr,0);
vector<
int
> numbers(body, body + len);
int
sum = 0;
for_each(numbers.begin(), numbers.end(), [&sum] (
int
y) {
sum += y;
});
env->ReleaseIntArrayElements(arr,body,0);
__android_log_print(ANDROID_LOG_INFO, LOG_TAG,
"sum(arr) = %d"
, sum);
return
sum;
}
}
|
Java_com_example_MainActivity_sumArray
receives three arguments and returns a jint
. The third argument, named arr
, is a Java int
array (jintArray
). The function returns the sum of the Java int
array received as an argument.
The code uses the auto
keyword and includes three STL headers: algorithm
, iterator
, and vector
. There is some code to retrieve the information from the jintArray
and convert it into a vector<int>
named numbers
. This way, the code can use for_each
to calculate the sum of all the elements in the numbers
vector. Of course, for_each
uses a C++11 lambda expression to perform the sum, this is done just to demonstrate the use of different C++11 features. Note that the "range for" statement isn't supported, so the following code wouldn't compile:
1
2
|
for
(auto n : numbers)
sum += n;
|
There are two calls to the __android_log_print
function to send formatted strings to the log. This way, it is possible to check the information in the LogCat
within Eclipse when you execute the app on your Android virtual device with the C++11_sumArray
tag. Because there are so many configurations involved in making C++11 features and the STL work as expected, it is usually best to send some information to the log to check whether the Java-to-native calls are working correctly.
If you build the project, a libcplusplus11.so
library will be generated for each ABI specified in the Application.mk
file. You will find each generated library in subfolders within the libs folder. For example, if you included x86
, you will find a /libs/x86/libcplusplus11.so
library in your project structure.
Finally, it is necessary to add Java code to load the libcplusplus11.so
library and call the previously defined native method. The following lines show the code for src/com.example/MainActivity.java
that defines the MainActivity
class, loads the library, and calls the native method in OnCreate
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
package
com.example;
import
android.support.v7.app.ActionBarActivity;
import
android.support.v7.app.ActionBar;
import
android.support.v4.app.Fragment;
import
android.os.Bundle;
import
android.view.LayoutInflater;
import
android.view.Menu;
import
android.view.MenuItem;
import
android.view.View;
import
android.view.ViewGroup;
import
android.widget.TextView;
import
android.os.Build;
public
class
MainActivity
extends
ActionBarActivity {
private
native
int
sumArray(
int
arr[]);
static
{
System.loadLibrary(
"cplusplus11"
);
}
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
TextView textView =
new
TextView(
this
);
int
arr[] =
new
int
[
10
];
arr[
0
] =
3
;
arr[
1
] =
7
;
arr[
2
] =
45
;
arr[
3
] =
9
;
arr[
4
] =
32
;
arr[
5
] =
23
;
arr[
6
] =
44
;
arr[
7
] =
12
;
arr[
8
] =
34
;
arr[
9
] =
56
;
int
sum = sumArray(arr);
textView.setText(String.format(
"C++11 Sum: %d"
, sum));
setContentView(textView);
}
@Override
public
boolean
onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return
true
;
}
@Override
public
boolean
onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int
id = item.getItemId();
if
(id == R.id.action_settings) {
return
true
;
}
return
super
.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public
static
class
PlaceholderFragment
extends
Fragment {
public
PlaceholderFragment() {
}
@Override
public
View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false
);
return
rootView;
}
}
}
|
The MainActivity
class includes a line with the definition of the native method in Java. As I explained before, the method returns a Java int
and receives a Java int
array.
1
|
private
native
int
sumArray(
int
arr[]);
|
The call to System.loadLibrary
loads and links the libcplusplus11.so
library. Notice that the specified name is cplusplus11
, but the method will map the name to the full path for loading the library based on the platform and will add the lib
prefix and the .so
extension.
1
2
3
|
static
{</p>
System.loadLibrary(
"cplusplus11"
);</p>
}
|
The OnCreate
method creates an int
array with 10 elements (arr
), and uses it as an argument to call the sumArray
native method. This method will end up calling the Java_com_example_MainActivity_sumArray
method exported in the libcplusplus11.so
library. Once the native method finishes its execution, a TextView
will display the sum result (see Figure 2).
Figure 2: The Android app displaying the results of calling C++ code with C++11 features from Java.
Conclusion
Android Development Tools has made many changes in the latest versions, so many tutorials that were valid for previous versions aren't compatible with the newest releases. These latest releases really simplified working with the Android NDK without forcing you to leave Eclipse. You just need to follow a few simple steps to configure your project to add support for C++11 features and STL. In this case, I took advantage of the auto
keyword, lambda expressions, iterators, vector
, and for_each
. Obviously, there are many other options and possible configurations to enable additional features. You can easily explore them using this example as a baseline.
Gastón Hillar is a senior contributing editor at Dr. Dobb's.