ブラウザ上で動作する.NETデコンパイラ

少し前にブラウザ上で動作する.NETデコンパイラ **[Kani](https://yaegaki.github.io/Kani/)** を作成したのでその紹介です。
ブラウザさえあれば簡単に試すことができます。

![Kani.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42193/20d335dc-9056-fdbd-6544-9fcff77c9e3a.gif)

PWAに対応しているのでメニューからインストールすることでローカルで動作させることも可能です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42193/c7c5d0a7-e6fb-ac9f-f9e4-bdc78c2fab53.png" width=50%>

ソース:[yaegaki/Kani](https://github.com/yaegaki/Kani)

## 解説

Kaniでは**Blazor**と**dnSpy**を内部で使用しています。
これらを使用することで簡単にブラウザ上で動作する.NETデコンパイラを作成することができました。

### Blazorについて

**Blazor**はブラウザ上で.NETアプリを動作するためのフレームワークです。
以下の記事がqiitaでも人気になっていたので知っている人も多いのではないでしょうか。

[C# で Single Page Web Application が書ける Blazor が凄かった件](https://qiita.com/jsakamoto/items/20d4893f6c8cdb0356f6)

**Blazor**の完成度は結構高く特に意識することなくC#で書いたコードが動作しました。

### dnSpyについて

**dnSpy**はWindows向けの.NETデコンパイラでとにかく完成度が高いです。

[凄すぎて大草原不可避な.NET デコンパイラdnSpyを使ってみる](https://qiita.com/Tokeiya/items/54fbf30cb21c77c05c41)

**dnSpy**自体はWindows向けのアプリケーションですがすべてがC#で書かれています。
なのでGUIなどのWindowsに依存する部分を除けば**Blazor**で動かすことができるようになります。
幸いなことに**dnSpy**は機能ごとにプロジェクトが分かれており簡単に再利用できました。

## 苦労したポイント

**2019/7/9時点での情報です**

### Blazorアプリのビルドに失敗する

Windows上でBlazorアプリをビルドすると特におかしいところがなくてもエラーになる場合がありました。
エラーログは以下のようなものでした。

```
  Unhandled Exception: Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly: 'C:\Users\xx\.nuget\packaes\system.threading.tasks.parallel\4.3.0\lib\netstandard1.3\System.Threading.Tasks.Parallel.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' ---> Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly: 'C:\Users\xx\.nuget\packaes\system.threading.tasks.parallel\4.3.0\lib\netstandard1.3\System.Threading.Tasks.Parallel.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
     at Mono.Linker.DirectoryAssemblyResolver.Resolve(AssemblyNameReference name, ReaderParameters parameters)
     at Mono.Linker.AssemblyResolver.Resolve(AssemblyNameReference name, ReaderParameters parameters)
     at Mono.Linker.LinkContext.Resolve(IMetadataScope scope)
     at Mono.Linker.LinkContext.Resolve(IMetadataScope scope)
     at Mono.Linker.LinkContext.Resolve(String name)
     at Mono.Linker.Steps.ResolveFromAssemblyStep.Process()
     at Mono.Linker.Steps.BaseStep.Process(LinkContext context)
     at Mono.Linker.Pipeline.ProcessStep(LinkContext context, IStep step)
     at Mono.Linker.Pipeline.Process(LinkContext context)
     at Mono.Linker.Driver.Run(ILogger customLogger)
     at Mono.Linker.Driver.Execute(String[] args, ILogger customLogger)
     at Mono.Linker.Driver.Main(String[] args)
```

内容的にはアセンブリの解決ができなかったというものですが**パスをよく見たら何かおかしい**です。
本来ならばnugetのパッケージのパスは`C:\Users\xx\.nuget\packages\...`というものですが`C:\Users\xx\.nuget\packaes\...`となっています。
(`packages`が`packaes`になっている)
詳細はわかりませんがおそらく引数が長くなりすぎて限界を超えてしまったため、一部の情報が欠落してしまったのだと思います。

[コマンド プロンプト (Cmd.exe) へ 8192 文字以上の引数を渡した場合に発生する現象](https://support.microsoft.com/ja-jp/help/2823587)

根本的にはどうやって解決するべきかわからなかったため参照するアセンブリの数を減らすという対策をとりました。
dnSpyは多言語に対応しておりリソースファイルが多かったのでその数を減らしました。

https://github.com/yaegaki/dnSpy/commit/d2fcbcdf96cfad23f8e093caffe63560490a7535

### ファイルのドラッグドロップについて

**Kani**は.NETアセンブリをドラッグドロップすることでデコンパイルを行います。
このドラッグドロップを実装するために少し苦労しました。
ブラウザ上にファイルをドラッグドロップしてそのバイナリを得るためには`dragover`と`drop`で発生するイベントについて`preventDefault`を呼び出す必要があります。
**Blazor**のバインド機能でイベントを設定した場合、**どうやっても`preventDefault`を呼ぶことができず**ページ遷移が発生してしまいました。
これを回避するためにはjsで処理を書く必要があり、C#だけで完結できずに少し残念でした。

また、jsから`Uint8Array`をC#に渡す方法がよくわからず最終的には**`blob`にして`blobURL`を発行しC#側でHTTP.GETを行う**という回りくどい方法をとりました。
これについてはもう少しまともな方法があるかもしれません。


## 最後に

**Blazor**と**dnSpy**が凄すぎて感動しました。:sparkles:
特に**Blazor**は色々可能性を感じました。:zap:
適当なC#アプリをブラウザに移植してみるのも楽しいと思いますのでぜひやってみてください。:thumbsup: