アジョブジ星通信

進捗が出た頃に更新されるブログ。

ILSpyのエンジンで逆コンパイルしよう

高校三年生になりました。降参です。以上です。

とかいう話をしましたが、あのあともはや完全に 1 から作りなおしに近い形で大幅アップデートしました(といってももう 1 週間以上前の話ですが)。あの記事で挙げたこれからの予定ですが、まさかの「アイコンほしい」以外をすべて実現してしまいました。その中でもイチオシ機能はアセンブリブラウザの実装です。

我ながら便利すぎて惚れてまうんですが、この C# コードの吐き出しの仕方を紹介します。

ILSpy のソースコードを入手

コンパイルには ILSpy 内にある ICSharpCode.Decompiler を使いました。これ単体でも十分使えるライブラリなのですが、残念ながら単体で NuGet に公開されていたりはしないので、 ILSpy をダウンロードして dll を取得するか、ソースコードをダウンロードしてくる必要があります。

NRefactory と Mono.Cecil に依存しているのでそれらも忘れないでください。

いざ逆コンパイル

なんとなく CoreTweet.Streaming.StreamingApi でやってみました。

using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Ast;
using Mono.Cecil;

var modDef = ModuleDefinition.ReadModule("CoreTweet.dll");
TypeDefinition typeDef = modDef.GetType("CoreTweet.Streaming.StreamingApi");

var builder = new AstBuilder(new DecompilerContext(modDef));
builder.AddType(typeDef);
builder.RunTransformations();
var output = new PlainTextOutput();
builder.GenerateCode(output);
Console.Write(output.ToString());

Cecilアセンブリを読み込み、それを ICSharpCode.Decompiler.Ast.AstBuilder に入れてやることで C# コードが生成されます。結果はこんな感じになります。

using CoreTweet.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
namespace CoreTweet.Streaming
{
	public class StreamingApi : ApiProviderBase
	{
		protected internal StreamingApi(TokensBase tokens) : base(tokens)
		{
		}
		private IEnumerable<string> Connect(StreamingParameters parameters, MethodType type, string url)
		{
			using (HttpWebResponse httpWebResponse = base.Tokens.SendStreamingRequest(type, url, parameters.Parameters))
			{
				using (StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
				{
					foreach (string current in 
						from x in streamReader.EnumerateLines()
						where !string.IsNullOrEmpty(x)
						select x)
					{
						yield return current;
					}
				}
			}
			yield break;
		}
		public IEnumerable<StreamingMessage> StartStream(StreamingType type, StreamingParameters parameters = null)
		{
			if (parameters == null)
			{
				parameters = new StreamingParameters(new Expression<Func<string, object>>[0]);
			}
			string url = (type == StreamingType.User) ? "https://userstream.twitter.com/1.1/user.json" : ((type == StreamingType.Site) ? " https://sitestream.twitter.com/1.1/site.json " : ((type == StreamingType.Filter) ? "https://stream.twitter.com/1.1/statuses/filter.json" : ((type == StreamingType.Sample) ? "https://stream.twitter.com/1.1/statuses/sample.json" : ((type == StreamingType.Firehose) ? "https://stream.twitter.com/1.1/statuses/firehose.json" : ""))));
			IEnumerable<string> enumerable = 
				from x in this.Connect(parameters, (type == StreamingType.Filter) ? MethodType.Post : MethodType.Get, url)
				where !string.IsNullOrEmpty(x)
				select x;
			foreach (string current in enumerable)
			{
				StreamingMessage streamingMessage;
				try
				{
					streamingMessage = StreamingMessage.Parse(current);
				}
				catch
				{
					streamingMessage = RawJsonMessage.Create(current);
				}
				yield return streamingMessage;
			}
			yield break;
		}
	}
}

yield がちゃんと再現されてるのが感動しますね。

AstBuilder.DecompileMethodBodiesfalse にすると、 NuGetCalc Web でやっているように、メンバー一覧機能として利用できます。これは MonoDevelopアセンブリブラウザで「サマリー」を指定したときに使われているものです。

そのほか、 DecompilerContext.Settings で出力コードのスタイルを変更できます。

XML コメントを表示する

これは ICSharpCode.Decompiler では提供されていない機能なので、 ILSpy 自体のソースコードから取ってきます。必要なのは以下 3 ファイル

そして builder.RunTransformations(); のあとに

ICSharpCode.ILSpy.XmlDoc.AddXmlDocTransform.Run(builder.SyntaxTree);

を追加します。これだけ。これで ModuleDefinition の情報から XML ファイルを探しに行って AstBuilder に追加してくれます。

実行結果も書いておきたいところでしたが、 mono の XmlTextReader のバグによりぬるりで落ちるので書けませんでした。はい。 NuGetCalc Web では ReferenceSource に移行したバージョンを使っているのでちゃんと動いています。

まとめ

NuGet パッケージ化頼む。ていうか、てさ部何やってるの。