JNI JNA SWIG for Android

  • 8
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

advent_calender_17.png

Goodpatch Advent Calendar 17日目の投稿です。
こんにちは。@hi6484です。
Goodpatchで日々Androidアプリを作ってます。

昨日は @ghostnotesさんの [Google I/O 2015 Android Appのソースを読んでみた。」でした。
二日連続のAndroid関連の記事となります。

あるアプリにて、C言語で書かれたモジュールを使う事になったため、
JNI,JNA,SWIGとかを色々と調べてみたので、その事に付いて書いてます。

android studioもNDK対応してくれたので、以前に比べるとかなり楽に実装できるようになってます。
(以前はsoファイルを作るためのAndroid.mkとか環境構築とか大変だった・・・)

JNAは実装できなかったんですが、
そもそもJNIに比べて速度が遅いみたいなんで、やらなくていいんじゃないか。
メンテも2年前くらいから止まってるし(言い訳です)

色々と調べていて、SWIGを見つけました。
SWIGはJNIのダルい部分を省いてくれるので素敵な感じです(中身はJNIです)

JNI-Java Native Interface-

C/C++ファイルの追加

jniフォルダを作成して、C/C++ファイルを追加する。
今回は、初期化、加算、取得する簡単なサンプルを作成。

sample_advent.c
#include "sample_advent.h"

uint32_t dataVaule;

void adventInitValue(void) {
    dataVaule = 0;
}

void adventAddValue(uint32_t value) {
    dataVaule += value;
}

uint32_t adventGetValue(void) {
    return dataVaule;
}

sample_advent.hも同様に作成。

jniファイルの追加

先ほど作成したjniフォルダに作成する。

jni_sample_advent.c
#include <string.h>
#include <jni.h>
#include "sample_advent.h"


void Java_jp_hi6484_android_sample_adventcalendarjni_MainActivity_adventInitValue (JNIEnv* env,
                                                                                   jobject thiz) {
    adventInitValue();
}

void Java_jp_hi6484_android_sample_adventcalendarjni_MainActivity_adventAddValue (JNIEnv* env,
                                                                                    jobject thiz,
                                                                                    uint32_t value) {
    adventAddValue(value);
}

uint32_t Java_jp_hi6484_android_sample_adventcalendarjni_MainActivity_adventGetValue (JNIEnv* env,
                                                                                     jobject thiz) {
    return adventGetValue();
}

build.gradle変更

Project/build.gradle

classpathの下記の箇所を変更
'com.android.tools.build:gradle:1.5.0'
 →'com.android.tools.build:gradle-experimental:0.4.0'
・変更前

Project/build.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

・変更後

Project/build.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle-experimental:0.4.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

app/build.gradle

色々と変えるとこがあるので下記を確認してください。
曲者は「=」を入れて変更する必要がでてきます。

・変更前

app/build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "jp.hi6484.android.sample.adventcalendarjni"
        minSdkVersion 19
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:design:23.1.1'
}

・変更後

app/build.gradle
apply plugin: 'com.android.model.application'

model {
    android {
        compileSdkVersion = 23
        buildToolsVersion = "23.0.2"

        defaultConfig.with {
            applicationId = "jp.hi6484.android.sample.adventcalendarjni"
            minSdkVersion.apiLevel = 19
            targetSdkVersion.apiLevel = 23
            versionCode = 1
            versionName = "1.0"
        }
    }
    android.ndk {
            moduleName = "SampleAdventCalendar"
        /*
         * Other ndk flags configurable here are
         * cppFlags += "-fno-rtti"
         * cppFlags += "-fno-exceptions"
         * ldLibs    = ["android", "log"]
         * stl       = "system"
         */
    }

    android.buildTypes {
        release {
            minifyEnabled = false
            proguardFiles.add(file('proguard-rules.txt'))
        }
    }

    android.productFlavors {
        create("all")
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:design:23.1.1'
}

JavaからNative呼び出し

MainActivity.java

    //libSampleAdventCalendar.soファイルの読み込み.
    static {
        System.loadLibrary("SampleAdventCalendar");
    }

    //nativeメソッドの作成.
    public native void adventInitValue();
    public native void adventAddValue(int value);
    public native int adventGetValue();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        //初期化.
        adventInitValue();
        //加算.
        adventAddValue(100);
        //取得.
        int value = adventGetValue();

    }

libSampleAdventCalendar.soは下記フォルダに作成される
[app/build/intermediates/binaries/debug/all]
libSampleAdventCalendar.soの読み込みに失敗している場合などは上記フォルダにsoファイルが存在するか確認するとよい。

JNIの面倒な点

JAVAとC/C++を繋ぐJNIを書くのが面倒。
パッケージ名を含めた関数名って・・・。
Java_jp_hi6484_android_sample_adventcalendarjni_MainActivity_adventGetValue
1文字間違えてもだめ!!

SWIG-Simplified Wrapper and Interface Generator-

SWIGインストール

ターミナルから下記を叩けばOK

brew install https://raw.github.com/cobrateam/formulae/master/swig.rb

C/C++ファイルの作成

上記、JNI時と同じ。

.iファイルの作成

jniフォルダ以下にsample_advent.iを作成

sample_advent.i
/* File : sample_advent */
%module sample_advent
%include "stdint.i"

%inline %{
    extern void adventInitValue(void);
    extern void adventAddValue(uint32_t value);
    extern uint32_t adventGetValue(void);
    %}

実際、.iファイルってよくわからない・・・
だれかわかる方or参考サイト知っている方教えてください。
個人的には.hファイルをexternを前に付けて作成しております。

JNIファイルの作成

ターミナルで[app/src/main]まで移動して
下記のコマンドを実行

swig -java -package jp.hi6484.android.sample.adventcalendarswig jni/sample_advent.i

実行するとjniフォルダ直下に下記3ファイルが自動生成される
sample_advent_wrap.c
sample_advent.java
sample_adventJNI.java
※javaファイルは後ほど移動。

build.gradle変更

上記、JNI時と同じ。

JavaからNative呼び出し

先ほど自動生成された下記2ファイルをjavaフォルダに移動.
sample_advent.java
sample_adventJNI.java

MainActivity.java

    //libSampleAdventCalendarSWIG.soファイルの読み込み.
    static {
        System.loadLibrary("SampleAdventCalendarSWIG");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        //初期化.
        sample_advent.adventInitValue();
        //加算.
        sample_advent.adventAddValue(100);
        //取得.
        int value = sample_advent.adventGetValue();

    }

sample_advent.javaにnativeメソッドは作成されているので呼び出すだけ

SWIGの面倒な点

  • .iファイルがよくわからない。
  • けれども、jniファイルは自動生成で吐き出されるため楽。

Sample Code

SampleAdventCalendarSWIG
SampleAdventCalendarJNI

参考

明日は @urapico さんの投稿となります!!お楽しみにー。

この投稿は Goodpatch Advent Calendar 201517日目の記事です。