真intermediate languageのキホン
TRANSCRIPT
真Intermediate Languageのキホン2016.3.19 第六回CENTER CLR勉強会 KOUJI MATSUI (@KEKYO2)
自己紹介
けきょ (@kekyo2)
ロードバイク乗り
Microsoft MVP for Visual Studio and Development Technology
認定スクラムマスター・スクラムプロダクトオーナー
Center CLRオーガナイザー
ようこそ
最近、VRとか話題に上っておりますね。
私もGAME ONでプレステVR体感してきましたよ。
IBMとかなんちゃってSAOとかやってるようで、気合の入り方が斜め上ですなぁ。
インサイド: http://www.inside-games.jp/article/2016/03/17/96944.html
本日は
それはさておき、本日はIntermediate Language(中間言語)のお話です。
「よく来たなひよっこどもよ」
本日は
ILは基礎的な学問でいう所の「算数」みたいなもので、とにかくやって理解するのが近しい。応用の事は2の次。
なので、
よく言えばワークショップ
悪く言えば算数ドリル
はじめに
導入前回結構大変だったので、今回は最初に少しだけ抑えるべきところを説明しておきます。
① ILで覚えておく最初のことは、
「System.Reflection.Emit名前空間」だ。
◦ MSDNやILSpyで調べる足掛かりにする。
導入
「System.Reflection.Emit.OpCodesクラス」
◦このクラスに「オプコード」の定義が含まれている。
コードから使えるようにする利便性のため(Meta-programming)
導入
②オプコード(OpCode)とは:◦ CLRが理解できる中間言語(バイトコード)
CLR
OpCode.Ldarg
OpCode.Add
OpCode.Ldfld
OpCode.Call
OpCode.Ret
0x20
0x58
0x7b, &_v
0x28, &ToString
0x2a
導入
③ OpCodeにはいくつか種類がある◦定数系: Ldc Ldstr Ldnull Ldtoken
◦引数操作系: Ldarg Starg
◦ローカル変数系: Ldloc Stloc
◦フィールド操作系: Ldfld Stfld
◦配列操作系: Ldelem Stelem Newarr
◦計算系: Add Sub Mul Div And Or Xor Not Shl Shr
◦生成系: Newobj Dup
◦型変換系: Conv Castclass Box Unbox
◦遷移系: Br Call Callvirt Ret Throw Rethrow Switch
◦その他: Ldind Stind Localloc
導入
④スタック操作・スタックマシンとは:◦ CLRがバイトコード由来で動作するための基礎となる構造
CLR
1. Ldarg
3. Add
2. Ldfld
CLR Stack
123
456
導入
④スタック操作・スタックマシンとは:◦ CLRがバイトコード由来で動作するための基礎となる構造
CLR
3. Add
CLR Stack
123
456123
456
579
導入
④スタック操作・スタックマシンとは:◦ CLRがバイトコード由来で動作するための基礎となる構造
CLR
3. Add
CLR Stack
579579
いよいよ実践
準備
適当にチーム分けします。
チームには少なくともVisual Studioが入ったPCを。
GitHub: https://github.com/kekyo/CenterCLR.ReflectionEmitTemplate.git
Macの人は、可能ならVisual Studio Codeと.NET Core5をインスコ(あんまりフォローできないけど、ソースはnetcore5で動くようにしてあります)
ILSpy必須(Bingる)
Part 1引数の値に1を加算して返すようにする。
ポイント:◦引数の値はどうやって取得するか
◦加算するにはどうするか
ヒント:◦引数操作系: Ldarg Starg (MSDNのOpCodesクラスを見る)
◦計算系: Add Sub Mul Div And Or Xor Not Shl Shr
◦戻り値を返すには?元のコードはどうなっていた?
Part 2引数を増やして、加算して返す
ポイント:◦引数を増やすためには、Emitterも変更する必要がある。
ヒント:◦ DefineMethodとは
Part 3引数の文字列を値に変換する
ポイント:◦ Convert.ToInt32メソッドを使う事にする
◦メソッドを呼び出すにはCallオプコードを使う
◦沢山のオーバーロードからどうやって絞り込むか?
ヒント:◦ MethodInfoはType.GetMethodsで取得する
Part 4二つの引数の値をstring.Formatでフォーマットする
ポイント:◦ String.Formatメソッドのオーバーロードをどうやって特定するか?
◦型が違う場合は、変換する必要がある
ヒント:◦ MethodInfoはType.GetMethodで取得する
◦型変換のIL命令は2種類ある
◦ object hoge = 123; という式の暗黙変換の事を何て言ったっけ?
Part 5インスタンスメソッドを作る(仕様はPart4のまま)
ポイント:◦ テンプレートコードはstaticメソッドを作って、そのデリゲートを返す。だからEmitterを変更する必要がある
◦ インスタンスメソッドを実行するには、インスタンスが必要(インスタンスの生成はILでやらなくて良い)
◦ thisはどこにあるのか◦ デフォルトコンストラクタを定義する
ヒント:◦ DefineMethodのフラグによって、定義するメソッドの属性が変わる◦ インスタンスメソッドの第0引数がthis◦ Activatorクラス
Part 6フィールドを作り、前回の結果も加えて結果を計算する
ポイント:◦ TypeBuilderを使ってフィールドを定義する
◦フィールドにアクセスするIL命令
ヒント:◦「フィールドの値を読み書き」と言うより、「フィールドの値をスタックに積んだり出したり」
Part 7C#で書いたクラスを継承する
ポイント:◦クラスはpublicでnon-sealedで定義する
◦クラスのコンストラクタは呼び出し可能でなければならない
ヒント:◦「継承するクラス型の指定」は、どこで定義される情報なのか?
Part 8継承クラスのインスタンスメソッドを呼び出す
ポイント:◦継承元のクラスのメソッド定義を入手して呼び出す
◦呼び出しにthisが必要
ヒント:◦なし
Part 9継承してオーバーライドされた仮想メソッドを呼び出す
ポイント:◦基底クラスのメソッド情報を使う
◦オーバーライドされたメソッドが呼び出される
ヒント:◦仮想メソッドを呼び出すIL命令
Part 10オーバーライドされた仮想メソッドを無視し、基底メソッドを呼び出す
ポイント:◦ C#では実現できない
ヒント:◦ IL命令は実直
Part 11クラスのインスタンスを生成する
ポイント:◦ newするには?
◦コンストラクタとは
ヒント:◦ newに対応するIL命令と、その引数
Part 12プロパティを読み書きする
ポイント:◦プロパティ情報を取得する
◦ Getter/Setterメソッドとは
ヒント:◦なし
Part 13Windows Formを表示し、タイトルバーに”Hello IL”と表示する
ポイント:◦ Formクラスを使い、インスタンスを生成し、プロパティにアクセスする
◦ ShowDialogメソッドを呼び出して表示する
ヒント:◦なし
その他のトピック
デリゲートの生成:◦ Delegate.CreateDelegateとかをいじってからILに望むと吉。
イベント:◦プロパティと非常によく似ている。実態はadd/removeメソッド。
仮想メソッドのオーバーライド:◦まずDefineMethodでメソッドを定義してから、DefineMethodOverrideで基底メソッド定義と関連付ける。
インターフェイス型定義と実装:
あとはほとんど応用だ!ILで書く範囲を少なくできるように工夫する。◦例:基底クラスやユーティリティクラスをC#で書いておき、複雑だがIL書かなくてもよい処理をオフロードする
Emitは色々つらいので、他の方法も理解しておく。◦ Emitは実行環境が限られる。Standard CLRやCore5ではサポートされているが、UWPとかダメ
◦ Emitでコードを量産しようとしている → T4テンプレートを使うと、似たようなソースコードをマクロ的に大量生産できる
◦動的型定義はなく、単に高速実行可能な動的言語がほしい → 式木を使う(式木構造について理解が必要だが、不正なILを作ってしまう危険はない)
◦サードパーティライブラリを使う(Mono.CecilはCLRに依存しないでバイナリを生成できる・Emit定義が楽になる各種補助ライブラリとか)
がんばりました!!