[JAVA] Incorporate existing C / C ++ code into your Android application

About this article

Incorporate existing C / C ++ code into your Android application as a native function using the Android NDK. It's basically the official USER GUIDE (https://developer.android.com/studio/projects/add-native-code).

Assumed situation

--Originally, I was developing C / C ++ applications using CMake. ――The usual development environment is Visual Studio or GCC --It became necessary to port the application to Android --Only the core library part (function) is ported. --Do not port the application part (main function or drawing process). The application part should be created on the Java side ――Although it is "porting", the library part is used for both conventional C / C ++ applications and newly developed Android applications. I don't want to do double maintenance, so I want to share it

Existing project

Consider a very simple project like this: Main.cpp has only the main function. In normal C / C ++ development, it is assumed that this main function mainly works as an "application". This time, it will not be used on the Android side. Submodule has a set of functions. This time, I prepared a function called SubModule_funcDouble that simply doubles the input value. Our goal this time is to call this as a "library" from Java.

Project structure


MyProject
├─CMakeLists.txt
├─Main.cpp
└─SubModule
   ├─CMakeLists.txt
   ├─SubModule.cpp
   └─SubModule.h

Top CMake Lists.txt


cmake_minimum_required(VERSION 3.0)
project("C_application") 

add_executable(Main "Main.cpp")

add_subdirectory(SubModule)
target_include_directories(Main PUBLIC ./SubModule)
target_link_libraries(Main SubModule)

Main.cpp


#include <stdio.h>
#include "SubModule.h"

int main()
{
	SubModule_funcDouble(10);
	return 0;
}

SubModule CMakeLists.txt


cmake_minimum_required(VERSION 3.0)
add_library(SubModule STATIC SubModule.cpp SubModule.h)

SubModule.cpp


#include <stdio.h>
#include "SubModule.h"

int SubModule_funcDouble(int a)
{
	printf("SubModule_funcDouble: %d x 2 = %d\n", a, a * 2);
	return a * 2;
}

SubModule.h


#ifndef _SUB_MODULE_H_
#define _SUB_MODULE_H_

#ifdef __cplusplus
extern "C" {
#endif
#if 0
}
#endif

int SubModule_funcDouble(int a);

#ifdef __cplusplus
}
#endif

#endif	/* _SUB_MODULE_H_ */

Make an Android application

NDK installation

First, open the appropriate Android Studio project and open the menu bar-> Tools-> SDK Manager. From the SDK Tools, select LLDB, CMake and NDK and click OK to install.

01.png

(You can do this later after creating the project)

Generate Android project

Open Android Studio and create a new project. When choosing a template, choose Native C ++.

03.png

Project storage location

I wanted to manage the source code together with C / C ++, so I created a folder called ʻAndroidApp` in the same location as shown below and saved it in that folder.

Project structure


MyProject
├─AndroidApp/     <--Save your android project here
├─CMakeLists.txt
├─Main.cpp
└─SubModule/
   ├─CMakeLists.txt
   ├─SubModule.cpp
   └─SubModule.h

Note: Other methods

If you want to manage your C / C ++ code and your Android code separately, you can save them in a completely different location. Also, if you want to create a new C / C ++ code, there is a CPP folder in the Android project, so I think you should save it there. I think this is probably the expected usage. However, assuming that you want to use "existing" C / C ++ code this time, and that some people are using C / C ++ code as an existing C / C ++ project regardless of Android, the above I made it like this. (Assuming that common library functions are used in Android apps and C / C ++ projects)

Decide how to save various files according to the project system, team members, and operation method.

Note: When embedding C / C ++ in an existing Android project

Right-click on the project (app) you want to import and select Link C ++ Project with Gradle. However, it seems that the template of CMakeLists.txt, which will be introduced later, cannot be created.

02.png

Editing C / C ++ code

05.png

Looking at the generated project, a folder called cpp is created as shown above, and CMakeLists.txt and native-lib.cpp are created under it. By default, a library template is created that provides a function that just returns the string Hello World.

After that, if you change CMakeLists.txt, you should first go to Menu Bar-> Build-> Reflesh Linked C ++ Projects.

Editing CMakeLists

If you want to create a new C / C ++ code, you can edit this CMakeLists as well, but this time we will only import the existing C / C ++ code, so we will try to reduce the trouble of changing as much as possible.

As shown below, only ʻadd_subdirectoryof SubModule of existing C / C ++ code, addition of include path, and link setting are added at the end. The path specification is relative to thisCMakeLists.txt`. If you increase the number of files in SubModule or call another module in the future, you will not need to change this. (Unless you create a header file that messes up the dependencies)

native-lib/CMakeLists.txt


~Abbreviation~

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp)

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

~Abbreviation~

target_link_libraries( # Specifies the target library.
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

#↓ Add
add_subdirectory(../../../../../SubModule SubModule)
target_include_directories(native-lib PUBLIC ../../../../../SubModule)
target_link_libraries(native-lib SubModule)
#↑ Add

Editing native-lib.cpp

There is a function that just returns the string Hello world that was originally provided. You can change the name of this, but use it as it is.

The function I want to call this time is in SubModule, so basically I have it play the role of Wrapper. Have Java and C / C ++ interface conversion according to JNI. Also, this time it's only int, so it's easy, but if an array or object is in an argument or return value, we will ask you to convert it as well.

native-lib.cpp


#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

//↓ Add
#include "SubModule.h"
extern "C" JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_funcDouble(
        JNIEnv *env,
        jobject /* this */,
        jint a
) {
    int ret = SubModule_funcDouble(a);
    return ret;
}
//↑ Add

Call C / C ++ functions from Java code

Finally, let's call a C / C ++ function from Java code. The library (so) file load process (loadLibrary) is already auto-generated.

Declare a C / C ++ native function. It seems that writing at the bottom seems to be a method. The function call can then be implemented as plain Java code.

MainActivity.java


package com.example.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());

        //↓ Add
        int ret = funcDouble(10);
        Log.i("MyApp", "funcDouble: " + ret);
        //↑ Add
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();

    //↓ Add
    public native int funcDouble(int a);
    //↑ Add
}

Note

printf The printf on the C / C ++ side was not output anywhere and seemed to be ignored. However, it is probably environment-dependent, so it seems better to be able to switch between printf and __android_log_print with #ifdef __ANDRDOID__ etc.

fopen In the unlikely event that there is code to read or write files on the C / C ++ side, SIGSEGV will crash if no consideration is given. To support reading and writing files

--Added <uses-permission android: name =" android.permission.WRITE_EXTERNAL_STORAGE "/> to Manifest --After that, grant permissions from the Android app settings. Or, as in this article, voluntarily urge authorization from the app --Set the path specified by fopen to a directory that exists on the Android system. --Don't use fopen ("test.txt", "w"); because you want to output to the current directory. --For example, to output to the top of system memory, use fopen ("/sdcard/test.txt", "w");

OpenMP I could use it normally by adding the following to CMakeLists.txt under native-lib. When I put it in CMakeLists.txt under SubModule, it didn't work. (Build error occurred)

native-CMakeLists under lib.Add the following to txt


#↓ Add
find_package(OpenMP REQUIRED)
if(OpenMP_FOUND)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
endif()
#↑ Add

add_subdirectory(../../../../../SubModule SubModule)
target_include_directories(native-lib PUBLIC ../../../../../SubModule)
target_link_libraries(native-lib SubModule)

Also, there was information on the net that it would crash if it was not called from the UI thread, but it seems that it works fine even if it is called from other than the UI thread. (NDK version is 19.2) Specifically, I called a native function in the ʻonCameraFramecallback of OpenCV'sCameraBridgeViewBase.CvCameraViewListener` and tried running a for statement in it, but it worked without a crash. It was also speeded up.

in conclusion

This is a method that assumes a fairly targeted use case. There may be a better way. If there are any improvements, I would be grateful if you could let me know in the comments.

Recommended Posts

Incorporate existing C / C ++ code into your Android application
Incorporate Elasticsearch into your Rails app
Incorporate Docker into your Ruby on Rails portfolio!