LoginSignup
19
20

More than 5 years have passed since last update.

JNIでC++のソースコード・共有オブジェクトをAndroidから利用するチュートリアルをやってみた

Last updated at Posted at 2018-06-07

プロジェクトへの C / C++ コードの追加  |  Android Developers
にあるとおり、JNIのチュートリアルを実行した際のメモです

ドキュメント

このドキュメントにある通りに実行しました

プロジェクトへの C / C++ コードの追加  |  Android Developers

関連ドキュメント、サンプル

ビルド

Android Studioのデフォルトプロジェクト

プロジェクトへの C / C++ コードの追加  |  Android Developers
このドキュメントにある通りに、Include C++ supportにチェックを入れて、新規プロジェクトを作成しました。ビルドに成功すると以下のような画面になりました。
C++で実行された"Hello from C++"という文字列がKotlinに渡されます。

ScreenShots_180607_1625.png

ファイル構成
スクリーンショット 2018-06-07 19.18.18.png

以下、ソースコードについての解説をおこなってみます
    

MainActivity.kt

MainActivity.kt
package org.ymmtyuhei.jnitrial

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Example of a call to a native method
        sample_text.text = stringFromJNI()
    }

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

    companion object {

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

MainActivity が System.loadLibrary() を使用してネイティブ ライブラリnative-libを読み込みます。
これで、ライブラリのネイティブ関数である stringFromJNI() をアプリで使用できるようになります。

public native String stringFromJNI();

このメソッドの宣言内の native キーワードは、この関数が共有ライブラリ内にある(つまり、ネイティブサイドにある)ことを仮想マシンに伝えます。

native-lib.cpp

native-lib.cpp
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring

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

戻り値の型である jstring は、Java Native Interface の仕様で定義されているデータ型です。 実際は文字列ではなく、Java の文字列へのポインタです。

  • jstring の後には関数名を記述します。関数名は、Java の関数名と、その関数を含むファイルへのパスに基づいて作成します。 次のルールに沿って作成します。

    • 先頭に Java_ を追加します。
    • 最上位のソース ディレクトリからの相対ファイルパスを記述します。
    • スラッシュの代わりにアンダースコアを使用します。
    • ファイル拡張子(.java)は省略します。
    • 最後のアンダースコアの後に関数名を追加します。
  • JNIEnv* は VM へのポインタで、jobject は Java サイドから渡される暗黙的な this オブジェクトへのポインタです。

  • 次の行は、VM API (*env) を呼び出し、これに戻り値(Java サイドでこの関数がリクエストした文字列)を渡します。

参考 : サンプル: hello-jni | Android NDK | Android Developers

build.gradle

build.gradle(app)
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "org.ymmtyuhei.jnitrial"
        minSdkVersion 27
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

後述するCMakeLists.txtをexternalNativeBuildと指定します。
これにより、build.gradleがCMakeを実行し、C++コードを.soファイルとしてコンパイルし、APKに含めます。

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }

CMakeLists

CMakeLists.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

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).
             src/main/cpp/native-lib.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

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 )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       native-lib

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

CMake用の設定ファイル。
CMakeがこのファイルを参照しC++ソースコードのビルドを行います。ndk-buildの場合はAndroid.mkやApplication.mkが相当します。

今回、CMakeはnative-lib.cpp をコンパイルして共有オブジェクトライブラリとしてlibnative-lib.soを生成します。
生成されたら、GradleがAPKパッケージに組み込みます。

以下の手順で、APKにlibnative-lib.soが組み込まれている様子を確認できます。
1. Build > Build APK 
2. Build > Analyze APK

スクリーンショット 2018-06-07 18.46.47.png

参考 :
- CMakeビルドスクリプトの作成
- ndk-build | Android NDK | Android Developers

既存のプロジェクトから生成した.soを利用する

先のプロジェクトで生成した、libnative-lib.soを、別のプロジェクトで使ってみます

参考ドキュメント

ネイティブライブラリへのGradleのリンク | 事前構築されたネイティブライブラリを含める

.soファイルをコピー

.soは以下のcmakeのディレクトリに生成されています。

スクリーンショット 2018-06-07 19.57.26.png

それを新しく作成したプロジェクトの以下のディレクトリにコピペしました。そのさいx86...などからコピーします

スクリーンショット 2018-06-07 21.19.28.png

build.gradleに.soを追加

build.gradle
android {
    ...
    sourceSets {
        main {
            jniLibs.srcDirs 'imported-lib/src/', 'more-imported-libs/src/'
        }
    }
}

つまづいたところ

先のMainActivityと同じようにSystem.loadLibrary...などから読み出してみるもランタイムエラー。
AnalyzeAPKでAPKの中身を確認してみると、ライブラリは正常にコピーされている。
Logcatを見てみると以下のようなエラー。
呼び出し元パッケージ名と、.soのビルド時のパッケージト名が違うことで、失敗しているようだ。

    java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String org.ymmtyuhei.importjni.MainActivity.stringFromJNI() (tried Java_org_ymmtyuhei_importjni_MainActivity_stringFromJNI and Java_org_ymmtyuhei_importjni_MainActivity_stringFromJNI__)

以下、大変雑な検証ですが...

  • 元のプロジェクトに戻り、関数名を変更しビルド、再度パッケージを転送したら期待通りに実行できた。
native-lib.cpp
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring

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

ScreenShots_180607_2035.png

  • あるいは元の.soを出力したときのパッケージ名とクラス名に、.soの関数を実行するだけのクラスを作成し(MainActivityという名のただの呼び出し飲みを行うクラス)、 MainActivityから読み出し、実行することができました。

スクリーンショット 2018-06-07 21.14.03.png

MainActivity.java
package org.ymmtyuhei.importjni

...

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val m = MainActivity()
        my_text.text = m.sample_text()
    }
}
MainActivity.java

package org.ymmtyuhei.jnitrial

class MainActivity  {

    fun sample_text() : String{
        return stringFromJNI()
    }

    external fun stringFromJNI(): String

    companion object { 

        init {
            System.loadLibrary("native-lib")
        }
    }
}

用語

Java関連用語

JDK

Java Development Kit とは、Java開発者向けのバイナリ形式でありオラクルによりリリースされる、Java SE、Java EEやJava MEの各プラットフォーム全ての実装である。Javaプラットフォームの導入以来、JDKは最も広く使われているソフトウェア開発キット (SDK) です。

JNI

Java Native Interfaceとは、Javaで記述されたプログラムと、他の言語で書かれた実際のCPUの上で動作するコード(ネイティブコード)とを連携するためのインタフェース仕様である。Java言語からネイティブコードを利用するためのABIと、逆にネイティブコードからJavaバイトコードを動作させるためのバーチャルマシンを利用するためのAPIの2つから成る。

Javaネイティブインタフェース仕様 - 目次

JVM

Java virtual machineとは、Javaバイトコードとして定義された命令セットを実行するスタック型の仮想マシンです。

LLDB

LLDB(Low Level Debugger)とは、ソフトウェアデバッガである

Android関連用語

Android向けにコンパイルし、端末上で実行するまで
ART_view.png

NDK

Native Development Kitとは、Android アプリで C および C++ コードを使用できるようにするツールのセットです。

ADT

Android Development Toolsとは、Eclipse IDE用のカスタムプラグイン。新しいAndroidプロジェクトをすばやく設定し、アプリUIを構築し、アプリをデバッグし、署名付き(または署名なし)アプリパッケージ(APK)を配布用に書き出すことができます。

CMake

Gradle と一緒に使用してネイティブ ライブラリをビルドする外部ビルドツール。ndk-build を使用する予定の場合は、このコンポーネントは不要。

DVM

Dalvic Virtual Machineは、Androidプラットフォームで採用されていたレジスタベースの仮想マシン

ART

Android Run Timeは、ランタイム環境で動作するAndroidオペレーティングシステムを指します。Android 5.0以降で、従来のDalvikからARTに置き換えられ、正式サポートされている。

.dex

Dalvik Executable、Android OSがDVMで実行する、実行ファイルです。
アプリに Java ソースコードがまったく含まれていなくても、ビルドプロセスによって、ネイティブ コンポーネントを実行するための .dex 実行ファイルは生成されます。

コンパイラ関連用語

より一般的なはなし
ソースコード(.cpp) → オブジェクトコード(.so) → 実行ファイル(.dex,.jar)
linking.gif

オブジェクトコード、オブジェクトファイル

オブジェクトコードあるいはオブジェクトファイルとは、コンパイラがソースコードを処理した際に作り出される中間的存在のコードのことです。オブジェクトコードはバイナリで、コンパクトで構文が解析済みのコードです。適切にリンクすることで最終的な実行ファイルやライブラリを作成できます。

実行ファイル

実行ファイル(Executable、Executable file)とは、コンピュータがプログラムとして解釈実行できるファイルである。実行可能ファイル、実行形式ファイル、実行形式、バイナリファイル。拡張子 ".bin" ".exe"など。

ABI

Application Binary Interfaceとは、アプリケーション(ユーザ)プログラムとシステム(OSやライブラリ)との間の、バイナリレベルのインタフェースです。また、アプリケーション相互間や、それらの部品(プラグイン等)とのバイナリインタフェースでもあります。
NDK はこれらの定義に対して .so ファイルをビルドします。

API

APIはソースコードとライブラリ間のインタフェースを定義したもので、同じAPIをサポートしたシステム間では同じソースコードをコンパイルして利用できます。一方、ABIはオブジェクトコードレベルのインタフェースであり、互換ABIをサポートするシステム間では同じ実行ファイルを変更無しで動作させることができます。

NDK関連

本記事には登場しないがndk-buildを使う場合必要な知識

  • 次の 2 つの項目は、ndk-build スクリプトを使用してビルドする、または ndk-gdb スクリプトを使用してデバッグをする場合のみ必要です。

Android.mk

Android.mk 設定ファイルを jni フォルダ内に作成する必要があります。 このファイルにはモジュール、モジュールの名前、コンパイル対象のソースファイル、ビルドフラグ、リンクするライブラリが定義されており、これを ndk-build スクリプトが参照します。

Application.mk

アプリに必要なモジュールのリストとモジュールの説明が含まれています。 たとえば次のような情報があります。
- 特定のプラットフォームのコンパイルに使用する ABI。
- ツールチェーン。
- 組み込む標準ライブラリ(静的 STLport および動的 STLport、または既定のシステム)。

19
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
20