- Android Studio で C++ のライブラリを作成し、他のプロジェクトから再利用したい。
基本方針
公式の Hello-libs サンプル が知りたい事を全てやってくれている。Android Studio のトップメニューからインポートできるので試してみるよろし。
要点はだいたい以下のような感じ。
ライブラリには、STATIC ライブラリ (*.a) と、SHARED ライブラリ (*.so) の2種類ある。STATIC ライブラリは利用側のビルド時にリンクされる。SHARED ライブラリは dll みたいなもんで、ライブラリのビルド時にすでにリンクされている。
ライブラリ提供側は、利用側が参照しやすい場所にライブラリを出力させる。出力先の指定は CMakeLists.txt で行える。
ライブラリのヘッダファイルも利用側から参照しやすい場所に置いておく必要があるが、CMakeLists.txt で自動でコピーさせる事も可能。
ライブラリ利用側は、CMakeLists.txt でライブラリファイルやインクルードファイルの位置さえ指定すれば利用はできる。
さらにライブラリモジュールをアプリ側のプロジェクトに組み込んでしまえば、ちゃんと連動してビルドしてくれる。
STATIC ライブラリの作り方
ライブラリ側の CMakeLists.txt の
add_library
でSTATIC
を指定すれば作成できる。出力先はARCHIVE_OUTPUT_DIRECTORY
プロパティで指定する。NDK の仕組み上、ライブラリファイルは対応するプロセッサ別に出力しないといけないので、出力先フォルダはシステム変数を使って生成する。
# ライブラリのソースファイル add_library( ライブラリ名 STATIC 以下ソースファイル ) # ライブラリプロジェクトのルート # ここではこのファイルから2階層上を指定 set( LIB_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) # ライブラリの出力先 # 対応するプロセッサごとにフォルダを分けて出力 set( OUTPUT_DIR ${LIB_ROOT}/lib/${ANDROID_ABI} ) # 出力先を指定(※SHAREDとはプロパティ名が違うので注意!) set_target_properties( ライブラリ名 PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${OUTPUT_DIR} )
CMakeLists.txt 内で最終的に誰もリンクしない STATIC ライブラリはそもそもビルドされない ので、常にビルドさせるため build.gradle に
targets
の設定を追加する。android { defaultConfig { externalNativeBuild { cmake { targets 'ライブラリ名' } } } }
SHARED ライブラリの作り方
CMakeLists.txt の
add_library
でSHARED
を指定すれば作成できる。出力先はLIBRARY_OUTPUT_DIRECTORY
(※STATIC と微妙に違う!) プロパティで指定する。また、SHARED ライブラリはこの時点でリンクするので、
target_link_libraries
に依存ライブラリを記述しないとリンクエラー。# ライブラリのソースファイル add_library( ライブラリ名 SHARED 以下ソースファイル ) # ライブラリプロジェクトのルート # ここではこのファイルから2階層上を指定 set( LIB_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) # ライブラリの出力先 # 対応するプロセッサごとにフォルダを分けて出力 set( OUTPUT_DIR ${LIB_ROOT}/lib/${ANDROID_ABI} ) # 出力先を指定(※STATICとはプロパティ名が違うので注意!) set_target_properties( ライブラリ名 PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR} ) # SHARED はこの時点でリンクするので、依存するライブラリがあるなら指示 target_link_libraries( ライブラリ名 android log )
STATIC ライブラリでは build.gradle に
targets
の設定が必要だったが、SHARED ライブラリでは必要ない(書いても問題はない)。
別プロジェクトからの利用方法
ライブラリモジュールをあらかじめアプリプロジェクトに組み入れておくと、連動でビルドしてくれたり、デバッガでライブラリソース内まで追えたり、いろいろ便利になる。手順は通常のJavaでの開発時と同じなので、詳しくは 別記事 にて。
C / C++ で作ったライブラリを、利用側の C / C++ ソースで使う場合は、利用側の CMakeLists.txt でライブラリやインクルードファイルの参照先の指示が必要。
# ライブラリプロジェクトのルート # 適宜自分の環境に合わせてください set( LIB_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../../ライブラリ名 ) # ライブラリが外部参照である事を指定 # SHARED の場合は STATIC の部分を変更 add_library( ライブラリ名 STATIC IMPORTED ) # ライブラリファイルの位置を指定 # SHARED の場合は拡張子を .so に変更 # ライブラリファイルには勝手に lib とプレフィックスが付くので注意 set_target_properties( ライブラリ名 PROPERTIES IMPORTED_LOCATION ${LIB_ROOT}/lib/${ANDROID_ABI}/libライブラリ名.a ) # アプリのソース add_library( appmain SHARED 以下ソースファイル ) # ライブラリのインクルードファイルの参照先を指定 # これで #include <ファイル名> でのインクルードが可能になる # 適宜自分の環境に合わせてください target_include_directories( appmain PRIVATE ${LIB_ROOT}/include ) # アプリにライブラリをリンク # STATIC の場合は、ライブラリが依存する他のライブラリもここに全て記述 # SHARED の場合は、アプリソースが依存するライブラリだけ書けばOK target_link_libraries( appmain ライブラリ名 android log )
SHARED ライブラリの場合は、ライブラリの *.so ファイルをアプリの apk 含めるように build.gradle に指示 してやらないといけない。これを忘れてもビルドエラーにはならないが、アプリを起動するなり落ちる。
android { sourceSets { main { // 適宜自分の環境にあわせてください。複数指定もできるらしい jniLibs.srcDirs = ['../../../ライブラリ名/lib'] } } }
2018.04現在、Android Studio 3.x では NDK のビルドシステムに問題があるようで、ライブラリモジュールをアプリと連動してビルドさせようとした場合、依存関係を無視してライブラリ側より先にアプリ側がビルドされてしまう。 やっつけの回避方法については 別記事 にて。
で、STATIC と SHARED どっちがいいん?
※まだあまり使いこなしてる状況じゃないので、主観。
NDK アプリが複数の SHARED ライブラリで構成されていて、それぞれからライブラリの機能を使いたいのであればライブラリも SHARED ライブラリにすべきだが(そうしないと各ライブラリに同じコードが含まれる事になる)、アプリが1つの SHARED ライブラリだけで完結するのであれば、STATIC の方が扱いが楽とは思う。
1つの SHARED ライブラリにいろんな機能をごった煮で詰め込んだ場合、あるアプリではごく一部の機能しか使わなかったとしても全機能が apk に含まれてしまうと思われる。そもそもそんな SHARED ライブラリ作るのが間違いなんだけど。
その点、STATIC だとリンク時に使ってない機能は省いてくれるのではないかと期待はできるけど、実際に確認した訳ではない。
てことで、今のところはとにかく扱いが楽なので STATIC で。最終的にライブラリが安定したら SHARED にするんじゃないかと。
その他おぼえがき
ライブラリの出力先を指定しなかった場合のデフォルトの出力先
{ライブラリモジュール}/build/intermediates/cmake/
ライブラリのヘッダファイルをビルド時に書き出したい
今のところ必要じゃないのでちゃんと調べてないけど、ライブラリを公開したい場合など、ライブラリソース群からヘッダファイルだけ別フォルダに書き出したい場合は、CMakeLists.txt にスクリプトを書けば自動でやってくれるぽい。Hello-libs サンプル でもやってくれてるので以下あたりを参照。
gen-libs/src/main/cpp/gmath/CMakeLists.txt
gen-libs/src/main/cpp/gperf/CMakeLists.txt