android support c++11

来源:互联网 发布:javascript 刷新 编辑:程序博客网 时间:2024/06/03 10:40

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 MainActivityJava 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 cplusplus11in 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

C++11andANDROID NDK
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"
 
usingnamespace 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);
 
        intsum = 0;
        for_each(numbers.begin(), numbers.end(), [&sum] (inty) {
            sum += y;
        });
 
        env->ReleaseIntArrayElements(arr,body,0);
 
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "sum(arr) = %d", sum);
 
        returnsum;
    }
}

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: algorithmiterator, 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.solibrary 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
packagecom.example;
 
importandroid.support.v7.app.ActionBarActivity;
importandroid.support.v7.app.ActionBar;
importandroid.support.v4.app.Fragment;
importandroid.os.Bundle;
importandroid.view.LayoutInflater;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.TextView;
importandroid.os.Build;
 
publicclass MainActivity extendsActionBarActivity {
    privatenative int sumArray(intarr[]);
     
    static{
        System.loadLibrary("cplusplus11");
    }
 
    @Override
    protectedvoid onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        TextView textView = newTextView(this);
         
        intarr[] = newint[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;
        intsum = sumArray(arr);
 
        textView.setText(String.format("C++11 Sum: %d", sum));
        setContentView(textView);
    }
 
 
    @Override
    publicboolean onCreateOptionsMenu(Menu menu) {
         
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        returntrue;
    }
 
    @Override
    publicboolean 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.
        intid = item.getItemId();
        if(id == R.id.action_settings) {
            returntrue;
        }
        returnsuper.onOptionsItemSelected(item);
    }
 
    /**
     * A placeholder fragment containing a simple view.
     */
    publicstatic class PlaceholderFragment extendsFragment {
 
        publicPlaceholderFragment() {
        }
 
        @Override
        publicView onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);
            returnrootView;
        }
    }
}

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
privatenative int sumArray(intarr[]);

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.solibrary. Once the native method finishes its execution, a TextView will display the sum result (see Figure 2).

C++11andANDROID NDK
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.



0 0
原创粉丝点击