Android系女史 -3ページ目

Android系女史

Android開発など雑多なプログラムの愚痴でもしています。

Android.mkにC++のプログラムを付け加えてはビルド。
それをしていると問題に当たりました。
iostream: No such file or directory
要はプログラムファイル内で
#include <iostream>
としているのですがiostreamがないですよーとそういうこと。

調べてみるとApplication.mkというのをjniフォルダ以下に作成して内部に
APP_STL := gnustl_static
と付け加えてやればいいらしい。

早速Application.mkを作成してビルドし直す。
/パス/obj/local/armeabi/libgnustl_static.a: No such file: Permission denied
とエラーが出た。
エラー内容にあったパスを見てみるとlibgnustl_static.aはあるので
どうやらPermissionがない模様。
ビルド時にandroid-ndkのフォルダからilbgnustl_static.aをobjフォルダにコピーしていて
このコピー前のlibgnustl_static.aのPermissionが000だったのが原因。
元ライブラリは
android-ndkのフォルダ/sources/cxx-stl/gnu-libstdc++/libs/armeabi
にあるのでここにread権を付加したらビルドが通った。
コードの内容について一通り説明をしてもらう。
大体の処理の流れと書き換え箇所を把握。
ついでにこれから先のAndroid化の作業順について話し合い。

1. C++コードをAndoroid.mkでビルドできるように
2. ウィンドウ画面を表示し、JavaからC++のコードを読み出して描画
3. コールバック関数などの実装
4. Webとのアップロード、ダウンロード、カメラなどの実装

の順で。

なのでまずは1.から。

コードは処理の内容ごとにフォルダに分かれているので
フォルダごとにAndroid.mkを書くことにする。

まずはフォルダ内のファイル数が少ないやつでテスト。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := ライブラリ名
LOCAL_SRC_FILES := C++ファイル名列挙

include $(BUILD_SHARED_LIBRARY
)
で上手くいかない。
Android.mkは間違ってないし何故?
と悪戦苦闘したらAndroid.mkはjniフォルダ内にないとビルドできないっぽいということが判明。理由はわからない。

ここのC++ファイルはWindows版でもiOS版でも共有で使っているから
フォルダ名変えるわけにもな。と試行錯誤したところ
jniフォルダ内にAndroid.mkを置いてそこからビルドしたいフォルダのAndroid.mkをincludeしたらできた。
LOCAL_PATH := $(call my-dir)
include ビルドしたいフォルダへのパス/Android.mk
include $(CLEAR_VARS)

LOCAL_MODULE := ライブラリ名

include $(BUILD_SHARED_LIBRARY)
元々フォルダごとにAndroid.mkを置いて起点となるAndroid.mkから呼び出そうと思っていたので
それをjniフォルダ以下のAndroid.mkにすればいいかな。

でも何故フォルダ名がjniじゃないと上手くいかないかが調べてもわからんとです。
納期が色々降ってきてほうっておいてたらまたJavaを忘れた
納期が色々終わって休みが取れたので今のうちに頑張ることにする。

ようやくiPhoneアプリのAndroid化に着手することにする。
……というわけでVisual Studio(Express Ver.)を立ち上げてWindowsで挙動を確認しながら
iOS用のプログラムでコードの確認。
xcodeとか使えないんだよ( ´・ω・`)

iOS用プログラムはAndroid移植を見越したのかどうかは知らないけど
プログラムの設計段階で

・Objective-Cで書く部分は最小限
・できるところは全てC++で書く
・Objective-Cの中のデータはObjective-C内で完結。C++側も然り。
・Objective-C <-> C++の受け渡しはCで行う

とやっているらしいので
Objective-Cで書かれている部分をJavaに
Cで書かれている部分をJNIにすればいい……のかなぁ。

まずはコードを見てみる。
……いまさら、当たり前のことに気づいたんだけど
iOS用のプログラムをAndroid用のプログラムに変えるってことは
Objective-Cプログラムを理解しないと駄目ってことか!

えーと、えと、C#で書かれているプログラムは拡張子が.mmで
int main(int argc, char *argv[]) {}
がmain文で
その中の
UIApplicationMain(argc, argv, @"クラス名", nil);
がアプリケーションの初期化か。

……Android化の着手はいつからだろう。
相方が電子レンジでゆで卵作れるのかと聞いてきたので全力でとめました、そんな日曜。


今回移植するプログラムは既にフォルダ構成がきっちり決められていて
プロジェクトの下にsrc/とかではないのである。

つまり、通常Eclipseのプロジェクトは
- プロジェクトフォルダ
    - bin
    - src
        - パッケージフォルダ
    - Cソースフォルダ
    - lib
とプロジェクトフォルダの下に
ソースコードやらライブラリのフォルダがありますが
今回やりたいのは
- プロジェクトフォルダ
    - bin
- Javaソースフォルダ
- Cソースフォルダ
みたいにプロジェクトフォルダ以外の場所にソースコード等をおきたい。

こういう場合はどうすんだと色々eclipseを触ってみたら

ソースコードの追加の場合は
追加したいフォルダを
右クリック->新規->ファイル
でダイアログの"拡張"を選択
"ファイル・システム内のファイルにリンク"をチェックして
参照したファイルを選ぶと
シンボリックリンクみたいな感じで追加できるっぽい。

フォルダも同じ感じ。
プロジェクトフォルダ下にない
Javaファイルとjni、libフォルダをリンクで追加して実行して動作確認。

……と、出来ることは確認したけどこの方法ってお作法的に正しいのかしら。
相方からスピードアップして!ってせっつかれている。
しかしながら平日は寝に帰るだけ、
休日もゲームしてたりアニメ見たり本読んでたりすると時間がないのである。

Hello Worldができたところで次はJavaからC++の関数を読み込む。
JavaからC/C++の言語で書かれた関数を読み込むためには
JNI(Java Native Interface)というインタフェースを使用するらしい。
このJNIをAndroidプログラムで利用するために必要なのがAndroid NDK

Android NDKをダウンロードするページを見ると
NDKは計算量が必要でメモリをあんまり使わない場所に使うといいよ
それ以外のときはコードが複雑になるからC++が好きって理由だけで使うべきじゃないよ
(意訳)
などと書かれている。
確かにAndroid NDKについて紹介してある他の記事等でも
アプリの高速化と絡めて書いているし。
……今回JavaからC++のプログラムを読み込む理由ってどっちかっていうとC++が好きだからなんだけどいいのかな。

一応、Windows, iOSのプログラムのコアの部分はC++で書かれているから
AndroidもなるべくC++でかける部分はC++のほうが同じソースを利用できて
保守性があがる、開発効率があがるっていう利点があるはず。
問題はJavaからC++のコードを呼ぶときにどれだけ面倒なのかかな。

すでに私のPCにはNDKがインストールされているのでサンプルプログラムを見てみる。

Java:
package com.example.hellojni;

import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;
public class AppFrame extends Activity
{
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);

    TextView tv = new TextView(this);
    // Cの関数呼び出し
    tv.setText( stringFromJNI() );
    setContentView(tv);
  }
  // Cから呼び出す関数名を記述
  public native String stringFromJNI();

  // Cのライブラリ呼び出し
  static {
    System.loadLibrary("hello-jni");
  }
}
hello-jni.c:
#include <string.h>
#include <jni.h>
jstring
Java_com_example_hellojni_AppFrame_stringFromJNI( JNIEnv* env,
jobject thiz )
{
  return (*env)->NewStringUTF(env, "Hello from JNI !");
}
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)
cygwinでhello-jni.cとAndroid.mkがあるディレクトリに移動しndk-buildをするとlibhello-jni.soが出来るので
呼び出すJavaプログラム側では
System.loadLibrary("hello-jni");
と記述してこの出来上がったライブラリを読み込み。
そしてCのプログラム内の呼び出したい関数名を
public native String stringFromJNI();
のように記述し、後は使用するだけ。
呼び出されるCプログラム側では
Javaから呼び出したい関数名がstringFromJniとすると
呼び出されるCのプログラム内では
Java_パッケージ名_クラス名_stringFromJniという名前で記述……

……呼び出される側で名前を変えないといけないの……?
え?何その面倒な仕様。
そういうのって呼び出す側で何とかするもんじゃないの?
ていうか呼び出される側で
呼び出す側のパッケージ名やクラス名を入れて関数名作成しないといけないとなると
このJavaから呼び出される関数は
Javaのパッケージ名やクラス名が変わるごとに作らないといけないってことになるんじゃ?

C++からFortranを呼び出したりFortranからC++を呼び出したりするのと
同じ感じだと予想していたからこの面倒はびっくりだよ。
いや、JavaとC++のつなぎ目の部分だけは面倒そうだけど
それ以外の部分はそのままコードを使える……と信じたい。

一からプロジェクトを作成して
JavaからCのコードを読み込めるテストは成功。
そろそろ実際のコードをAndroid用に変換を始めたい。