このエントリーは C# Advent Calendar 2017 – Qiita の 8日目のエントリーです。前のエントリーは atsushieno さんの libsoundio-sharpとPInvokeGeneratorについて – ものがたり です。
Orange Pi とは何か?
端的に言えば、Raspberry Pi 互換機です。ラズパイのように40本のGPIOが設定してあったり、マウスやキーボードに繋げるためのUSBコネクタがあったり、モニタに繋げるための HDMI があったりします。
Orangepi http://www.orangepi.org/
互換機であるならば、普通にRaspberry Piを買った/使ったほうがいいだろうという話もありますが(実際に初心者的には無難なラズパイ3を薦めるところですが)、まあ、2台め3台目…n台目となると値段の関係もあって、格安な互換機を買うの面白いところです。
詳しく言えば、ラズパイ3とは大きさが違ったり、メモリ量が違ったり、場合によってはラズパイ3よりも性能が良かったりするので、実際に業務として使う分には慎重な比較が必要なのでしょうが、複数のボードを使ってあれこれ試したいときにはこういう冒険もよいでしょう。
Orange Pi に OS を入れる
Orange Pi One には Allwinner H3(ARM Cortex-A7) という CPU が乗っかっています。ラズパイにも ARM が乗っているし、巷の Android スマートフォンにも ARM が乗っかっています。Intel CPU との違いは主に消費電力が低いということですね。最近では Intel CPU もほどよく低電力で動きますが(Lattepanda が 5V2.2A 程度で動いている)、Orange Pi のような ARM CPU の場合は 5V2A 以下で動いています。まあ、最近のラズパイ3となると 2.4A 程度ないとうまく起動できなかったり、機械学習で GPU をフルに使おうとすると 5V2A の範囲では電力不足で落ちてしまったりするので、どちらの CPU を使うのか?というのは単体では選択の予定が出て来るのですが。
それはさておき、ラズパイには Raspbian という Linux な OS を入れますが、Orange Pi には armbian? https://www.armbian.com/ を入れます。Orange Pi の本家から OS がダウンロードできるハズなのですが、どうもダウンロードスピードが遅いのと、root なパスワードが分からない!!! のでw armbian を使います。Raspbian は Debian ベースですが、Orange Pi には Ubuntu を入れることになります。
root/1234 で入れるので、passwd でパスワードを変更しておきましょう :) そのあとはターミナルで扱えるように
adduser ユーザー名 gpasswd -a ユーザー名 sudo
で、自分で使うユーザーを作っておいて sudo できるようにしておきます。そうすると root を使う必要がなくなりますからね。
Tera Term からログインできるようになると、こんな風になります。
実際に動作しているところは、こんな感じです。「響け!ユーフォニアム 北宇治高校吹奏楽部、波乱の第二章」の文庫本よりも小さいですね。
.NET Core 2.0 をインストールする
さて、やっとこさ本題です。実は Orange Pi One を選択したのは、.NET Core 2.0 を動かしたかったからなのです。実は .NET Core 2.0 の動作環境は、ARMv7 以上が必要でして、更に格安な Raspberry Pi Zero の場合は ARMv6(ARM1176JZF-S)なので、.NET Core の対象外なんですよね(https://ja.wikipedia.org/wiki/Raspberry_Pi)。勿論、Raspberry Pi Zero に普通に Raspbian を入れて gcc や Python を使ってもいいのですが。そこは「.NET Core を動かす」という手段と目的を敢えて取り違えるということで。そうそう、mono は動くはずなので、apt-get install mono-complete すれば .NET 環境は即整います。
Intel な Ubuntu の環境で .NET Core を揃えるには、Prerequisites for .NET Core on Linux | Microsoft Docs を参照に必要なライブラリをインストールしていきます。最後に dotnet-sdk-2.0.0 をインストールしてセルフビルド環境を整えます。
しかし、現時点では ARM の .NET Core は提供されていません。正確に言えば「セルフビルド環境が提供されていないが、実行環境は用意されています」という状態です。妙な状態ではありますが、
- PC のクロス環境で ARM 版をビルドする
- ARM 環境にアセンブリをコピーして実行する
の2段階を踏むと、ラズパイ3やOrange Piなどの ARM 環境でも .NET Core を動かせるようになります。この環境ですが、先ごろ発表された Windows 10 for ARM によって解消されるのではないかなと期待しています。
まず、必要なライブラリをインストールします。
sudo apt-get install libunwind8 libunwind8-dev gettext libicu-dev liblttng-ust-dev libcurl4-openssl-dev libssl-dev uuid-dev unzip
通常ならばこの後に、sudo apt-get install dotnet-sdk-2.0.0 するところですが、ARM 環境なので必要ありません。逆に言えば、Intel な Linux 環境でも実行環境のみ用意するのであれば、dotnet-sdk-2.0.0 自体は必要ないという訳です。
.NET Core 2.0 でビルドする
いわゆる Hello World な C# プログラムを作って .NET Core 2.0 でビルドしてみましょう。PC の環境で .
dotnet new console -n hello
を実行します。
ひな型となる hello プログラムができるので、ARM 用に
dotnet publish -r linux-arm
でビルドをします。-r linux-arm で実行する環境を指定して、publish で全てのアセンブリを生成させます。
通常は、hello.dll しかないところですが、publish にしたので、その他のアセンブリ(*.dll)やライブラリ(*.so)がひとつのフォルダーにコピーされています。
これを winscp などで、Orange Pi 上にコピーします。
実は、必要なアセンブリやライブラリは Orange Pi 側で適切なパスを通してやれば全てをコピーする必要はないのですが、色々面倒なのでごっそりコピーしてしまいます。
.NET Core 2.0 な環境で実行する
Orange Pi 側で、実行ファイル hello に実行権限を付けます。
chmod +x hello
そして実行
./hello
単純に hello world しかでないコンソールなものですが、これで .NET Core 2.0 な環境で動いています。
この現象で何がわかるかというと、
- Orange Pi(あるいはRaspberry Pi)には、.NET なパッケージをインストールしていない。
- PC でクロスコンパイラしたアセンブリを Orange Pi にコピーするだけで、Orange Pi 上で実行ができる。
ところです。まあ、実際のところは、libicu-dev やらなにやらをインストールしているので開発環境がインストールされてはいるのですが(このあたり dev 付きじゃないほうで環境を整えることはできるんでしょうか?)、比較的 Linux な環境を肥大化させないで済むという利点はありますよね。
あと、重たいコンパイル/ビルドな処理を、PC 側でのクロス開発環境で済ませることができます。この場合は、クロス開発環境を Windows の PC 上で行いましたが、Intel な Ubuntu 上とか macOS な環境でも実行は可能なはずです(ここのあたりは試していない)。
Linux & .NET Core で何をする?
基本、.NET Core でできるものは mono でもできるので、Linux や Rasbian 上で .NET Core じゃないと駄目っていう話はないのですが、
- ASP.NET Core + .NET Core の組み合わせで Linux 上で常駐(デーモン)化させる。
- Python + C言語ライブラリや、Node.js + ライブラリの組み合わせで動作しているものを、.NET Core + ライブラリの組み合わせに置き換えられる
というメリットがあります。このあたり Azure や AWS を使ってクラウド化してしまうほうが良いというパターンもありますが、
- 頻繁に該当 API を呼び出す必要ある。
- そもそも、ネットワークが細い(LoRaWANとか)
場合にはローカルのライブラリを呼び出すのが必須条件になります。そういったチープなメモリとチープなCPU(Orange Pi等を見る限り、全然チープではありませんが)な環境で何かを動かすときに有効な手段です。勿論、これ以下で動かす場合には、そもそも Linux などの OS を動かすことなく(mbed OS とかあるけど)別な方法をとりますよね。
ちなみに、ラズパイやOrange Piのような ARM CPU 上では Visual Studio Code が動きません。Intel Linux だと動くので、仮想上の Ubuntu 上では使ってみたりするのですが、いまのところは PC でクロスビルド → Raspberry Pi/Orange Pi に転送、という手段を取っています。そういう意味では、セルフ環境がラズパイ上に無くてもあまり苦になりません。
簡単な *.so を呼び出してみる
というわけで、Linux 上で .NET Core な環境で閉じてしまってはあまり意味がないので、自前の C API ライブラリを作ってそれを呼び出してみましょう。OpenCV を呼び出したり、機械学習っぽいライブラリを呼び出す例でもいいのですが、OpenCV だと OpenCvSharp を使うのが手っ取り早かったり、機械学習や ROS のほうは私自身がまで実験中なので手が出なかった(苦笑)というのもあるので、まあ、自前の C API ライブラリということで。
Orange Pi 上で fibo.c を作ります。まあ、コンソールなので vi で。
g++ でコンパイルします。
g++ -c fibo.c g++ -shared fibo.o -o libfibo.so
fibo.c の中の fibo 関数が C# へエクスポートされる C API になります。Windows 上の DLL だとあれこれと設定しないと駄目なんですが、Linux に限ってしまえば extern “C” を付けるだけで十分です。
C# 側の hello.cs を fibo 関数を呼び出すようにします。
using System; namespace hello { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); for ( int n=1; n<=10; n++ ) { int ans = fibo( n ); Console.WriteLine( $"{n} -> {ans}"); } } [global::System.Runtime.InteropServices.DllImport("fibo", EntryPoint="fibo")] public static extern int fibo( int max ); } }
これを windows 上で dotnet publish -r linux-arm でクロスビルドして Orange Pi 上へ転送。
*.so ファイルは、hello と同じ場所から呼べるように ln -s しておくと便利です。
./hello で実行すれば、C API で作った自前のフィボナッチ関数が呼び出されていることがわかりますね。実際にあれこれやる場合は、構造体の問題とか文字コードの問題とかがあるので色々とややこしいのですが、C# と C API の連携が比較的簡単なことがわかります。つまりは、既存の C API のライブラリがあれば、いちいち C# に直す必要はなくて適当な dlimport を使って連携が可能な訳です。
dllimport のおまけ
C/C++ と C# の相互運用のあたりで swig を含めて改めて分かったのですが、実は dllimport は共有ファイルの名称は自動的に変換してくれるのですね。Interop with Native Libraries | Mono をみると、dllimport(“fibo”) と書いた場合は、
- Windows 上では fibo.dll が呼び出される
- Linux 上では libfibo.so が呼び出される
- macOS 上では libfibo.dylib
な形で呼び変えてくれます。当然、.NET Core もこれに倣っていてファイル名を環境によって変えてくれます。
お次は、matarillo さんの「Null 非許容な参照型」ということで。
さらにおまけ
Xamarin なカレンダーにちょうど dllimport な話があるので、こちらも。
DllImport クロスプラットフォーム nuget パッケージ作成方法(unitypackage もあるよ!) – Qiita
https://qiita.com/TNaruto/items/ac36d87fed07e80ee8be