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

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

Pythonのctypesを使ってネイティブライブラリを使う

また備忘録として.

前回は NuGet でプラットフォームごとにネイティブライブラリを同梱してそれを使うラッパーのパッケージを作る方法 - 窓を作っては壊していた人のブログ という感じでC# でネイティブライブラリをラップして使うようなことを紹介しました. 私はC# が好きだし,Xamarin Workbooks などがあるため REPL があるため C# だけでもいいかなと思っていたのですが,Python もあるとやっぱり楽だなぁということを思うようになりました.

ということで Python でラップするとどうなるの?ということをメモしていきたいと思います.

おことわり

今回は 16.16. ctypes — Pythonのための外部関数ライブラリ — Python 3.6.1 ドキュメント を使いましたが,今ネイティブライブラリをラップしたり速度を求めるなら一般的には Cython: C-Extensions for Python を使います. 今回 ctypes を使った理由としては,Cython を入れることが困難な環境(そんな環境あるのかよという感じですが… Windows ユーザーでも今は簡単に入りますよね?たぶん)とかでも簡単に使えるからと言う理由です. 自分の研究室のメンバーがあまり Python に詳しくなさそう()というのもあるので,とりあえずどんな制限下でも使えないといけないという…

ネイティブライブラリの準備

もはやここは gcc の使い方とか,Windows で言ったら cl.exelink.exe の使い方の話になるので省略.

github.com

このリポジトリmakefilebuild_windows.bat を見れば大体わかります. Windows の nmake が使いたかったのですが,あまり理解できていないので cl.exe と link.exe を愚直に叩くやり方で頑張っています.

今回は以下のようなライブラリを想定してやってみます.

void mod_1d_array(double *arr, int size)
{
    for (int i = 0; i < size; i++) arr[i] *= 2;
}

int sum_1_to_value(int value)
{
    return value + 1;
}

void value2times(int *value)
{
    *value *= 2;
}

ネイティブライブラリを使う

とりあえず流れを追ってみましょう.

ライブラリをロードする

import ctypes

my_clibrary = ctypes.cdll.LoadLibrary(YOUR_C_DYNAMIC_LIBRARY_PATH)

my_clibrary に先程(?)用意したライブラリをロードしたものを格納します(語弊があるけど気にしないでください).

この my_clibrary の attribute にロードしたライブラリのシンボルが含まれているので,以下のようにして対象の関数を呼び出すことが出来ます.

関数を呼び出してみる

result = my_clibrary.sum_1_to_value(1)

この result には 2 が格納されていると思います.

非常に簡単,Python から C のライブラリが呼び出せるとか最高!という感じですが,まだまだ序の口,簡単な型だから出来たわけです.

それでは参照渡しとかそういうのをやってみましょう…と言いたいところですが,ちょっと待ってください. 引数の数だったり,引数の型,戻り値の型とかをちゃんとアノテーションしておかないと,後から見た時にわかりづらくないですか?

実際 ctypes を使う時は引数を誤ってしまった場合など,セグメンテーションフォルトで落ちたりします,つらいですよね. しかし予め型の定義をしておくと渡したパラメータがその型と互換性があるか,キャスト可能かなどの判定を行った後に呼び出されるので非常に安心です.

ということで,型の定義をしてみましょう.

型定義で安心して使えるようにする

今回用意した3つの関数について定義してみようと思います.

# void mod_1d_array(double *arr, int size) の場合
my_clibrary.mod_1d_array.restype = None
my_clibrary.mod_1d_array.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.c_int]

# int sum_1_to_value(int value)
my_clibrary.sum_1_to_value.restype = ctypes.c_int
my_clibrary.sum_1_to_value.argtypes = [ctypes.c_int]

# void value2times(int *value)
my_clibrary.value2times.restype = None
my_clibrary.value2times.argtypes = [ctypes.POINTER(ctypes.c_int)]

こんな感じになります. ポイントとしては,配列やポインタなどの参照渡しの場合は,対象の型を POINTER で囲ってあげればポインタであるということが明示することが出来ます.

これだけでぐっと ctypes を使った時のネイティブライブラリへのアクセスが安全なものになります. Jupyter Notebook とかで開発している時はものすごい助けられました.

また副産物としては,このように attribute にアクセスするので,もしもそんな関数がシンボルとして定義されていない場合は例外を吐きます. ネイティブライブラリを実装している時にシンボルの公開忘れなどがあった場合そこで気づくことも出来ます(今回自分で実装していたライブラリで Windows でのシンボル公開を忘れているものがあったのにこれで気づくことが出来た).

それでは今回定義したもので安全に関数を呼び出してみましょう.

参照を伴う関数の呼び出し

まずはシンプルな変数の参照渡しをやってみましょう.

value = ctypes.c_int(10)
my_clibrary.value2times(ctypes.byref(value))
print(value.value)

20が出力されたでしょうか. このように値の参照渡しは byref によって行います.

さて,次は配列です. これはすこし厄介です. 上記の value2times は配列とかでもなんでもないものを渡すだけだったので byref だけで良かったのですが,今回は配列の確保なども行わなければなりません.

今回は 1 ~ 10 の値が入った配列を mod_1d_array で操作してみます.

my_array = list(range(1, 10 + 1)) # Python2.x ならこれでいいけど,3.x だと list(range(1, 10 + 1)) とかにしないとだったりする?
my_c_array = (ctypes.c_double * len(my_array))(*my_array)
my_clibrary.mod_1d_array(my_c_array, len(my_array))
print(list(my_c_array))

2.0, 4.0, …. みたいになったと思います.

Cライブラリに配列を渡す時は 目的の型 * 配列の長さ という を作って,そのインスタンスを作ります. ここでは ctypes.c_double * len(my_array) としていて,この場合 c_double_Array_10 という型になります.

そのためここでは my_c_array という c_double_Array_10 という型のインスタンス*my_array のようにリストを展開して初期化しています. ポインタ型と 1d-Array は型の互換があるので,そのまま渡しても自動的にキャストされてそのまま使うことが出来ています.

こんな感じでやり方さえ覚えてしまえば配列だろうが簡単に渡すことが出来ます.

早いけどおわりに

今回は ctypes を使ってネイティブライブラリを使う初歩をお伝えしました.

実際の環境ではこんな簡単なライブラリじゃなくてもっと型定義がややこしいものなどたくさんあると思います. そんな時は公式ドキュメントをちゃんと読んでみるとヒントがあったりします.

また構造体や2次元配列みたいのは少しテクニックが必要だったりします.

それについては実際に私がラップした

github.com

とかを見ていただければと思います.

毎回途中まで記事書いて力尽きるのやめたい

没ネタ

頑張って書こうとしたけど,まとめるのが面倒でソース見てって思ったもの

  • 多次元配列に関してはサポートモジュールを作っておくと楽
inner_type = (c_double * inner_length)()
outer_type = (inner_type * outer_length)()
buffer = outer_type()

for i in range(outer_length):
    buffer[i] = inner_type()

とか二次元配列用意する時に書くのアホらしすぎる.

またこれは c_double_outer_length_array_inner_length_array みたいな型定義になって, POINTER(POINTER(c_double)) の型とマッチしなくて,型チェックで弾かれてしまう.

だからこれを cast という形で整えてあげる必要がある. これも一気にやるサポートモジュールを作ると楽.

  • ライブラリ名とかを列挙して LoadLibrary し易い形に

__init__.py でやったこと. ctypes ではないけれども,pkg_resources を使うことによって,そのライブラリがあるパスを特定出来るので,安全にファイルを探すことが出来る.

  • 配布のための setup.py で pre-build を含ませる

Python の C拡張 を今回は使ったわけではないので,同梱したりするのが難しかった. これも ctypes とは関係ないけれども,setup.py で引っ張ってくるようにしたらある程度楽になると思う.

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叩いてたからコマンドほとんど分からなくて疲れた

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