窓を作っては壊していた人のブログ

提督の窓や成績の窓を作ってました.今では適当に好き勝手に開発をしてます.

NuGet でプラットフォームごとにネイティブライブラリを同梱してそれを使うラッパーのパッケージを作る方法

非常に自分で挑戦して悩んだことだったので,備忘録として残しておくことにします.

現在大学院で音声研究をしていて,音声信号処理系のライブラリを使うことが多いのですが,その多くが C/C++ で書かれていたりします. 実験レベルではシェルからパイプで繋いだり,スクリプトを書くというような流れていいと思いますが,実際に実験や研究から得られた知見を元にものを作ろうとした場合,C/C++ でなるべく書きたくありません,難しいですし. そのため C/C++ のライブラリを C# でラップして使うことが多く,その中で NuGet のパッケージにすると更に使いまわせることに気づき,このタイトルにあるようなことをやってみました.

はじめにおことわりしておきますが,Githubリポジトリを見てね案件が多いです,手を抜いてしまい申しわけないです.

NuGet のパッケージを作るには

ひとまず今回作ったライブラリをご紹介します.

github.com

World という音声分析合成ツールをラップしたものになります.

ものを見て理解する,という方は DotnetWorldlibrary/nugetディレクトリ内を見ていただければ理解できるかと思います.

さて,タイトルを満たす NuGet のパッケージを作る上で必要になるポイントは以下の4点です.

  • プラットフォーム毎のマネージドなライブラリ(ラッパー)を用意する
  • プラットフォーム毎のネイティブライブラリ(ラップ対象)を用意する
  • nuspec ファイルを用意する(プラットフォーム毎に出し分ける設定とかも)
  • (モバイルも対象にするのであれば)targets ファイルを用意する

基本的にはこの4点を頑張ればネイティブライブラリを含む NuGet パッケージは作れます.

プラットフォーム毎のマネージドなライブラリ(ラッパー)を用意する

ここを本来は頑張って書くべきではあるのですが,実際自分もまだマーシャリングなどを詳しく理解できているわけではないので,実際に今回扱った例を挙げて簡単に紹介いたします.

今回は PInvoke を多用しました.以前は C++/CLI でラップしていたのですが,Windows 以外での環境を想定した場合動作しないという問題があったからです.

PInvoke ってどんなもの?という感じですが,Win32API などを使ったことのある人にとっては見覚えのある DllImport 属性のものとイメージしていただければいいと思います.

例としては

if __Linux
        private const string DllName = "libworld.so";
#elif __Win
        private const string DllName = "world.dll";
#endif
        [DllImport(DllName,CallingConvention = CallingConvention.Cdecl)]
        public static extern void CheapTrick([In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] double[] x,
            int x_length, int fs, [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex=5)] double[] temporal_positions,
            [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex=5)] double[] f0, int f0_length, [In] CheapTrickOption option,
            [In][Out] IntPtr[] spectrogram);

こんな感じです. ラップ対象のシンボル名のメソッドを用意して,引数に対して色々とアノテーションしてあげます.

今回の関数は C/C++ では

void CheapTrick(const double *x, int x_length, 
    int fs, const double *temporal_positions, const double *f0, 
    int f0_length, const CheapTrickOption *option, double **spectrogram)

として定義されています.

見ての通り基本的な型に関しては特に何か特別なアノテーションは必要ないのですが,配列や多次元配列,構造体に関してはアノテーションが必要になってきます.

配列に関して見てみると,[In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] double[] x という引数の定義になっています. これは [In] 属性がついているため,ネイティブ側での変更はなく一方向のメモリのマーシャリングのみでよいということがわかります.また [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] という属性では SizeParamIndex=1 とあるようにこの配列の大きさは 0 始点で,順番的に 1 の引数に与えられた数字の大きさであることを示しています. こうすることでマネージド環境での配列を非マネージド環境にその与えられたサイズだけ転送することが出来ます.

次に多次元配列ですが,こいつが非常に厄介です.正直なところ自分のやり方であっているのか少し自信がないところもあります. 実際探してみたのですが,今回のように double** として渡す方法がなさそうだったので,IntPtr の配列にして渡してしまいます(三次元以上になったらどうなるんだろう…).

以下,extern した関数を呼んでいるマネージドなコードです.

public static void CheapTrick(double[] x, int x_length, int fs, double[] temporal_positions,
            double[] f0, int f0_length, CheapTrickOption option,
            double[,] spectrogram)
{
    int outer = spectrogram.GetLength(0);
    int inner = spectrogram.GetLength(1);

    IntPtr[] ptrs_sp = new IntPtr[outer];

    for (var i = 0; i < outer; i++)
    {
        ptrs_sp[i] = Marshal.AllocHGlobal(inner * Marshal.SizeOf<double>());
    }

    CoreDefinitions.CheapTrick(x, x_length, fs, temporal_positions, f0, f0_length, option, ptrs_sp);

    var tmp_arr = new double[inner];
    
    for (var i = 0; i < spectrogram.GetLength(0); i++) {
        Marshal.Copy(ptrs_sp[i], tmp_arr, 0, inner);
        Buffer.BlockCopy(tmp_arr, 0, spectrogram, i * inner * sizeof(double), inner * sizeof(double));
        Marshal.FreeHGlobal(ptrs_sp[i]);
    }
}

やっていることとしては,二次元配列の大きさを調べて,IntPtr に対象の配列の型の大きさと配列の大きさでメモリを確保して非マネージド側に渡しているだけです. 上記のように予めどれだけのメモリが必要なのかを知っておく必要があるため,いい感じに設計する必要があります.(自信がないからこのあたりで切り上げる)

最後に構造体です.構造体でも簡単出来るものと出来ないものがあるのですが,今回は簡単なものについて触れたいと思います. 上記の CheapTrickOption はマネージド側ではクラスで定義されていますが,C/C++ 側では構造体で定義されています.

C# でのクラス定義は

[StructLayout(LayoutKind.Sequential)]
public class CheapTrickOption
{
    public double q1;
    public double f0_floor;
    public int fft_size;
}

C/C++ では

typedef struct {
  double q1;
  double f0_floor;
  int fft_size;
} CheapTrickOption;

となっています. C# 側で[StructLayout(LayoutKind.Sequential)]という属性を付け,型を同じにして順番も揃えてあげることで簡単に構造体をやり取りすることが出来ます. 面倒な構造体は中にポインタが入ったり,よく中身がわからない構造体が含まれているものなのですが,今回はなるべく触らないように逃げた& C/C++ 側の定義をちょっと変更したので述べません. 誰かわかったら教えてください.

と,以上の3つの要素を覚えればある程度のネイティブライブラリはラップできるかと思います. 更に踏み込んだライブラリは自分もラップしたことがないのでこの辺で〆たいと思います.

プラットフォーム毎のネイティブライブラリ(ラップ対象)を用意する

上記のステップでラッパーライブラリが完成したとします. あとは各プラットフォーム毎に共有ライブラリを作っていくだけです. 今回は Makefile が用意されていて単純なフローで作ることの出来るライブラリだったために,コンパイラとオプションをいじるだけで済みました.

以下メモ

  • Windows: cl.exe に /c オプションをつけてオブジェクトを生成し,link.exe に /dll オプションを付けて DLL を作る
  • Mac: -dynamiclib オプションを付けてオブジェクトをリンクする
  • Linux: -shared オプションを付けてオブジェクトをリンクする
  • Android: standalone toolchain をどうにかして頑張る.32bit の arm はオプションが若干変化
  • iOS: xcrun でコンパイラ関係探して流れに沿って行う.最後に lipo でまとめる.

上記の詳しい内容については

GitHub - yamachu/World: A high-quality speech analysis, manipulation and synthesis system

.travis.ymlappveyor.ymlMakefile などにまとまっています.

以上の流れに沿って各プラットフォームで共有ライブラリを作成します(iOSに限っては静的ライブラリ)

nuspec ファイルを用意する(プラットフォーム毎に出し分ける設定とかも)

Microsoft MVP である bonprosoft 氏の投稿の

blog.bonprosoft.com

を参考にしました. その為本記事ではこの内容に関しては割愛します.

またソースとしては DotnetWorld.nuspec を参照.

(モバイルも対象にするのであれば)targets ファイルを用意する

ここが一番意味苦労した部分だと思います. ターゲットがデスクトップ環境であれば runtimes/~/native 以下に置いておけば読んでくれたのですが,モバイル環境は別の操作が必要です.

非常に参考になったのは

realm-dotnet/Realm.nuspec at master · realm/realm-dotnet · GitHub

realm-dotnet/Realm.Database.targets at master · realm/realm-dotnet · GitHub

です(これがなかったら多分解決してなかった).

nuspec の方は特にこれと言ったものはないのですが,途中に targets ファイルを build フォルダに入れている流れが見えると思います. ここが非常に重要で,この targets ファイルは何をしているかというとビルドプロセスの中に入って何かしら決められたタスクをこなしています.

詳しい内容は

www.kekyo.net

が非常に参考になります.

この targets ファイルでビルド時にネイティブライブラリの位置を教えたり,属性はどんなものかなどを指定しています.

iOS を例に取ると

<?xml version="1.0"  encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <_WorldRootPath Condition="'$(_WorldRootPath)' == ''">$(MSBuildThisFileDirectory)..\..\native\</_WorldRootPath>
  </PropertyGroup>
  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == 'Xamarin.iOS'">
    <NativeReference Include="$(_WorldRootPath)ios\universal\libworld.a">
      <Kind>Static</Kind>
      <SmartLink>True</SmartLink>
      <IsCxx>True</IsCxx>
      <LinkerFlags>-lstdc++</LinkerFlags>
    </NativeReference>
  </ItemGroup>
</Project>

ツリーにするとこんな感じに NuGet のパッケージは構成されていて(だいぶ省略しましたが)

├─build
│  └─Xamarin.iOS10
│          DotnetWorld.targets
│
└─native
    └─ios
       └─universal
          libworld.a

この Xamarin.iOS10/DotnetWorld.targets を始点にどこに同梱するべきネイティブライブラリがあるかを上記の targets では示しています.

このようにモバイル環境の場合はリンクするライブラリを示して上げる必要があるので,大変だねってことを言いたいがためにこの記事書いたものでもあります.

パッケージング

nuget pack 頑張って作ったライブラリ.nuspec

を叩いて終わり.やったー

最後に

うっわ,すっごい手抜き記事…という感じがありますが,とりあえず自分がまた読んで思い出せるレベルには書けたかなという感じです. 実際にリポジトリの中身を見て理解するほうが早いと思うので,それと照らし合わせて手を動かしてみてください.

今回の記事でやっとデスクトップで使っていたネイティブライブラリがモバイルでも使えるようになったため,更にプロダクトの幅が広がりそうです.

またこの実験をする上で非常に Travis CI や AppVeyor が役に立ちました. 今度から積極的に使っていきたいです.

とあるウェブコンテストみたいなものに参加してから結果発表までに約半年かかったことに関してのお気持ち

非常に辛いお気持ちになってしまったので,コンテストなど参加者が熱意を持って参加するイベントを運営する側には今後このようなことがないようにというお気持ちを込めて雑に記録.

TL;DR

行われること 日時
締め切り 2016年12月2日23:59
プロダクトの評価 12月18日
当選発表 順次
当選発表延期のアナウンス 12月30日
当選発表(延期後) 年始
実際の当選発表 2017年5月13日

約半年のラグ.

何が問題だったか

1点目,上記の表のプロダクトの評価に関しての日程はおそらく参加者全員には伝わっていなかったかと思います. 今回のコンテストではWebアプリケーション(ChatBot)の作成がキモとなっていたため,WebサーバーのレンタルやAzureやEC2などのレンタルがほぼ必須となっていました. たかがか一つのコンテストのために借りるのもなと思っていた人もいるでしょうし,物によっては料金が発生しうる状況です. それでいながらいつまでに評価を行うというのがアナウンスされていなかったのでいつまでインスタンスを保持し続けなければわからない状態でした. このあたりは予め予想できていたはずであるのにアナウンスされていなかったのは非常に運営としての落ち度だと感じています.

2点目,また当選発表延期のアナウンスですが,この延期のアナウンスが行われていてから一切この件に関して連絡が参加者にされていません. またこの発表の後も他のコンテストを開催していて非常に理解に苦しみました.

3点目,18卒向けのコンテストの後始末もついていないのにもかかわらず

Twitterのアカウントでは告知を行っているようです. 運営体制はどうなっているのでしょうか.

学生支援だったりプロコンとかを開く企業にお願いしたいこと

  • 参加者が安心できるような連絡をください
  • 自分たちで一度実装可能かどうか,用意したテストケースで実現できるかの確認は行ってください(上には書いてないけどリジャッジとかもあった)
  • 問い合わせに対してはちゃんと他の人にも伝わるようにちゃんと連絡してください

はーつら,時間無駄にしたわ

ぼやき

この最終的な発表が行われる前に

このようなツイートがされました. このツイートがされる数時間前に定期的にRTしていた

を再度RTした後お気持ちを述べていたので,こう近い時間に発表が行われると,もしかして見られてる?みたいに思ってしまい怖いですね(笑

さて,自分はもう18卒というわけでもないですし,使う機会がないというのもありますが,もうこのような参加者に不信感を持たせてしまうような Sprint というサービスは使いたくないですね. しかも当選もしなかったし賞金でなかったしほんま時間の無駄だった(このプロダクト作ってとあるライブラリのバグ見つけてPR投げてマージされたのは良かったけど)

以上お気持ちでした.

雑記: Mastodon を見ての学祭での利用について

学祭アプリのタイムラインを Mastodon に置き換えたい.

前回のアプリケーションではサーバーとモバイルで頑張って WebSocket をはっていたので,再度今年実装などになると非常に面倒だろうなと思った. 問題になってくるのはアカウント登録や認証周り.ここは Mastodon の仕組みをちゃんと抑えて自前で改造するのが必要なのかなと思っている.

雑に Android であれば ANDROID_ID を取得してサーバー側で適当なアカウントを作ってしまい AccessToken を返して通信してもらうとかでいいか.

学祭などで Mastodon を使うと他の大学祭との交流とかに使えそうかもしれないなーという雑な思いがある.

こういった企画に関しては私はあまり得意ではないので,何かできそうだったら教えて欲しい.

の前に今年本当に学祭アプリ作成に携わるかどうかという感じなんですよね……….

C# で Mastodon のAPIライブラリの Mastodot を作った

最近 Twitter も見つつの Mastodon も見つつみたいな生活になってきました(どのインスタンスかは…察してください)(就活の方をちゃんとやりなさい)(研究が炎上している)

そうなると自分で適当なクライントを作ったり,API を叩いてちょっとした監視などをしたいなぁという欲求が出てくるようになってきます.

ということで実際にライブラリを作って Nuget に公開してみました.

GitHub - yamachu/Mastodot: C# Library for Mastodon. Easy Toot!

www.nuget.org

それで作ってみた監視の Bot がこちら.

qiita.com

使ってみる

とりあえず最低限使えるような流れを少し載せようと思います.

MastodonAPI を叩いてごにょごにょするためには Mastodonインスタンスに対して自分のクライアントアプリを登録して,それに紐付けられた ClientIdClientSecret を取得する必要があります.

var app = await ApplicationManager.RegistApp("mastodon.cloud", "はじめての Mastodon アプリ", Scope.Read | Scope.Write | Scope.Follow);

こんな感じで Mastodonインスタンスに今回では “はじめての Mastodon アプリ” というアプリを登録することが出来ました. ここで得られた app にさっき必要といった ClientIdClientSecret が格納されています.

このオブジェクト自体をシリアライズするなりトークンを別に保存するなりします(接続の度にアプリケーションを作り直すのはちょっと…)

  • AccessToken の取得

先程得られた ClientIdClientSecret を使って AccessToken を取得していきます. Mastodon ではユーザー認可に OAuth を使っているのですが,そこで必要になるのがその AccessToken です.

この取得方法には2種類あり,メールアドレスとパスワードのペアによる取得か,ログイン済みのブラウザなどで特定の URL に飛ぶことで行う方法があります.

様々な解説記事ではメールアドレスとパスワードのペアによるログインが多く載っているので,ここでは後者の方法を取ってみようと思います.

// さっきの app 変数が残っているのであれば,
var url = ApplicationManager.GetOAuthUrl(app);
// なければこんな感じで
// var url = ApplicationManager.GetOAuthUrl("mastodon.cloud", "ここにClientId", Scope.Read | Scope.Write | Scope.Follow);

ここで得られた url にログイン済みのブラウザなどでアクセスして認証?をクリックすると,真ん中にながーーーーーーい文字列が表示されます.これをコピーしてください.

var tokens = await ApplicaionManager.GetAccessTokenByCode(app, 長い文字列);
// もしくは
// var tokens = await ApplicaionManager.GetAccessTokenByCode("mastodon.cloud", "ここにClientId", "ClientSecret", 長い文字列);

ここで得られた tokens に お待ちかねの AccessToken が格納されています. これからはこの AccessToken を使って API を叩くのでどこかに保存しておいてください.

自分は今のところ確認していないのですが,たまに認証・認可を行う際にサブドメインを切ってそこで認証とかをする場合があります. その際は上のメソッドに名前付き引数で subdomain: "authenticate" みたいな感じに入れるといいと思います(仮に authenticate.mastodon.cloud みたいなURLで認証が行われるのであれば).

  • Toot しよう

簡単に HelloWorld 的なこともやってみましょう. 自分のクライントから Toot 出来たときは本当に嬉しいものなので(個人差があります),それをやってみます.

var client = new MastodonClient("mastodon.cloud", "ここにAccessToken");
var status = await client.PostNewStatus(status: "Hello Mastodon!");

こうすると対象の Mastodon インスタンスに自分の Toot が投稿されます.やったぜ

画像つきはちょっと面倒で,Xamarin とかだと自分で画像を byte[] に変換して〜という作業が入ってここに書くのは大変なので,コンソールアプリとかで実行していることを想定してみます.

var attachment = await client.UploadMedia("アップしたいファイルのパス");
var status = await client.PostNewStatus("矢澤にこは俺の嫁", mediaIds: new int[] { attachment.Id});

みたいにすると画像つきの投稿もできます. 他の詳しい仕様などは公式のドキュメント

documentation/API.md at master · tootsuite/documentation · GitHub

またはこのライブラリのソースなどをご覧ください(投げやり).

  • ストリームをだら見する

最後にストリームをダラダラと見ていきます. 本ライブラリでは Reactive 系のライブラリを導入しているため,ストリームに接続すると Observable 系のものが返ってくるようになっています. そのため他の Rx.* を使ったことのある人であればなんとなく使い勝手がわかると思います.

var publicStream = client.GetObservablePublicTimeline()
                    .OfType<Status>()
                    .Subscribe(x => Console.WriteLine($"{x.Account.FullUserName} Tooted: {x.Content}"));
            

ここでは流れてくる投稿のみをフィルタして表示していますが,OfType<Notification> などにすると通知が取れたりもします.

非常に簡単に使えるように,ということを心がけて作ってみたので,是非試してみてください.

まとめたコードなどは Mastodot/Program.cs at master · yamachu/Mastodot · GitHub

に置いてあるので参考にしてみてください.

バグ報告やIssue,PRお待ちしております!

久しぶりに Windows 使って開発する時に .NET Core 周りで困ったこと

はじめに

最近(1ヶ月前ぐらい)Windowsマシンを導入しました. 今使っているMac Book ProにもVMで入れてはいたのですが,ストレージが足りずに起動が出来なくなってしまって, ある意味いい機会だということで,どうせ重い作業をするのであればリモートで繋げばいいじゃんという風に考えたのがきっかけです.

数年前まではずっとWindowsを使っていたのですが,最近の開発環境の導入とかってどうなってるの?という浦島太郎状態です. そこで自分がやったことを備忘録として挙げておきます.(インストールしたOSはWindows10)

本題の.NET Coreはまた後ほど.

最低限の開発環境導入

絶対入れるもの

必要そうなもの

とりあえずこれだけで人権が担保されました. あとは適当に何か必要になったら入れることにします.

本題の .NET Core のこと

Windows で開発したい理由はもう一つあって,自分がちょっと使ってみた GitHub - reactjs/React.NET: .NET library for JSX compilation and server-side rendering of React components

っていうライブラリがどうも macOSLinuxWindows でパスの扱いの挙動が違うという問題を解決したかったということです.

Windows というか,Visual Studio で開発することのメリットとして,Windows 環境で動いているプロセスに対してデバッグが出来て,また Docker コンテナにデプロイしてからそいつに対しても デバッグが出来るという点があります. これをすれば Windows と他の OS での挙動の違いが探れると思い実際にやってみました.

が,まずこのライブラリを動作させることに苦労するという意味の分からない展開になりました. これがタイトルに挙げた .NET Core 周りで困ったことです.

このライブラリで使用されている .NET Core の SDK のバージョンは 1.0.0-preview2-003121 です. 現在 dotnet core とかで検索して見つかるページで配布されているものは 1.0.0-preview2-003131 で,バージョンが合わず,このままだと動いてくれませんでした. で,以外と検索すると見つかりづらいといいますか,ちょっとファイル名を予想して検索したりというようなことだったり,リリース時のブログ記事から URL を探したりみたいなことをする羽目に.

また OS を再インストールした時に迷わないようなメモを残そうということで,この記事を書いたわけです.

結論:

github.com

これを見る.

やったー見つかったーこれで開発できるーーーーーー

というわけでもないんですよね. インストールしてみようとするとバージョン云々で弾かれる. どうもこの問題はリリース時に色々問題として挙がっていたようです.

対策としては DotNetCore.1.0.0-VS2015Tools.Preview2.exe SKIP_VSU_CHECK=1 みたいにフラグをつけてインストールする.

最後に

ライブラリのパス問題の PR もマージされたので良かったです. なんというか,すごくその場しのぎ感ある対策ですが,とりあえず動くのでいいでしょう(

本当の最後に,

Windows 難しい.

VSCodeでの.NETアプリ開発の知見(随時追加

問題 - ローカル環境変数設定 - エディタ周り

状況

プログラム内で環境変数が欲しい時があって,そのプロジェクトのデバッグの時とか使う時のみで設定したい. VSCode内のTaskでsourceとかしてもデバッガ起動する時の設定のlaunch.jsonを実行するshellは別物だから環境変数が引き継がれないという問題に直面.

解決策

${workspaceRoot}/.vscode以下にsettings.jsonを追加する.そこに目的の環境変数を入れ込む.

{
 "WEB.TOKEN": "hoge",
 "WEB.KEY": "fuga"
}

launch.jsonからは

...
"env": {
 "TOKEN": "${config.WEB.TOKEN}",
 "KEY": "${config.WEB.KEY}"
},
...

問題 - tasks.jsonで色々コマンド使いたい - エディタ周り

状況

.NET のプロジェクトを読み込んで自動的に作られたtasks.jsonでbuild以外のコマンドをしたくて,buildのargs内に ; で区切ってシェルコマンドを入れたけどダメだった.

解決策

そもそもトップにあるコマンドが "command": "dotnet"だからってこと.ここをMac/Linuxユーザは"sh"とかに変えておけばいい感じだった. そしてコマンドを実行するために,その下にあるargsを "args": [ "-c" ]に変更. これで万事解決かと思いきやそうではなく,tasksの要素にsuppressTaskNameが含まれていないもしくはfalseになっている場合task名が第一引数として渡される.

 "command": "dotnet",
 "args", [],
 "tasks" : [
 {
  "taskName": "build",
  "args": [
   "${workspaceRoot}/project.json"
  ]
}

みたいになっていると,dotnet build ${workspaceRoot}/project.jsonとして実行されるということ. コマンド名と関係ないtask名をつけるのであれば,"suppressTaskName": trueとして設定するといい.

結局こんな形になった(上の環境変数を設定しようとしてダメだったやつが含まれてる)

{
    "version": "0.1.0",
    "command": "sh",
    "isShellCommand": true,
    "showOutput": "always",
    "args": [ "-c" ],
    "tasks": [
        {
            "taskName": "build",
            "suppressTaskName": true,
            "args": [
                "dotnet build ${workspaceRoot}/project.json"
            ],
            "isBuildCommand": true,
            "problemMatcher": "$msCompile"
        },
        {
            "taskName": "set-env",
            "suppressTaskName": true,
            "args": [
                "source ${workspaceRoot}/.token_env"
            ]
        }
    ]
}

問題 - yo aspnetで作られたDockerfileを使ってみたけどrestoreでこける - 開発周り

状況

2016/12/3現在yo aspnetで作られるDockerfileは以下のとおり

FROM microsoft/dotnet:latest

COPY . /app

WORKDIR /app

RUN ["dotnet", "restore"]

RUN ["dotnet", "build"]

EXPOSE 5000/tcp

CMD ["dotnet", "run", "--server.urls", "http://*:5000"]

テンプレートで吐き出されるのはバージョンが1.0.1のものだけれども,今のlatestは1.1.0なのでそのあたりでも怒られる.

解決策(正しいかは別

諦めた

FROM microsoft/aspnetcore:1.0.1

COPY ./out/ /app

WORKDIR /app

EXPOSE 80

ENTRYPOINT ["dotnet", "アプリ名.dll"]

で,このDockerfileでイメージをbuildする前にdotnet publish -c Release -o outって感じで${ProjectRoot}/out 以下に吐き出す. これでデプロイできた. Dockerとかコピペでcompose叩いてたからコマンドほとんど分からなくて疲れた

なにか詰まったら随時追加していきます.

WebSharpを触ってみた(導入編

Xamarinの強い人たちのツイートを眺めていたらこんな記述がありました.(会話の内容は省略)

なにこの見るからに面白そうなもの…ということで実際にサンプルだけでも触ってみることにしました.

そこそこ詳しくこっちに書いたのでこちらも併せてごらんください. qiita.com

追記に追記(2017年2月20日) 追記(2017年1月27日)

さて,現在開発をmacで行っているのですが,dotnet core のバージョンが 1.0.0-rc4-004616 こんな感じといいますか, project.jsonでプロジェクトを管理するのではなくcsprojで管理し始めたものになると,binding.gypとかを書き換える必要が出てきます. ちょっとdiffを用意し忘れましたが,見ればすぐわかります(雑

ちなみにそれでビルドは全部通ったのですが,スタックオーバーフロー起こしてElectronで起動できませんでした,現在原因調べてます.

予想はついてはいましたが,1.0.0-preview2-1-003177とかに戻すと動きますね…dotnet coreの最新のrc4のものをリネームしてpreview2を指すようにするという対処法で行っていますが, あまり良い方法ではないですね…

edge.jsのプロジェクトにglobal.jsonを加えてビルドするようにしたら問題ないですね.自分のフォークしたリポジトリに入れたのでそれを見ていただければと…

導入

GitHub - xamarin/WebSharp: Support for running C# on the web browser.

からリポジトリをクローンしてきて,その中のExampleをとりあえず眺めてみることに.

Pre-requisitesとして挙げられているものを確認してみると

  • Node.js
  • Native Abstractions for Node.js
  • PepperPlugin

の3つ.Electron触ったことある人ならNode.jsは入ってるだろうし問題無し.

nanはネイティブライブラリをラップしたりしてたことのある人ならば入っているだろうけど,入っていなかったらnpm i nanでインストール

最後のPepperPluginだけれども,この名前を聞くとChromeのあれかな…と勘違いしてしまう.あ,流行ってる(?)PPAPでもないです.ということで,このPepperPluginのインストールについて触れてみることにする.

リンク先に飛んでみると,

If you have never setup WebSharp before, use the make setup makefile target, this will download the dependencies for the build: Pepper SDK and NuGet:

make setup

と言われてるので大人しくmake setupを叩いてみると make: *** No rule to make target `setup'. Stop.

おいREADME!!!!!!!となるのですが,文章を見て察しろということなんでしょうね,WebSharpを一度もsetupしたことがないのであれば,と言っているので,リポジトリのルートディレクトリに行ってmake buildを叩くと無事に関連物がポンポンダウンロードされます.あとは言われた通りにmake buildを叩けば準備完了…と行きたいところですが, 私が以前に書いた Visual Studio for Macをインストールしてmono4.8+が無いと怒られた時の対処法 - Qiita を実践し,monoが4.8.0になっていた場合, mono/utils/mono-dl.hがないとか言われて怒られます.

なのでその時は

sudo ln -nfs /Library/Frameworks/Mono.framework/Versions/4.6.2 /Library/Frameworks/Mono.framework/Versions/Current

みたいにして以前のバージョンに戻してください.(この辺り.zshrcとかに関数用意しておこうかなあ…)

追記(2017年2月20日)

上記のリンクに載せたQiitaの記事に色々と対策やら考察やら書きました. とりあえずPKG_CONFIG_PATHを設定すればln -sとかをしなくて良さそうです.あとはmono 4.8.0 のバージョンを新しめの物にかえれば問題なく動作しそうですね.

あとは言われた通りに写経すれば動くはずです.動かないなーっていう人は,使われてるElectronが自分でglobalに入れたものでバージョンがあっていないとかその辺の問題だと思いますので,node_modules/.bin/electronあたりのものを使って起動しましょう.

とPepperPlugin必要なのかーという感じではありますが,機能を限れば不要でした.

というのもDotNetSamplesのサンプルを試した時はPepperPluginのインストールをしない状態で始めた時に動作していたからです. 詳しい理由とかは調べるのが めんどう たいへんなので,誰か詳しい方お願いします.

触ってみた感想としてはcsxみたいなものかーという感想です. 私はjs書くのが非常に苦手だったので,C# で書けるのはだいぶ負担が減って良いと思います(jsじゃなくてtsという選択肢もあってそっちも使ってたけど,やっぱりC# の方がいい).

すぐ試したいことはネイティブライブラリをラップしたやつでも容易に呼び出すことができるか,またReactの中に埋め込んでごにょごにょ出来るかって感じですかね.

ViewをReactで書いてモデル部をC# で書くとかごった煮感あってすごい楽しみ…Electronで使えるから,GUIアプリケーションもある意味C# で書ける環境が作られつつあるって考えてもいいんですよねやったーーー