はじめに 🤗
Xamarin その1 Advent Calendar 2017 の 8日目のエントリーです。
本記事はネイティブライブラリ用 C# バインディングライブラリの nuget パッケージ(nupkg)をクロスプラットフォームへの対応手順になります。また今回は Unity3d 用の unitypackage も作成してみます。
軽量ブロック暗号ライブラリ Naruto/simon-speck-c の C# バインディングライブラリ Naruto/simon-speck-net のクロスプラットフォーム用 nuget パッケージを作成した時の備忘録になります。
バインディングの実装の内容は薄くして、マルチ環境開発環境作りの内容を厚くしています。
nuget パッケージ作成の定石に詳しくないため、作成手順やファイル構成に間違いや改善点があればぜひコメントお願いします!
忙しい人向け🏃
先に生成物を見たい方向け。
下記で nupkg ファイルと unitypackage ファイルを作れますよ。
git clone --recursive git@github.com:Naruto/simon-speck-net.git
cd simon-speck-net
./scripts/create_unitypackage.sh # (Optional) out 以下に unitypackage が作成されるよ。
./scripts/create_nuget.sh # out/net/ 以下に nupkg が作成されるよ。
nuget.org にアップロードしたのが以下になります。
https://www.nuget.org/packages/SimonSpeckNet/
動かしてみる場合は以下のサンプルコードを使ってください。
String plainText = "test text abcdefg.";
byte[] plainByte = System.Text.Encoding.ASCII.GetBytes(plainText);
// Speck ECB mode
using (SymmetricAlgorithm algo = new Speck())
{
algo.BlockSize = 128;
algo.KeySize = 128;
algo.GenerateKey();
using (ICryptoTransform encryptor = algo.CreateEncryptor() , decryptor = algo.CreateDecryptor())
{
byte[] plainEnc = encryptor.TransformFinalBlock(plainByte, 0, plainByte.Length);
byte[] plainDec = decryptor.TransformFinalBlock(plainEnc, 0, plainEnc.Length);
Console.WriteLine(System.Text.Encoding.ASCII.GetString(plainDec));
Console.WriteLine();
}
}
// Speck CTR mode
using (SymmetricAlgorithm algo = new SpeckCTR())
{
algo.BlockSize = 128;
algo.KeySize = 128;
algo.GenerateIV();
algo.GenerateKey();
using (ICryptoTransform encryptor = algo.CreateEncryptor() , decryptor = algo.CreateDecryptor())
{
byte[] plainEnc = encryptor.TransformFinalBlock(plainByte, 0, plainByte.Length);
byte[] plainDec = decryptor.TransformFinalBlock(plainEnc, 0, plainEnc.Length);
Console.WriteLine(System.Text.Encoding.ASCII.GetString(plainDec));
Console.WriteLine();
}
}
バインディング対象📌
今回は simon-speck-cの C# バインディングを作成します。
simon と speck は IoT などの低スペックで限られた電力の環境での動作を考慮されて提案された共通暗号鍵アルゴリズムです。
simon は半導体向けに、speck は CPU 向けにそれぞれ最適化されています。
simon-speck-c は simon と speck の暗号アルゴリズムの実装となります。(現状は speck のみの対応)
実装の特徴としては Intel AVX2 や ARM NEON などの SIMD を使って最適化しています。
作成する nuget パッケージのタイプ 🐔
今回作成する nuget パッケージは対応プラットフォームのネイティブライブラリをすべて包含するオールインワンパッケージとなります。
残念なことに、オールインワンパッケージ構成は nuget パッケージ的には正しくなさそうです。
openssl バインディングライブラリの構成を見てみると各プラットフォームの各アーキテクチャ毎にパッケージ化がわけられていて、おそらくこれがクロスプラットフォームのバインディングのあるべき姿なのだと思われます。
- openssl ( Dependencies を開いてみると… )
オールインワン nuget パッケージがプラットフォーム毎 nuget パッケージを比較して優れている点は「メンテナンスコストが少ないこと」。デメリットとしては「Windows でラッピング対象のネイティブライブラリを分けられない。」や「Linux のディストリビューション毎にパッケージを作成できない」というのがあります。
今回はメンテナンスコストをかけずに作成したい。そして、Windows は x64 対応のみ、Linux を泣く泣くサポート対象外にすることでデメリットを回避しています。
対象プラットフォーム 💻
今回は対応するプラットフォームは下記のとおりです。
- Xamarin.iOS 📱
- MonoAndroid 🤖
- Xamarin.Mac x86/x64 🍎
- Windows x64🍷
Linux 🐧 は distribution ごとのパッケージシステム経由でネイティブライブラリをインストールするのがベストなライブラリ管理方法ですので今回は対応対象外とします。(simon-speck-c の rpm や deb パッケージを作ったら対応検討します。)
Windows は、バインディング対象のネイティブライブラリファイルのうまい切り替え方法が思いつかなかったため x64 のみの対応としています。(AnyCPU の指定ができなくて不便なのでなんとかしたいところではあるのですが・・・)
開発環境 👩💻
今回は macOS 上 で Visual Studio for Mac(Xamarin Studio)を用いて開発します。
今回は macOS 上で jetbrains の Rider を用いて開発します。(※ 記述最中に Rider 2017.2 から 2017.3 へバージョンアップしたため所々バージョンの違うレイアウトが混在します。)
前提知識 🤔
ネイティブライブラリの C# バインディング対応する目的
Xamarin や .NET の機能が充実してきているのでネイティブライブラリ(しかも swift や kotlin じゃなくて C/C++ で記述された!)のバインディングが必要になるケースはほぼほぼないはずなのですが・・・下記のような場合にネイティブライブラリのバインディングが必要になります。
ネイティブ実装しかないライブラリを C# で利用したい
マルチメディア関連のライブラリの C# 実装は数が少なく、利用する際はバインディングが必要になります。
例えば libwebp や opencv、ffmpeg などなど。
C# では利用できない低レイヤーの機能を利用したい
パフォーマンスが必要なので CPU の機能、例えば Intel Quick Sync Video や Intel AVX2, arm NEON などを利用する場合にバインディングが必要になります。考えてみると、上記であげた libwebp や opencv、ffmpeg もパフォーマンス向上のために SIMD を使ってますね。
過去のリソースを修正せずに C# で利用したい
私はこのケースに出会ったことがないのですが、Win32 API で作られた .dll ファイルと .h ファイルがあるが、ソースがない場合などでしょうか。
ロストテクノロジー化したけど、モダンな環境でメンテナンスしていきたい場合に C# バインディングして再利用していくなどあるかもしれません。
simon-speck-net を C# バインディング対応した理由
理由は二つありました。
一つ目は他プログラミング言語対応は暗号アルゴリズムのロジックを一カ所にまとめてメンテナンスコストを増やしたくない考えがありました。今後言語が増えるたびにロジックを書いていたらメンテナンスコストが指数的に増えていきます。
二つ目は simon-speck-c 側で SIMD 命令(intel avx2, arm neon) の実装をしていたためです。SIMD を使うと 2-4倍ぐらいは高速化が可能です。わざわざこのパフォーマンスを捨てて C# で実装する理由がありませんでした。
以上から、C# で暗号アルゴリズムのロジックを実装せずに、C# バインディングにすることに決めました。
C# にはネイティブライブラリの API を C# から実行する仕組み「プラットフォーム呼び出しサービス」が用意されています。
プラットフォーム呼び出しサービス (P/Invoke: Platform Invocation Services)
C# 側でネイティブライブラリ API と同じ返り値型と引数で関数を宣言し、その宣言した関数へ DllImport
属性を付与すると、C# からネイティブライブラリ API を実行できるようになります。
例えば、下記は実行中プロセスのプロセス ID を返す getpid
を宣言したものになります。getpid
は返り値型は pid_t(int の typedef)
で引数がありません。C# 側で宣言すると下記のようになります。
[DllImport ("libc.so")]
private static extern int getpid ();
上記 API を C# 側で実行するとプロセス ID が返ってきます。
DllImport
は C 言語の関数のバインディングしか実装できないので、C++ で実装したネイティブライブラリの API のプラットフォーム呼び出しをする場合は extern "C" { }
で囲むように注意してください。
P/Invoke の詳細な解説は Xamarin のバックエンドの mono のドキュメント ( http://www.mono-project.com/docs/advanced/pinvoke/ ) が参考になります。
各プラットフォームの DllImport でのネイティブライブラリ指定方法
各プラットフォームでバインディングが可能なネイティブライブラリタイプと指定方法は以下の通りです。
プラットフォーム | タイプ | DllImport の指定方法 | ファイル名 | 備考 |
---|---|---|---|---|
Xamarin.iOS 📱 | 静的ライブラリ | DllImport("__Internal") |
libname.a |
fat library の利用可能 |
MonoAndroid 🤖 | 動的ライブラリ | DllImport("name") |
libname.so |
アーキテクチャ毎のバイナリが必要。 |
Xamarin.Mac 🍎 | 動的ライブラリ | DllImport("name") |
libname.dylib |
fat library の利用可能 |
Windows🍷 | 動的ライブラリ | DllImport("name") |
name.dll |
アーキテクチャ毎のバイナリが必要。 |
静的ライブラリは DllImport("__Internal")
を指定し、動的ライブラリはライブラリファイルから拡張子と lib
文字列を抜いたもので指定できます。例えば、 macOS の libname.dylib
や Linux(Android) の libname.so
や Windows のname.dll
は DllImport("name")
で指定できます。
この仕様はバインディングライブラリのソリューションの構成にも影響してきます。
事前準備 🚚
各プラットフォームのネイティブライブラリのバイナリを用意します。
各プラットフォームのビルド環境の構築手順は省略します。具体的なビルド手順は simon-speck-c のビルド手順を記載ます。
iOS 📱
静的ライブラリ(.a)を用意します。
静的ライブラリは armv7a
, arm64
, x86
, x86_64
用のライブラリを含んだ fat ライブラリであることが望ましいです。(iOS11 以降 arm64
バイナリのみのサポートになりましたが、 iOS10 のサポート期間が終わるまでは armv7a
バイナリを入れた方が良いでしょう。また、x86
と x86_64
を入れてるのはシミュレータのためです。)
全アーキテクチャの静的ライブラリを作成後、 libtool
でfat ライブラリファイルを生成します。
libtool -static ../build_iphone64/${LIBNAME} ../build_iphone/${LIBNAME} ../build_iphones/${LIBNAME} ../build_sim/${LIBNAME} ../build_sim64/${LIBNAME} -o ./${LIBNAME}
ビルドスクリプトは下記になります。
https://github.com/Naruto/simon-speck-c/blob/develop/scripts/speck/build_ios.sh
Android 🤖
動的ライブラリ(.so)を用意します。
動的ライブラリは armeabi
, armeabi-v7a
, arm-v8a
, x86
, x86_64
アーキテクチャのそれぞれの動的ライブラリがあることが望ましいです。(といいつつ、もう armeabi
, x86
と x86_64
はもう不要では・・・と思っています。)
cmake v3.7 から正式に Android NDK 用ライブラリのビルドがサポートされました。ビルド時に -DCMAKE_ANDROID_ARCH_ABI=
でアーキテクチャ指定するとそれぞれのアーキテクチャのライブラリをビルド可能です。
Android プロジェクトの libs 以下と同じディレクトリ構造にします。すなわちネイティブライブラリはアーキテクチャ名のディレクトリへ配置します。
android/
|-- arm64-v8a
| `-- libspeck.so
|-- armeabi
| `-- libspeck.so
|-- armeabi-v7a
| `-- libspeck.so
|-- x86
| `-- libspeck.so
`-- x86_64
`-- libspeck.so
ビルドスクリプトは下記になります。
https://github.com/Naruto/simon-speck-c/blob/develop/scripts/speck/build_android.sh
macOS 🍎
動的ライブラリ(.dylib)と bundle ファイル(.bundle)を用意します。動的ライブラリは x86
と x86_64
アーキテクチャの fat ライブラリにしておくとベターです。
全アーキテクチャの動的ライブラリを作成後、 lipo
で fat ライブラリファイルを生成します。
lipo -create ../build_x86_64/${LIBNAME} ../build_i386/${LIBNAME} -o ./${LIBNAME}
bundle ファイルは上記の動的ライブラリファイルから作成します。
speck.bundle/Contents/MacOS
以下に libspeck.dylib
を bundle ファイル名と同じ名前 speck
で配置すれば完成です。
BUNDLENAME=speck
/bin/mkdir -p ${BUNDLENAME}.bundle/Contents/MacOS
/bin/cp /path/to/libpseck.dylib ${BUNDLENAME}.bundle/Contents/MacOS/${BUNDLENAME}
ビルドスクリプトは下記になります。
https://github.com/Naruto/simon-speck-c/blob/develop/scripts/speck/build_mac.sh
windows 🍷
動的ライブラリ(.dll)を用意します。
動的ライブラリは x86_64
アーキテクチャのみを用意します。
https://github.com/Naruto/simon-speck-c/blob/develop/scripts/speck/build_win.bat
ライブラリ配置
できあがったライブラリ群はビルドして配置します。配置構成は下記の通り。
plugins
|-- Android
| `-- libs
| |-- arm64-v8a
| | `-- libspeck.so
| |-- armeabi
| | `-- libspeck.so
| |-- armeabi-v7a
| | `-- libspeck.so
| |-- x86
| | `-- libspeck.so
| `-- x86_64
| `-- libspeck.so
|-- iOS
| `-- libspeck.a
`-- x64
|-- libspeck.dylib
|-- speck.bundle
| `-- Contents
| `-- MacOS
| `-- speck
`-- speck.dll
各プラットフォームのビルド&配置用スクリプトは下記になります。
https://github.com/Naruto/simon-speck-net/blob/develop/scripts/plugins/deploy_android.sh
https://github.com/Naruto/simon-speck-net/blob/develop/scripts/plugins/deploy_ios.sh
https://github.com/Naruto/simon-speck-net/blob/develop/scripts/plugins/deploy_mac.sh
https://github.com/Naruto/simon-speck-net/blob/develop/scripts/plugins/deploy_win.bat
C# プロジェクトの作成 🛠️
C# ライブラリを作成するソリューションを作成します。
ソリューションには動的ライブラリ用の C# ライブラリプロジェクトと、静的ライブラリ用の C# ライブラリプロジェクトを追加します。また、バインディングライブラリのデバッグ用コンソールアプリケーションプロジェクトも追加します。
ソリューションの作成と動的ライブラリ用の C# ライブラリプロジェクトの作成
New Solution ダイヤログで .NET の Class Library を選んでソリューション名を決定します。
プロジェクトの構造は以下の通り。
ソリューション名ディレクトリ SimonSpeckNet
の中にソリューションファイル SimonSpeck.sln
と プロジェクトディレクトリ SimonSpeckNet/SimonSpeckNet
が作成されます。
SimonSpeckNet/
|-- SimonSpeckNet/
| |-- ...
| `-- SimonSpeckNet.cproj
`-- SimonSpeckNet.sln
名前が紛らわしいのでソリューションディレクトリを net
に変更します。(.NET なので、net にしましたが、もしかしたら定石な名称が存在する?)
net/
|-- SimonSpeckNet/
| |-- ...
| `-- SimonSpeckNet.cproj
`-- SimonSpeckNet.sln
ネイティブライブラリの配置とプロジェクトへの追加
事前準備で作成したネイティブライブラリ群をソリューションファイルと同ディレクトリへ配置します。
net/
|-- SimonSpeckNet/
|-- SimonSpeckNet.sln
`-- plugins/ # <- これ
SimonSpeckNet.cproj
ファイルへネイティブライブラリを追加します。プロジェクトファイルを右クリックして “Add Existing Item” をクリックしてください。
追加するライブラリを選びます。ここで追加するライブラリは C# バインディングの動作確認に利用するものです。今回は macOS での開発なので libspeck.dylib
を選択します。
追加した dylib ファイルを右クリック → “Properties” を選んでください。"Editable" -> "Copy to output directory:" を “Copy if newer” を選択してください。
SimonSpeckNet
を右クリック → “Build Selected Projects” をクリックし、プロジェクトのビルド後、 SimonSpeckNet.dll
と同ディレクトリに libspeck.dylib
ファイルがコピーされていれば正しく設定できています。
静的ライブラリ用のプロジェクトの作成
静的ライブラリは動的ライブラリと同じソースファイルを利用して生成します。そのため静的ライブラリのプロジェクトは動的ライブラリのソースを参照するだけのプロジェクトにします。(新規にプロジェクトを追加するほかに、define で分けてビルドする方法があるなら知りたいです。)
ソリューションを右クリックでプロジェクトを追加を選びます。
名前は何でもいいんですが、わかりやすいように SimonSpeckNetStatic
とします。
静的ライブラリ判別用 define 宣言を追加
SimonSpeckNetStatic.csproj
の DefineConstants へ静的ライブラリビルド用の define 宣言 LIB_SATATIC
を追加します。(LIB_STATIC
はコーディング時の DllImport の処理を分けるのに利用します。)
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
...
<DefineConstants>DEBUG;TRACE;LIB_STATIC</DefineConstants>
...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
...
<DefineConstants>TRACE;LIB_STATIC</DefineConstants>
...
</PropertyGroup>
動的ライブラリのソースファイルを参照
SimonSpeckNetStatic
プロジェクトの Class1.cs
を削除し、右クリック → “Add Existing Items” で SimonSpeckNet のソースファイルを指定します。SimonSpeckNetStatic.csproj
で cs ファイルのパスが SimonSpeckNet
ものを指定していれば成功です。
<ItemGroup>
<Compile Include="..\SimonSpeckNet\Class1.cs">
<Link>Class1.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
デバッグ用プロジェクトの作成
C# バインディング API が正しくバインディングされてるか確認するため、C# ライブラリを参照するコマンドラインアプリケーションのプロジェクトを追加します。
ソリューションを右クリックでプロジェクトを追加を選びます。
Console Application を選択し、プロジェクトを名を設定します。
これまた、名前は何でもいいんですがデバッグ用なので SimonSpeckNetTest
などにします。
SimonSpeckNetTest
を右クリック → “Add Reference” を選択し、SimonSpeckNet
を選択してください。
これで、SimonSpeckNetTest
をビルドした際に SimonSpeckNet
のファイルもまとめて配置されていれば成功です。
ディレクトリ構成
最終的なプロジェクトのディレクトリ構成は下記となります。
net/
|-- SimonSpeckNet/
|-- SimonSpeckNetStatic/
|-- SimonSpeckNetTest/
|-- SimonSpeckNet.sln
`-- plugins/
コーディング ✍️
C# のバインディングライブラリを実装します。
DllImport でのライブラリ名の指定方法
DllImport
属性で指定するライブラリ名の宣言を下記のようにします。
DllImport
で指定するライブラリ名は private const string
で宣言した変数を利用します。
変数宣言は #if LIB_STAITC
マクロで静的ライブラリ用と動的ライブラリ用の二つに分けるようにします。(静的ライブラリ判別用 define 宣言を追加で追加したマクロを使用します。)
静的ライブラリは __Internal
を指定、動的ライブラリは speck
と指定します。(指定名はこちらを参照してください。)
#if LIB_STATIC
private const string LibraryName = "__Internal";
#else
private const string LibraryName = "speck";
#endif
[DllImport(LibraryName)]
private static extern IntPtr speck_init(int _type, byte[] key, int key_len);
コーディング方針
DllImport API をそのまま利用するのは利用者に負担がかかるのため、 C# の API スタイルに合わせてラッピングしましょう。
例えば、simon-speck-net は共通鍵ブロック暗号なので、System.Security.Cryptography.SymmetricAlgorithm
の派生クラス Speck
と SpeckCTR
を実装しています。(SpeckCTR
は SymmetricAlgorithm
に CTR モードの指定方法がないので CTR モード専用に派生させたクラスになります。)
C# の基底クラスから派生させることで、利用者の負担が軽減するばかりではなく、既存機能の差し替えが楽になります。
是非、郷には入れば郷に従うの精神で DllImport API をラッピングしましょう。
コーディング時の注意方法
C# のバインディングを記述する上での基本的な原理原則は「Interop with Native Libraries
( http://www.mono-project.com/docs/advanced/pinvoke/ )」が参考になります。
ビルドとデバッグ 🔨
CLI でのビルド手順とデバッグ手順は下記になります。
xbuild /p:TargetFrameworkVersion="v4.5" /p:Configuration=Debug
mono SimonSpeckNetTest/bin/Debug/SimonSpeckNetTest.exe
nuget.exe の用意
nupkg を生成するコマンドを取得し、プロジェクトディレクトリ内に格納します。nuget.org からダウンロードできます。
mkdir -p NuGet
pushd NuGet
curl -Lsfo nuget.exe https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
popd
プロジェクト内に nuget
ディレクトリが作成され、その中に nuget.exe ファイルが格納されます。
net/
|-- SimonSpeckNet/
|-- SimonSpeckNetStatic/
|-- SimonSpeckNet.sln
|-- NuGet/
| `-- nuget.exe
`-- plugins/
nuget.exe を実行するとバージョン番号と usage が表示されます。
mono NuGet/nuget.exe
NuGet Version: 4.4.1.4656
...
nuspec ファイルの作成 📃
テンプレート生成
nuspec は nupkg を生成するためのファイルになります。
nuget の spec コマンドで nuspec ファイルのテンプレートが生成されます。
pushd NuGet
mono nuget.exe spec SimonSpeckNet
popd
メタデータ記述
パッケージ名やバージョン、著者名などなど <metadata>
タグ内のパッケージ情報を記載していきます。
各タグの説明は下記を参照してください。
https://docs.microsoft.com/en-us/nuget/schema/nuspec
SimonSpeckNet の 記述は以下になります。
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>SimonSpeckNet</id>
<version>0.3</version>
<authors>Naruto TAKAHASHI</authors>
<owners>Naruto TAKAHASHI</owners>
<licenseUrl>https://opensource.org/licenses/MIT</licenseUrl>
<projectUrl>https://github.com/Naruto/simon-speck-net</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>SIMON and SPECK are families of lightweight block ciphers; those block ciphers are efficient and provide high performances across a wide range of devices. This package includes classes that allow you to encrypt and decrypt using native libraries that are using SIMD such as AVX2 or NEON.</description>
<releaseNotes>- version up simon-speck-c to v0.6
- change SpeckCTR default padding mode to `Padding.None`
- SpeckCTR allows any length data when setting `Padding.None`. And return values length is same as input data length.
</releaseNotes>
<copyright>Copyright 2017 Naruto TAKAHASHI</copyright>
<tags>simon speck cryptography crypto security encryption lightweight symmetric Xamarin iOS Android macOS</tags>
</metadata>
...
targets ファイル作成
nuspec の続きを書く前に、各プラットフォームの targets ファイルを記載します。targets ファイルはビルドに必要なファイル情報を記載するファイルとなります。今回はネイティブライブラリの情報を記載します。
Common targets ファイル
windows と macOS 両用の targets ファイル NuGet/SimonSpeckNet.targets
の内容は下記になります。windows では .dll
ファイルを指定し、macOS では .dylib
ファイルを指定します。
<None>
タグの Include
属性値には nupkg へパッケージング後のファイルパスを記載します。
windows のネイティブライブラリは runtimes/win-x64/native/speck.dll
以下に格納し、macOS のネイティブライブラリは runtimes/osx-x64/native/libspeck.dylib
以下に格納します。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<!-- Windows -->
<None Include="$(MSBuildThisFileDirectory)..\runtimes\win-x64\native\speck.dll">
<Link>speck.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<!-- macOS -->
<None Include="$(MSBuildThisFileDirectory)..\runtimes\osx-x64\native\libspeck.dylib">
<Link>libspeck.dylib</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
iOS target ファイル
XamarioniOS 用の targets ファイル NuGet/iOS/SimonSpeckNet.targets
の内容は下記になります。
<NativeReference>
タグの Include
属性値には nupkg へパッケージング後のファイルパスを記載します。
iOS のネイティブライブラリは native/ios/libspeck.a
以下に格納します。
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<NativeReference Include="$(MSBuildThisFileDirectory)\..\..\native\ios\libspeck.a">
<Kind>Static</Kind>
<ForceLoad>True</ForceLoad>
</NativeReference>
</ItemGroup>
</Project>
Android target ファイル
MonoAndroid 用の targets ファイル NuGet/Android/SimonSpeckNet.targets
の内容は下記になります。
<AndroidNativeLibrary>
タグの Include
属性値には nupkg へパッケージング後のファイルパスを記載します。
Android のネイティブライブラリは native/android
以下に格納します。各アーキテクチャ毎の設定が必要になるので全部(arm, armeabi-v7a, arm64-v8a, x86, x86_64)追加します。
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AndroidNativeLibrary Include="$(MSBuildThisFileDirectory)\..\..\native\android\armeabi\libspeck.so">
<Link>Libs\armeabi\libspeck.so</Link>
</AndroidNativeLibrary>
<AndroidNativeLibrary Include="$(MSBuildThisFileDirectory)\..\..\native\android\armeabi-v7a\libspeck.so">
<Link>Libs\armeabi-v7a\libspeck.so</Link>
</AndroidNativeLibrary>
<AndroidNativeLibrary Include="$(MSBuildThisFileDirectory)\..\..\native\android\arm64-v8a\libspeck.so">
<Link>Libs\arm64-v8a\libspeck.so</Link>
</AndroidNativeLibrary>
<AndroidNativeLibrary Include="$(MSBuildThisFileDirectory)\..\..\native\android\x86\libspeck.so">
<Link>Libs\x86\libspeck.so</Link>
</AndroidNativeLibrary>
<AndroidNativeLibrary Include="$(MSBuildThisFileDirectory)\..\..\native\android\x86_64\libspeck.so">
<Link>Libs\x86_64\libspeck.so</Link>
</AndroidNativeLibrary>
</ItemGroup>
</Project>
ファイル情報記述
nupkg ファイルに格納するファイルの情報を記述します。
具体的には C# ライブラリとネイティブライブラリと targets ファイルの情報を記載します。
各ファイル毎に <file>
タグに記述します。src
属性値で指定するパスは nupkg 実行時の作業ディレクトリからの相対パス、dst
属性値で指定するパスは nupkg のルートディレクトリからの相対パスとなります。(nupkg コマンドははパッケージ作成はソリューションファイルの格納ディレクトリでの実行を想定して設定します。)
C# ライブラリの配置設定は以下の通り。動的ライブラリ用の C# ライブラリを lib/net
, lib/netcore
, lib/win
, lib/netstandard
以下に配置します。(lib/win
はいらないかもしれません。)
<file src="SimonSpeckNet/bin/Release/SimonSpeckNet.dll" target="lib/net" />
<file src="SimonSpeckNet/bin/Release/SimonSpeckNet.dll" target="lib/netcore" />
<file src="SimonSpeckNet/bin/Release/SimonSpeckNet.dll" target="lib/win" />
<file src="SimonSpeckNet/bin/Release/SimonSpeckNet.dll" target="lib/netstandard" />
common 用の targets ファイルとネイティブライブラリの設定を記載します。
speck.dll
と libspeck.dylib
は nuget/SimonSpeckNet.targets へ記述したパスへ配置します。
<file src="NuGet/SimonSpeckNet.targets" target="build/" />
<file src="plugins/x64/speck.dll" target="runtimes/win-x64/native" />
<file src="plugins/x64/libspeck.dylib" target="runtimes/osx-x64/native" />
iOS 用の targets ファイルとネイティブライブラリの設定を記載します。
iOS の場合は静的ライブラリ用バインディングライブラリが必要なので、
iOS の場合は動的ライブラリ用の C# ライブラリが必要なので SimonSpeckNetStatic
の成果物の SimonSpeckNetStatic.dll
を利用します。
libspeck.a
は nuget/iOS/SimonSpeckNet.targets へ記述したパスへ配置します。
<!-- iOS -->
<file src="NuGet/iOS/SimonSpeckNet.targets" target="build/XamariniOS"/>
<file src="SimonSpeckNetStatic/bin/Release/SimonSpeckNetStatic.dll" target="lib/XamariniOS/SimonSpeckNet.dll"/>
<file src="plugins/iOS/libspeck.a" target="native/ios"/>
Android 用の targets ファイルとネイティブライブラリの設定を記載します。
各アーキテクチャの libspeck.so
は nuget/Android/SimonSpeckNet.targets へ記述したパスへ配置します。
<!-- Android -->
<file src="NuGet/Android/SimonSpeckNet.targets" target="build/MonoAndroid"/>
<file src="SimonSpeckNet/bin/Release/SimonSpeckNet.dll" target="lib/MonoAndroid"/>
<file src="plugins/Android/libs/armeabi/libspeck.so" target="native/android/armeabi" />
<file src="plugins/Android/libs/armeabi-v7a/libspeck.so" target="native/android/armeabi-v7a" />
<file src="plugins/Android/libs/arm64-v8a/libspeck.so" target="native/android/arm64-v8a" />
<file src="plugins/Android/libs/x86/libspeck.so" target="native/android/x86" />
<file src="plugins/Android/libs/x86_64/libspeck.so" target="native/android/x86_64" />
完成 nuspec ファイル
完成した nuspec ファイルは以下になります。
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>SimonSpeckNet</id>
<version>0.3</version>
<authors>Naruto TAKAHASHI</authors>
<owners>Naruto TAKAHASHI</owners>
<licenseUrl>https://opensource.org/licenses/MIT</licenseUrl>
<projectUrl>https://github.com/Naruto/simon-speck-net</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>SIMON and SPECK are families of lightweight block ciphers; those block ciphers are efficient and provide high performances across a wide range of devices. This package includes classes that allow you to encrypt and decrypt using native libraries that are using SIMD such as AVX2 or NEON.</description>
<releaseNotes>- version up simon-speck-c to v0.6
- change SpeckCTR default padding mode to `Padding.None`
- SpeckCTR allows any length data when setting `Padding.None`. And return values length is same as input data length.
</releaseNotes>
<copyright>Copyright 2017 Naruto TAKAHASHI</copyright>
<tags>simon speck cryptography crypto security encryption lightweight symmetric Xamarin iOS Android macOS linux</tags>
</metadata>
<files>
<file src="SimonSpeckNet/bin/Release/SimonSpeckNet.dll" target="lib/net" />
<file src="SimonSpeckNet/bin/Release/SimonSpeckNet.dll" target="lib/netcore" />
<file src="SimonSpeckNet/bin/Release/SimonSpeckNet.dll" target="lib/win" />
<file src="SimonSpeckNet/bin/Release/SimonSpeckNet.dll" target="lib/netstandard" />
<file src="NuGet/SimonSpeckNet.targets" target="build/" />
<file src="plugins/x64/speck.dll" target="runtimes/win-x64/native" />
<file src="plugins/x64/libspeck.dylib" target="runtimes/osx-x64/native" />
<file src="plugins/x64/libspeck.so" target="runtimes/linux-x64/native" />
<!-- Android -->
<file src="NuGet/Android/SimonSpeckNet.targets" target="build/MonoAndroid"/>
<file src="SimonSpeckNet/bin/Release/SimonSpeckNet.dll" target="lib/MonoAndroid"/>
<file src="plugins/Android/libs/armeabi/libspeck.so" target="native/android/armeabi" />
<file src="plugins/Android/libs/armeabi-v7a/libspeck.so" target="native/android/armeabi-v7a" />
<file src="plugins/Android/libs/arm64-v8a/libspeck.so" target="native/android/arm64-v8a" />
<file src="plugins/Android/libs/x86/libspeck.so" target="native/android/x86" />
<file src="plugins/Android/libs/x86_64/libspeck.so" target="native/android/x86_64" />
<!-- iOS -->
<file src="NuGet/iOS/SimonSpeckNet.targets" target="build/XamariniOS"/>
<file src="SimonSpeckNetStatic/bin/Release/SimonSpeckNetStatic.dll" target="lib/XamariniOS/SimonSpeckNet.dll"/>
<file src="plugins/iOS/libspeck.a" target="native/ios"/>
</files>
</package>
ファイル自体は下記のものになります。
https://github.com/Naruto/simon-speck-net/blob/develop/net/NuGet/SimonSpeckNet.nuspec
その他
そのほかの nuspec の仕様(nupkg の依存関係など)や記述方法については、下記ドキュメントが参考になります。
nupkg の作成 🎁
ようやく、nupkg が作成できます。
C# ライブラリをビルドした後に nupkg を作成します。
xbuild /p:TargetFrameworkVersion="v4.5" /p:Configuration=Release
mono NuGet/nuget.exe pack nuget/SimonSpeckNet.nuspec -BasePath $(pwd)
上記に加えてパッケージング前に NUnit でユニットテストを実行する仕組みにしたスクリプトが下記になります。
nupkg 動作確認 🐛
nuget にアップロードする前にパッケージが正しく動作するか確認します。
パッケージの設定
nuget リポジトリはローカルのディレクトリパスを指定できます。nuget パッケージのデバッグする際に活用しましょう。
メニューの Tools → NuGet → Show NuGet Sources を選んで
フィードに file://ディレクトリパス
を追加してください。ここでは $HOME/projects/simon-speck-net/net
以下に nupkgファイル があるものと設定しています。
あとは、プロジェクトファイルの “Manage NuGet Packages” などで探し出して入れてみてください。
検索しなくても nupkg ファイルが見つかるはずです。
後は各それぞれのプラットフォームのプロジェクトを作成し、パッケージを入れてちゃんとビルドできるか確認しましょう。
- Mac
- Windows
- iOS
- Android
nuget.org へのアップロード(Optional)
あとは nuget.org へアップロードするだけですね。
mono ./NuGet/nuget.exe push SimonSpeckNet-XXX.nupkg
アップロードした結果はこんな感じ。 meta データに期待した内容が反映されていますね。
完成です。
お疲れ様でした。
unitypackage の作成(optional)
各プラットフォームのネイティブライブラリと C# ライブラリのソースが揃っていると、Unity3d 用のパッケージ unitypackage ファイルも作成可能になります。
Unity ではネイティブライブラリのことをネイティブプラグインを名称しておりまして、Unity の DllImport 周りの話は「ネイティブプラグイン」で検索すれば出てくるかと思われます。とりあえず Unity オフィシャルのネイティブプラグイン関連の内容は下記になります。
https://docs.unity3d.com/ja/current/Manual/NativePlugins.html
https://docs.unity3d.com/ja/current/Manual/PluginsForDesktop.html
忙しい人向け
simon-speck-net の unitypackage 作成手順をスクリプト化したものは下記になります。
ネイティブライブラリネイティブプラグインの用意
実はライブラリ配置は Unity プロジェクトを意識した配置にしていて、そのまま利用できます。
現行の Unity は下記ネイティブライブラリをサポートしていないため削除します。
- Android
- arm64-v8a
- armeabi
- x86_64
- macOS
- .dylib (代わりに .bundle を利用する)
C# ライブラリの用意
C# ソースをビルドします。
xbuild は使わずに Unity 内部の MonoBleedingEdge/bin/mcs
コンパイラを直接利用します。
UNITY_APP=/Applications/Unity/Unity.app
src_array=`find ${BASEDIR}/net/SimonSpeckNet/Speck -name '*.cs'`
COMPILER=${UNITY_APP}/Contents/MonoBleedingEdge/bin/mcs
${COMPILER} -sdk:2 -r:${UNITY_APP}/Contents/Managed/UnityEngine.dll -r:${UNITY_APP}/Contents/Managed/UnityEditor.dll -target:library -optimize+ -out:SimonSpeckNet_Static.dll -unsafe+ -define:LIBSPECK_STATIC ${src_array}
${COMPILER} -sdk:2 -r:${UNITY_APP}/Contents/Managed/UnityEngine.dll -r:${UNITY_APP}/Contents/Managed/UnityEditor.dll -target:library -optimize+ -out:SimonSpeckNet.dll -unsafe+ ${src_array}
ターゲット SDK は Unity Player で利用している 2.0 を指定。
念のため Unity Player のランタイムライブラリ UnityEngine.dll
と Unity Editor のランタイムライブラリ UnityEditor.dll
を参照します。
静的ライブラリ用と動的ライブラリの二つ作るのを忘れずに。
ライブラリ配置
ネイティブライブラリネイティブプラグインと C# ライブラリが用意できたので配置していきます。
unitypackage パッケージを作成する一時ディレクトリ build_unity
の中に配置していきます。
build_unity/Assets/Plugins/
|-- Android
| |-- SimonSpeckNet.dll
| `-- libs
| |-- armeabi-v7a
| | `-- libspeck.so
| `-- x86
| `-- libspeck.so
|-- iOS
| |-- SimonSpeckNet_Static.dll
| `-- libspeck.a
`-- x64
|-- SimonSpeckNet.dll
|-- speck.bundle
| `-- Contents
| `-- MacOS
| `-- speck
`-- speck.dll
Asset/Plugins/プラットフォーム名
に配置していきます。
Android は Asset/Plugins/Android
。iOS は Asset/Plugins/iOS
。
Windows と macOS は Asset/Plugins/x64
に配置します。
配置をしたら Unity Editor で build_unity
ディレクトリを開いて .meta
データを生成します。
unitypackage パッケージ作成
unitypackage は unity バッチモードにて -exportPackage
オプションで指定して作成します。
${UNITY_APP}/Contents/MacOS/Unity -quit -batchmode -nographics -projectPath $(pwd) -logFile /dev/stdout -exportPackage Assets/Plugins SimonSpeckForUnity.unitypackage
SimonSpeckForUnity.unitypackage
が生成されます。
まとめ
今までの手順を簡潔にまとめると下記になります。
- 各プラットフォームのネイティブライブラリのバイナリを用意する
- ネイティブライブラリを
DllImport
した C# コードを用意する。動的ライブラリ、静的ライブラリどちらにも対応しておくこと。 - 各プラットフォームのネイティブライブラリの参照情報を記述した targets ファイルを作成する
- nuspec で ネイティブライブラリ, C# ライブラリ, targets ファイルを固めて nupkg を作成する
- (Optional) nuget.org にアップロード
- (Optional) unitypackage を作成する
nuget.org へアップロードすると場所やプラットフォームを気にせずに利用可能になるため是非是非アップロードをオススメします。
課題はまだまだ多いですが、以上がネイティブライブラリ用 C# バインディングライブラリの nuget パッケージを作る手順になります。Linux の対応を入れていないのは致命的なので、Linux 対応を進めてまた記事を記載しようと思います。
最後まで読んでいただきありがとうございました。 🙇