[JAVA] Incorporez du code C / C ++ existant dans votre application Android

À propos de cet article

Incorporez le code C / C ++ existant dans votre application Android en tant que fonction native à l'aide du NDK Android. Il s'agit essentiellement du GUIDE DE L'UTILISATEUR officiel (https://developer.android.com/studio/projects/add-native-code).

Situation présumée

Projet existant

Considérez un projet très simple comme celui-ci: Main.cpp n'a que la fonction principale. Dans le développement normal en C / C ++, on suppose que cette fonction principale fonctionne principalement comme une "application". Cette fois, il ne sera pas utilisé du côté Android. Submodule a un ensemble de fonctions. Cette fois, j'ai préparé une fonction appelée SubModule_funcDouble qui double simplement la valeur d'entrée. Notre objectif cette fois est d'appeler cela comme une "bibliothèque" de Java.

Structure du projet


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

Top listes CMake.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;
}

Sous-module 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_ */

Créer une application Android

Installation NDK

Tout d'abord, ouvrez le projet approprié dans Android Studio et ouvrez la barre de menus-> Outils-> SDK Manager. Dans les outils du SDK, sélectionnez «LLDB», «CMake» et «NDK» et cliquez sur OK pour installer.

01.png

(Vous pouvez le faire plus tard après avoir créé le projet)

Générer un projet Android

Ouvrez Android Studio et créez un nouveau projet. Lors du choix d'un modèle, choisissez «Native C ++».

03.png

Emplacement de stockage du projet

Je voulais gérer le code source avec C / C ++, j'ai donc créé un dossier appelé ʻAndroidApp` dans le même emplacement que celui indiqué ci-dessous et l'ai enregistré dans ce dossier.

Structure du projet


MyProject
├─AndroidApp/     <--Enregistrez votre projet Android ici
├─CMakeLists.txt
├─Main.cpp
└─SubModule/
   ├─CMakeLists.txt
   ├─SubModule.cpp
   └─SubModule.h

Remarque: autres méthodes

Si vous souhaitez gérer votre code C / C ++ et votre code Android séparément, vous pouvez les enregistrer dans un emplacement complètement différent. De plus, si vous souhaitez créer un nouveau code C / C ++, il existe un dossier CPP dans le projet Android, je pense donc que vous devriez l'enregistrer ici. Je pense que c'est probablement l'utilisation prévue. Cependant, en supposant que vous souhaitiez utiliser du code C / C ++ «existant» cette fois et que certaines personnes utilisent du code C / C ++ comme projet C / C ++ existant indépendamment d'Android, ce qui précède Je l'ai fait comme ça. (En supposant que les fonctions de bibliothèque courantes sont utilisées dans les applications Android et les projets C / C ++)

Décidez comment enregistrer divers fichiers en fonction de la structure du projet, des membres de l'équipe et de la méthode d'exploitation.

Remarque: lors de l'incorporation de C / C ++ dans un projet Android existant

Cliquez avec le bouton droit sur le projet (application) que vous souhaitez importer et sélectionnez Lier le projet C ++ avec Gradle. Cependant, il semble que le modèle de CMakeLists.txt, qui sera introduit ultérieurement, ne peut pas être créé.

02.png

Modification du code C / C ++

05.png

En regardant le projet généré, un dossier appelé cpp est créé comme indiqué ci-dessus, et CMakeLists.txt et native-lib.cpp sont créés en dessous. Par défaut, un modèle de bibliothèque est créé qui fournit une fonction qui renvoie simplement la chaîne Hello World.

Après cela, si vous modifiez CMakeLists.txt, vous devez d'abord aller dans Menu Bar-> Build-> Reflesh Linked C ++ Projects.

Modification des CMakeLists

Si vous souhaitez créer un nouveau code C / C ++, vous pouvez également modifier ces CMakeLists, mais cette fois, nous n'importerons que le code C / C ++ existant, nous essaierons donc de réduire le plus possible les problèmes de modification.

Comme indiqué ci-dessous, seuls «add_subdirectory» du sous-module du code C / C ++ existant, l'ajout du chemin d'inclusion et la configuration du lien sont ajoutés à la fin. La spécification du chemin est relative à ce CMakeLists.txt. Si vous augmentez le nombre de fichiers dans SubModule ou appelez un autre module à l'avenir, vous n'aurez pas besoin de changer cela. (Sauf si vous créez un fichier d'en-tête qui gâche les dépendances)

native-lib/CMakeLists.txt


~Abréviation~

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)

~Abréviation~

target_link_libraries( # Specifies the target library.
        native-lib

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

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

Modification de native-lib.cpp

Il existe une fonction originale qui renvoie simplement la chaîne Hello world. Vous pouvez changer le nom de ceci, mais utilisez-le tel quel.

La fonction que je veux appeler cette fois est dans SubModule, donc en gros je la fais jouer le rôle de Wrapper. Avoir une conversion d'interface Java et C / C ++ selon JNI. De plus, cette fois, ce n'est que int, donc c'est facile, mais si un tableau ou un objet est dans un argument ou une valeur de retour, nous vous demanderons de le convertir également.

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

//↓ Ajouter
#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;
}
//↑ Ajouter

Appeler des fonctions C / C ++ à partir du code Java

Enfin, appelons une fonction C / C ++ à partir du code Java. Le processus de chargement du fichier de bibliothèque (so) (loadLibrary) a déjà été généré automatiquement.

Déclarez une fonction native C / C ++. Il semble que l'écriture par le bas semble être une méthode. L'appel de fonction peut alors être implémenté en tant que code Java brut.

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

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

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

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

Note

printf Le printf du côté C / C ++ n'était sorti nulle part et semblait ignoré. Cependant, cela dépend probablement de l'environnement, il semble donc préférable de pouvoir basculer entre printf et __android_log_print avec #ifdef __ANDRDOID__ etc.

fopen Dans le cas peu probable où il y aurait du code pour lire ou écrire des fichiers sur le côté C / C ++, SIGSEGV plantera sans aucune considération. Pour prendre en charge la lecture et l'écriture de fichiers

--Ajout de <uses-permission android: name =" android.permission.WRITE_EXTERNAL_STORAGE "/> au manifeste

OpenMP Je pourrais l'utiliser normalement en ajoutant ce qui suit à CMakeLists.txt sous native-lib. Cela n'a pas fonctionné lorsque je l'ai ajouté à CMakeLists.txt sous SubModule. (Une erreur de construction s'est produite)

native-CMakeLists sous lib.Ajoutez ce qui suit à txt


#↓ Ajouter
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()
#↑ Ajouter

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

En outre, il y avait des informations sur le net selon lesquelles il planterait s'il n'était pas appelé à partir du thread d'interface utilisateur, mais il semble que cela fonctionne correctement même s'il est appelé à partir d'un autre thread que le thread d'interface utilisateur. (La version NDK est 19.2) Plus précisément, j'ai appelé une fonction native dans le rappel «onCameraFrame» de «CameraBridgeViewBase.CvCameraViewListener» d'OpenCV et j'ai essayé d'exécuter l'instruction for dans OpenMP, mais cela a fonctionné sans plantage. Cela a également été accéléré.

en conclusion

C'est une méthode qui suppose un cas d'utilisation assez ciblé. Il y a peut-être un meilleur moyen. S'il y a des améliorations, je vous serais reconnaissant de bien vouloir me le faire savoir dans les commentaires.

Recommended Posts

Incorporez du code C / C ++ existant dans votre application Android
Intégrez Elasticsearch dans votre application Rails
Intégrez Docker à votre portefeuille Ruby on Rails!