Uzzu::Blog

Software Design, and my life.


monoのソースを読み始める話

Tags: csharp mono

この記事はC# Advent Calendar 2014の10日目のエントリです。 9日目はASP.NET/IIS環境での証明書の取り扱いにおけるノウハウ話でした。

近年はXamarinUnity3Dがお盛んで、今年初旬にmono for UnrealEngineが発表(2014年12月現在では辛い状態ですが)されたり等、何かとmonoのお世話になることが増えてきたんじゃないでしょうか。かくいう私もお世話になっているうちの1人です。お世話になっている訳ですから、その実装に興味を持ち始める人もいるんじゃないかなと思います。そこで本記事では、とっつきやすそうなところをなんとなく追っていきます。

まずは、mono/mono @ github.comからmonoをcloneします。Unity3Dな方はUnity-Technologies/mono @ github.comからどうぞ。

1
2
3
4
git clone https://github.com/mono/mono.git # Unity3Dな方は以下 git clone https://github.com/Unity-Technologies/mono.git

まずとっかかりが早いのはmscorlibですかね。分かりませんが進めます。
前提として、mcs/class以下に、アセンブリとして配布されるたくさんのソースコードがあります。mcs/class/corlibディレクトリがmscorlib.dllのソースコードに相当します。その他の実装を参照する場合も、アセンブリ名に紐づく形でディレクトリが掘られています(例えばSystem.LinqはSystem.Core.dllに含まれているので、mcs/class/System.Coreにある、といった感じ)から、分かりやすいですね。

ソースコードを読む際は合わせてMicrosoft ReferenceSourceも一緒に読むと、楽しくて1日が終わります。mono上での実装は.NETのそれと比べて非常にシンプルなものとなっているので、たとえばSystem.Linqの述語がどういう実装になってるのか?みたいな疑問は、monoのソースコードを読んだ方がもしかしたら早いかもしれません。

1つ言い忘れていましたが、みなさんお使いのエディタにはアセンブリブラウザが搭載されているでしょうから、そちらからdllを参照するでも良いですが、その場合アセンブリとしてコンパイルされた状態になっているので、IEnumerableやasync-awaitを使用したソースコードはCompilerGeneratedなソースコードになって出てくるので、今まで読んだことのない方は気持ちでやっていく必要が高まってしんどいかもしれません。

それでは実際にソースコードを追ってみましょう。今回は、System.Globalization.CultureInfoを読んでみます。

ふむふむと読み進めていくと、所々で

1
if (!constructed) Construct ();

というように、プロパティが実際に参照されるまでは値を取得しないように遅延評価が行われている事が分かります。Constructの実装はここですね。更に追っていくと、construct_internal_locale_from_lcid (cultureID);を実行しています。 このメソッドをみると

1
2
[MethodImplAttribute (MethodImplOptions.InternalCall)] private extern bool construct_internal_locale_from_lcid (int lcid);

となってますね。MethodImplOptions.InternalCallを指定していますので、CLR(共通言語ランタイム)上に実装されていることが分かります。「ぬああああん(以下略」とならず、もっといきましょう。monoならもっと先が読める!(コレが一番言いたかった)

monoにおいては、MethodImplOptions.InternalCallで指定されたメソッドは、mono/metadata/icall-def.h上でマッピングされています。今回の例で言えば、マッピングは同ヘッダにおいてこのように定義されており、そのメソッドの実装はmono/metadata/locale.cに、にあります。マッピングされているメソッド名はves_icall_{名前空間}_{クラス名}_{メソッド名}という命名規則(vesはVirtualExecutionSystemの略かな?分かりませんが)で定義されてたり、そうでなかったりとまちまちです。概ね(mini本体に含まれるものを除き)mono/metadataに含まれています。その他のメソッドを探す際も、ves_icall、あるいはicall-def.h上でのマッピングルールを見ながらgrepをかければみつかりそうですね。

InternalCallしているメソッドが分かったら、あとは頑張ってCを読みます。CultureInfoについて追っていくと、最終的にmono/metadata/culture-info-table.hにたどり着きます。 ものすごいたくさんの数値やバイトコードが列挙された配列がありますが、怖くはなくて、cultureIDに紐づくCultureInfoやRegionInfoに相当する値が入っている配列のアドレスが列挙されているとかそんな感じです。つまり定義済みのCultureInfoやRegionInfoにアクセスしているということです。ちゃんと読み進めると、各要素がISOCurrencySymbolだったりに紐付いている事が分かります。なるほどーためになる。

<< 追記 >>

Atsushi Enoさんから捕捉頂きました。ありがとうございますm(_ _)m

ちなみにculture-info-table.hはmono/tools/locale-builder でUnicode CLDRから生成されているので、そっちを見るともっと人にやさしいです。
とのことです。実際にlocale-buiilderを読んでみると、C#で書かれていて、culture_info_entryやregion_info_entryのソースコードがモリモリ生成されていたりするのが分かります。とても人にやさしいですね!

今回コードリーディングの対象にしたCultureInfoですが、偶然にもstaさんが詳細にまとめてくれていました。 CultureInfoの詳細はこちらの記事を読むと良いです。ありがとうございますm(_ _)m

<< 追記おわり >>

ここで、例えばUnity-Technologiesのmonoと、本家のmonoとでdiffを取ったりして、あーここは対応が追いついてないみたいだから、最新のmonoの実行結果からCodeDomを用いてソースコードを自動生成しておこうとかそういった行為がとれるわけですね。「いやあのそれはソースコード読まなくても普通に実行すれば分かるでしょ」というのもありますが、この記事を読んだ皆さんは、mono環境におけるInternalCallの壁を超えたようなものなので、実行する以外のアプローチがとれるようになって皆さんの業務の幅が広がって私はとても嬉しく思います。

続けて、mcs、miniの実装のコードにも触れようかという所なんですが、筆者都合により本記事はここまでです。 引き続き、mcsやminiのソースコードを読む際には、一度Mono Documentationやmonoのリポジトリの各docsディレクトリにあるテキストに目を通してからの方が気持ち理解が早いかなと思います。以上です。

11日目はmcsの最新情報と今後の展望についてです。とても気になります。

Comments