連絡先 Hikwareホーム
Hikware.Tech
自分用の覚え書きをそのまま公開。参考程度にどうぞ。

C++ でのライブラリ作成と再利用

公開日 2017/10/16
最終更新 2018/04/16
  • 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_librarySTATIC を指定すれば作成できる。出力先は 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_librarySHARED を指定すれば作成できる。出力先は 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