nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Development Technologies。

Infragistics Web Day 2017に参加してきました

表題の通りですが、Infragistics Web Day 2017に参加してきました。

connpass.com

ここ数年(多分8年とか?)まともにWebシステム開発に携わって来なかったのですが、今後ノータッチという訳にもいかないし、特にWebフロントエンドの知識の最新化は、今期の組織課題としてちょうど取り上げていたところでした。非常によいタイミングで告知があったので少し厳しいスケジュールでしたが参加させていただきました。

結果としては満足いく内容で、とくにパネルディスカッションと懇親会でお話させていただいた内容は、面白くもあり、ためにもなりました。
こういった場を提供していただいたInfragisticsさんに感謝の念が絶えません。

という訳で?簡単にですが感じたこと、考えたことを残しておきたいと思います。ぶっちゃけポエムなんで怒らないでください。
なお当日の時系列には全く沿っていません。
また完全仕事モードでしたので、個人の趣味嗜好ではなく一般的な受託開発メインのSIer的視点での感想になっています。個人の趣味趣向とは大きくずれている点があることを先にお伝えしておきます。

続きを読む

XamarinでもAOPしたい! Fody&Mono.Cecil編

さて前回、Mono.Cecilを利用した静的なAOPについて解説しました。

今回はいよいよXamarin.iOSでも動作する、静的なAOPの実装について解説したいと思います。Mono.Cecilを使ってILを織り込むFodyのアドインを作成し、コンパイル時に自動的にILを織り込むところまでを解説します。

具体的にはViewModelのメソッド呼び出し時にDebug.WriteLineでクラス名とメソッド名をログ出力するサンプルを作成します。

「使い勝手の良いAOPフレームワーク」は、まだまだお預けですが、今回を理解すればあとはILを「頑張れば」できたも同然でしょう(つまり先はまだ長いです)。

では早速内容に入って行きましょう。

続きを読む

XamarinでもAOPしたい! Mono.Cecil編

さて前回、Cauldron.Interception.Fodyを利用すればXamarinでもAOPできるんだけど、仕様が好みじゃないので作るしかないな!というお話をしました。

IL弄ってアスペクトを織り込むアドインを作成するわけですが、ILを弄ると言うと黒魔術のように感じるかもしれません。私自身そう思っていました。
しかし今回実際に触ってみて思ったのですが、技術的な難易度的には黒魔術というほどでもないかな?と、感じることができました。これはもちろん、ILを編集するための基盤を過去の偉人が構築してくれていたことも大きいです。
ただ通常の開発手段として気軽に使うべきではないという意味では、いかに容易に扱えるフレームワークがあると言っても黒魔術であることは変わりないでしょう。

という訳で、今回から2回にわたってXamarinで静的AOPを実装するためのHello, Worldレベルの解説をしたいと思います。
これらのエントリーでILを操作するする雰囲気をつかんでいただけたらと思います。
それでは本題に入りましょう。

静的AOPのアプローチ

通常の実行時にReflection.Emitなどを使って動的コード生成を利用するAOPに対して、アプリケーションの実行前、コンパイル時にアスペクトを織り込んでおくアプローチのため、仮に静的AOPと呼ぶこととします。異論は受け付けますというか、適切な名称があるのであれば指摘ください。

さてこれまで、PropertyChanged.FodyやCauldron.Interception.Fodyを紹介してきましたが、実のところ静的なAOPを実現するためのコアテクノロジーは二つあります。

  1. Fody
  2. Mono.Cecil(実際には4種類程のNuGetパッケージに分割されています)

このうち、実際にILの編集を行う機能を提供するのはMono.Cecilになります。
FodyはMono.Cecilを利用してILを編集する機能を実装したアドインを、MSBuildと協調してコンパイル時に制御することで、静的AOPを容易に活用する仕組みを提供するものになります。

全2回による解説の概略

という訳で、静的AOPのHello, World.を解説するにあたり、今回と次回の2回にわたって次のような手順で解説していきます。

今回:Mono.Cecilを利用し既存アセンブリに任意のILを織り込む方法の解説
次回:Mono.Cecilで作成したAOPアドインをFodyを使って適用する方法の解説

今回のゴール

ビルド済みのアセンブリ内の、指定のクラスの指定のメソッド呼び出し時に、ログ出力するコードを織り込む。
今回は.NET Standardではなく、すべて.NET Frameworkで実装します。

参考情報

さて私自身、先にも書いた通りIL読んだり書いたりするのは今回が初めてで、ぶっちゃけちゃんと説明できません。
という訳で、参考にさせてもらった文献を紹介しておきたいと思います。

手順概略

ILを編集してログ出力するにあたり、今回は次の手順で進めます。

  1. IL編集前のコードを実装し、ILSpyを利用してILを確認する
  2. ログ出力コードをC#で普通埋め込み、ILSpyを利用して差分を解析する
  3. 既存アセンブリにログを出力するILを埋め込むコンソールアプリを作成する
  4. 3.で編集したアセンブリを実行し動作を確認する
  5. 3.で編集したアセンブリをILSpyを利用して確認する

IL編集前のコードを実装する

それでは実際に始めましょう。まずはILを編集対象のプロジェクトを作成しましょう。
WeaveTargetという名称のコンソールプロジェクトを作成します。

f:id:nuitsjp:20171211132308p:plain

そしてProgram.csを開いて次のようなコードを記載しビルドします。

class Program
{
    static void Main(string[] args)
    {
        SayHello();
        Console.ReadLine();
    }

    private static void SayHello()
    {
        Console.WriteLine("Hello, AOP!");
    }
}

このコードをビルドしてILを覗いてみます。覗くのにはILSpyを利用します。
ILSpyを開くと次のような画面が開かれます。

f:id:nuitsjp:20171211132914p:plain

ここに先にビルドしたWeaveTarget.exeをドラッグ&ドロップし、開かれた中から前述のSayHelloメソッドを覗いて見ましょう。

f:id:nuitsjp:20171211230510p:plain

デフォルトではILから逆コンパイルされたC#のコードが表示されますが、今回見たいのはILです。という訳でドロップダウンをC#からILに切り替えてILを確認して見ましょう。

f:id:nuitsjp:20171211230523p:plain

抜粋したILのコードが次の通りです。

IL_0000: nop
IL_0001: ldstr "Hello, AOP!"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret

さて、今回はILの読み方そのものは解説しません。興味ある方は次の二つを参考にされると理解が進むでしょう。

ログ出力コードをC#で普通埋め込み、ILSpyを利用して差分を解析する

さて、先ほどのSayHelloメソッドをもう一度見て見ましょう。

private static void SayHello()
{
    Console.WriteLine("Hello, AOP!");
}

今回のゴールは「指定のクラスの指定のメソッド呼び出し時に、ログ出力するコードを織り込む。」でしたよね?という訳でSayHello()メソッド編集してつぎのコードと同義のコードを生成することを目指します。
が、ひとまずは実際のコードを修正してログ出力コードを埋め込んでビルドし、ILがどう変わるか確認してみます。

private static void SayHello()
{
    Console.WriteLine("Program#SayHello()");
    Console.WriteLine("Hello, AOP!");
}

そして再びビルドしたアセンブリをILSpyで覗いてみましょう。ILは次のように変わっています。

IL_0000: nop
IL_0001: ldstr "Program#SayHello()"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ldstr "Hello, Fody!"
IL_0011: call void [mscorlib]System.Console::WriteLine(string)
IL_0016: nop
IL_0017: ret

IL_0000、IL_0001、IL_0006が新たに追加されており、IL_000b以降は変化がありません。
つまり、既存のILに対してIL_0000〜IL_0006の内容を挿入してあげれば、同等の振る舞いが追加できるはずだ、と言うことになります。

ILを自力で記述する場合は、このように

  1. 実現したいコードを書いて見てそのILをチェックする
  2. ILを吐き出すコードを記載する
  3. 吐き出されたILをチェックし、また逆アセンブルしてC#コードをチェクする

これを繰り返して行くのが一般的な開発スタイルのようです(私も始めたばかりなので間違ってるかもしれませんが)。

さて、ここでログ出力のC#コードはいったん削除してリビルドしておきましょう。

既存アセンブリにログを出力するILを埋め込むコンソールアプリを作成する

それでは実際にILを編集していきます。
まずはILを編集するコンソールアプリのひな型を作成しましょう。

WeaveLogOutputという名前のコンソールアプリを作成し、NuGetからFodyCecilパッケージを適用しましょう。

f:id:nuitsjp:20171211231301p:plain

すると実際にはFodyCecilライブラリの参照は追加されず、Mono.Cecil系のライブラリが4つ参照に追加されます。
FodyCecilは実体がなく、依存するライブラリが定義されているだけです(こういうライブラリを何て言うんでしたっけ?)。

それではここにILを編集するコードを記述していきましょう。 まずは編集対象のアセンブリを読み込み、対象のメソッドを特定します。

static void Main(string[] args)
{
    var path = @"..\..\..\WeaveTarget\bin\Debug";
    var module = ModuleDefinition.ReadModule(Path.Combine(path, "WeaveTarget.exe"));
    var type = module.Types.Single(x => x.Name == "Program");
    var method = type.Methods.Single(x => x.Name == "SayHello");
}

そしてILを編集するProcessorと、SayHelloメソッドの先頭行を取得します。

static void Main(string[] args)
{
    ...
    var processor = method.Body.GetILProcessor();
    var first = method.Body.Instructions.First();
}

ここからfirstの前にログ出力のILを埋め込んでいきます。
改めて織り込むILを確認します。

IL_0000: nop
IL_0001: ldstr "Program#SayHello()"
IL_0006: call void [System]System.Diagnostics.Debug::WriteLine(string)

1行目と2行目は簡単ですね。次のようにしてfirstの前にILを織り込みます。

static void Main(string[] args)
{
    ...
    processor.InsertBefore(first, Instruction.Create(OpCodes.Nop));
    processor.InsertBefore(first, Instruction.Create(OpCodes.Ldstr, $"{type.Name}#{method.Name}()"));
}

3行目は少しだけ面倒です。少しだけね。
ConsoleクラスのWriteLineメソッドのMethodInfoを取得した上でILを挿入してあげます。

static void Main(string[] args)
{
    ...
    var consoleWriteLine = typeof(Console)
        .GetTypeInfo()
        .DeclaredMethods
        .Where(x => x.Name == nameof(Console.WriteLine))
        .Single(x =>
        {
            var parameters = x.GetParameters();
            return parameters.Length == 1 &&
                    parameters[0].ParameterType == typeof(string);
        });
    processor.InsertBefore(first, Instruction.Create(OpCodes.Call, module.ImportReference(consoleWriteLine)));
}

そして最後に編集したモジュールをかき出します。

static void Main(string[] args)
{
    ...
    module.Write("WeavedTarget.exe");
}

読み込んだ編集対象のアセンブリは「WeaveTarget.exe」でしたが、書き出したあとは「WeavedTarget.exe」に名前を変えています。

編集したアセンブリを実行し動作を確認する

それではWeavedTarget.exeを実行してみましょう。

f:id:nuitsjp:20171211232934p:plain

正しくログが織り込まれていることが確認できます。

編集したアセンブリをILSpyを利用して確認する

続いてWeavedTarget.exeをILSpyで覗いてみましょう。

f:id:nuitsjp:20171211233120p:plain

想定通りのILが書き出されていることが見て取れます。C#コードにリバースした結果も見てみましょう。

f:id:nuitsjp:20171211233209p:plain

完璧に想定通りのコードですね。
これで既存のアセンブリへ、ILを織り込むことができました。
今回は手動で織り込みを実行しましたが、次回はFodyを利用して自動的にコンパイル時に織り込まれるようFodyのアドイン化を解説したいと思います。

という訳で、今回はここまで! 近いうちに続きでお会いしましょう!

XamarinでもAOPしたい! Cauldron.Interception.Fody編

前回XamarinでAOPするにはIL弄るライブラリを自作するしかないと言ったな?
あれは嘘だ。Cauldron.Interception.Fodyを使えばできる。

www.nuget.org

なんてこった…まぁ皆さんには朗報ですしょう。私は赤面の限りですが。

という訳で、今回はCauldron.Interception.Fodyを使ってXamarinでAOPをする方法(正確にはメソッド呼び出しをインターセプトする方法)を紹介したいと思います。

続きを読む

XamarinでもAOPしたい! 希望編

この記事は [初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 の6日目の記事です。

qiita.com

空いたままだったのが忍びなく埋めようと書いた記事なので、完成まで到達できていませんがその辺はご容赦ください。
予定では25日の本チャン?までには完成する…といいな?的な?

続きを読む

やっぱりXamarin.iOSではRealProxyは使えなかった話

動的コード生成が絡むだろうし無理だろうと思ってたんですが、なぜかRealProxyクラスはあってコンパイルはできるので試してみました。

f:id:nuitsjp:20171204133000p:plain

はい、ダメでした。bugzillaにも上がってますね。動的生成コードが多くなるからiOSじゃ無理だよと。
27847 – Linked away exception on CallContext.LogicalGetData("SomeString") on device not simulator

しかしなぜRealProxyクラスがあるんだろう?Xamarin.Macのため??良く分かりません。まぁいっか。

AOPの手段を検討してたんですが、やっぱりIL弄るしかないですかね。 全部Commandパターンにするという荒業も無い訳ではないですが、それはやりたくないのでIL操作かな。 良く分かってないので辛いなあ。