夕暮ログ

C#やJavascript、最近はAndroidなんかも好きなtinqのブログ。「夕暮れログ」

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

ILSpy(3)逆コンパイルの結果

ILSpyのインストール法はこちらの記事を、基本的な使い方はこちらの記事を参照してください。

今回は、実際に逆コンパイルします。
たとえば、OpenFileDialogの中身を見てみましょう。

OpenFileDialogは、System.Windows.Forms内にあります。
まず、System.Windows.Formsを参照に追加します。[File]=>[Open from GAC]でダイアログを開きます。
一覧が表示された、上のテキストボックスに、「System.Windows.Forms」と入力します。
そうすると、図のように一気に絞り込まれます。

バージョン違いがいくつもありますが、とりあえず最新版、.NET4のものを指定しておきましょう。
すると、ツリービューにSystem.Windows.Formsが出現します。
そしたら、その中でOpenFileDialogを探しますが、数が多いので、またツリービューの上のテキストボックスに、「OpenFileDialog」と入力します。
そして、System.Windows.Formを開き、System.Windows.Forms名前空間を開くと、中にOpenFileDialogが登場します。

それを選択します。すると、右側にコードが表示されます。
このままでは、中のメソッドなどが表示されないので、先ほど指定した絞込みを解除しておいてください。

コードを見ると、CommonDialogで必要な、RunDialogがありません。変わりに、RunFileDialogがあります。
それを展開すると、NativeMethods.OPENFILENAME_Iという構造体を受け取るメソッドだとわかります。そして、その中では、GetOpenFileNameを呼びさしています。では、ここはどこから呼び出されているのでしょうか
それは、OpenFileDialogの親クラス、「System.Windows.Forms.FileDialog」です。
FileDialog.RunDialogOldなどを見ると、Win32API用の、OPENFILENAME構造体を初期化したりしているのがわかります。
もし、自分でOpenFileDialogを呼び出したいと思ったときに、このようなコードをそのまま利用できるのです。もちろん、著作権などもありますが、ここら辺は有用に使ってもかまわないでしょう。

他にも、気になった処理を探してみてください。
後の記事で、役に立ちそうなメソッドをいくつか紹介する予定です。

自分のコードを逆コンパイル

GACのアセンブリーの中身をのぞくのもいいですが、実際のコードとどのくらい同じものができるのでしょうか。
実際にコードを書いて、確認してみます。
以下は、知る人はいないであろう、Hello World!です。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}
このコードをビルドしてできた実行ファイルを読み込みます。
ツールバーのOpenボタンか、[File]=>[開く]をクリックして、ファイルを開くダイアログを出現させます。
そして、先ほどビルドしたプログラムを選択します。 そうすると、ツリービューにConsoleApplication1が追加されます。
このとき、System.Xmlなどのそのアセンブリーが呼び出している周辺のライブラリも読み込まれます。 その中のConsoleApplication1名前空間のなかの、Programをクリックしてみてください。
左側に、以下のコードが表示されます。
using System;
namespace ConsoleApplication1
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			Console.WriteLine("Hello World!");
		}
	}
}
最適化によって、使われなかったusing命令が消去されていますが、まったく同じになりました。

少し複雑に

もう少し、複雑なことをやってみます。以下は、BuzzFizz問題の答えです。
3項演算子のテストを兼ねているため、最短どころか、へんてこなプログラムになっているので、本当の回答がほしい方は別のサイトへお願いします
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 100; i++)
            {
                if ((i % 3) == 0 && (i % 5) == 0)Console.WriteLine("FizzBuzz");
                Console.WriteLine( (i%3) == 0 ? "Fizz" : ( (i % 5) == 0 ? "Buzz" : i.ToString() ));
            }
        }
    }
}
逆コンパイルした結果です。
using System;
namespace ConsoleApplication1
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			for (int i = 0; i < 100; i++)
			{
				if (i % 3 == 0 && i % 5 == 0)
				{
					Console.WriteLine("FizzBuzz");
				}
				Console.WriteLine((i % 3 == 0) ? "Fizz" : ((i % 5 == 0) ? "Buzz" : i.ToString()));
			}
		}
	}
}
括弧の位置や数が違いますが、三項演算子まで、ちゃんと復元されました。かなりの精度でできることがわかると思います。

匿名メソッドを使ったプログラム

匿名デリゲートと、ラムダ式を使ったシンプルなプログラムです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread t = new Thread(
                new ThreadStart(
                    delegate
                    {
                        for (int i = 0; i < 100; i++)
                        {
                            Console.WriteLine("Delegate" + i);
                        }
                    }
                )
            );
            t.Start();

            Thread t2 = new Thread(
                new ThreadStart(
                    () => {
                        for (int i = 0; i < 100; i++)
                        {
                            Console.WriteLine("ラムダ式" + i);
                        }
                    }
                )
            );
            t2.Start();
        }
    }
}

匿名デリゲートと、ラムダ式をつかって特に意味もなく、スレッドで0~100を出力します。
逆コンパイルした結果はこうなります。
using System;
using System.Threading;
namespace ConsoleApplication1
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			Thread thread = new Thread(delegate
			{
				for (int i = 0; i < 100; i++)
				{
					Console.WriteLine("Delegate" + i);
				}
			}
			);
			thread.Start();
			Thread thread2 = new Thread(delegate
			{
				for (int i = 0; i < 100; i++)
				{
					Console.WriteLine("ラムダ式" + i);
				}
			}
			);
			thread2.Start();
		}
	}
}
このようになります。なんとなく、同じような雰囲気になります。
このコードでは、元にはあった、ThreadStartが消えています。そのため、実際にコンパイルしようとすると、ParameterizedThreadStartとThreadStartがあいまいだというエラーがでてしまいます。
また、ラムダ式もなくなって、匿名デリゲートとなっています。これは、コンパイル後には匿名デリゲートとラムダ式がまったく同じになるので、区別がつかないためです。
このように、100%再現できるわけではありません。

では、もうひとつ。[File]=>[Option]から、「Decompile anonymous methods/lambdas」のチェックをはずしてみてください。
using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace ConsoleApplication1
{
	internal class Program
	{
		[CompilerGenerated]
		private static ThreadStart CS$<>9__CachedAnonymousMethodDelegate2;
		[CompilerGenerated]
		private static ThreadStart CS$<>9__CachedAnonymousMethodDelegate3;
		private static void Main(string[] args)
		{
			Thread thread = new Thread(new ThreadStart(Program.
b__0)); thread.Start(); Thread thread2 = new Thread(new ThreadStart(Program.
b__1)); thread2.Start(); } [CompilerGenerated] private static void
b__0() { for (int i = 0; i < 100; i++) { Console.WriteLine("Delegate" + i); } } [CompilerGenerated] private static void
b__1() { for (int i = 0; i < 100; i++) { Console.WriteLine("ラムダ式" + i); } } } }
匿名メソッドが別のメソッドとなって、いろいろとわかりにくい名前がついています。
$や、<>などの名前に使えない文字がいろいろと入ったりしているので、もちろんコンパイルできません。
ですが、別になったので、若干すっきりしたようにも感じます。処理を追うときには便利なのかもしれません。

列挙子(yield return)

C#ではforeachで使えるIEnumerableを返すときに、yieldを使って簡単に遅延実行できます。
そのコードはどうなるでしょうか。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var item in Test())
            {
                Console.WriteLine(item);
            }
        }

        static IEnumerable Test()
        {
            yield return "バナナ";
            yield return "リンゴ";
            yield return "メロン";
            yield return "いちご";
            yield return "みかん";
            yield return "ぶどう";
        }
    }
}
これを実行すると、バナナ、メロン・・・と順番に実行されます。これがたとえば関数なら、その処理が表示するときに実行するので、効率的にできるというわけです。
それを逆コンパイルした結果です。
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			foreach (string current in Program.Test())
			{
				Console.WriteLine(current);
			}
		}
		private static IEnumerable Test()
		{
			yield return "バナナ";
			yield return "リンゴ";
			yield return "メロン";
			yield return "いちご";
			yield return "みかん";
			yield return "ぶどう";
			yield break;
		}
	}
}
ちゃんとyield returnが復元されました。
では、[View]=>[Option]から、「Decompile enumerators (yield return)」のチェックをはずしてみてください。
・・・張るのが面倒になるぐらいのややこしいコードになります。
ながったらしいクラスができて、MoveNextでswitchで呼び出されるたびに違う処理をする、という感じです。
こんなの、とても人の手でつくろうとは思いません。これをみると、C#のyield returnのありがたみがわかると思います・・・

LINQ

C#の便利な機能、LINQはどうなるでしょうか。
これが元のコードです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] num = { 2,4,1,5,3,5,7,2,3 };
            var query = from x in num where x > 2 orderby x select x;
            foreach (var item in query)
            {
                Console.WriteLine(item);
            }
        }
    }
}
LINQのクエリ式によって、2より大きい物を小さい順に並べて出力しています。
using System;
using System.Linq;
namespace ConsoleApplication1
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			IOrderedEnumerable orderedEnumerable = 
				from x in new int[]
				{
					2, 
					4, 
					1, 
					5, 
					3, 
					5, 
					7, 
					2, 
					3
				}
				where x > 2
				orderby x
				select x;
			foreach (int current in orderedEnumerable)
			{
				Console.WriteLine(current);
			}
		}
	}
}
違ったコードにも見えますが、よくみればそうではありません。
まず、varによる暗黙的型宣言は失われます。これは、省略した書き方でコンパイル時にはその型の宣言と同じになるので、当然です。
配列がLINQ式の中に入ったのは、コンパイラの最適化によるものでしょう。ここにしか使わなかったので、変数にするより、直接埋め込んだ方が効率的だ、という判定でしょうか。
ちゃんとクエリ式まで再現されています。

それでは、[View]=>[Option]で「Decompile query expression」のチェックをはずしてみてください。
ちなみに、[Decompile anonymuse methods/lambdas]のチェックが外れていると、先ほどのチェックも入れられませんし、わかりにくいコードが出力されてしまいます。
そして、「クエリ式をデコンパイルする」のチェックをはずしたものがこちらです。
using System;
using System.Linq;
namespace ConsoleApplication1
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			IOrderedEnumerable orderedEnumerable = new int[]
			{
				2, 
				4, 
				1, 
				5, 
				3, 
				5, 
				7, 
				2, 
				3
			}.Where((int x) => x > 2).OrderBy((int x) => x);
			foreach (int current in orderedEnumerable)
			{
				Console.WriteLine(current);
			}
		}
	}
}
このように、クエリ式がメソッド式になっています。ここでは紹介しませんが、メソッド式をコンパイルしたものもクエリ式にすることもできます。
ちなみに、メソッド式は、クエリ式と完全等価になります。なので、コンパイル後にどちらだったのかを判断するすべはありません。




コードばかりで、若干わかりにくかったですが、各オプションと実際の逆コンパイル例を紹介しました。
逆コンパイルの結果は、必ずしもそのまま実行できる形になるわけではないので、気をつけてください。
もっと気になったらいろいろ逆コンパイルしてみてください。
次回はたぶん最終回です。役に立ちそうな関数などを紹介します。
関連記事

コメント

ここをクリックしてコメントを投稿

非公開コメント

トラックバック

http://tinqwill.blog59.fc2.com/tb.php/23-147bb99d

« next  ホーム  prev »

プロフィール

tinq tinq(もしくはTinqWill)

Sky  For   Every 改装予定

プログラミングお勉強中の高校生。月一くらいは更新したい

最新記事

カテゴリ

月別アーカイブ

検索フォーム

最新コメント

リンク

最新トラックバック

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。