.NETにはアセンブリ(クラスの実行部分を記述した物、いわばプログラム、多くの場合DLL形式)を実行時動的に読み込み、プログラム実行に使用する機能があります。よく使われるケースがプラグイン。プラグインファイルの中身が.NET形式のDLLになっていて、プログラム起動時に読み込み実行します。
で、今回来たミッション。
- サーバからオブジェクトのシリアライズされたデータが来るので、クライアントで逆シリアライズする
- ただしそのデータに対応するアセンブリがクライアントに組み込まれているとは限らない。無い場合はサーバにおいてあるから、適当に見つけて動的に読み込む。
プラグインファイルがサーバにあるって感じでしょうか。サーバから取ってくるアセンブリはファイルではなく、バイト配列(byte[])。中身は元々ファイルだったDLLそのままなのでローカルでそのバイト列をファイルに書き込んでから読むと言う方法もありますが、直接バイト列から読みこむSystem.Reflection.Assembly.Load(byte[])なんてのもあるのでそちらを使用。
が、アセンブリを読み込んだ後逆シリアルを行おうとすると、SerializationException。メッセージは"Unable to find assembly 'hoge'"つまり読み込んだはずのアセンブリが無かったことにされてしまっている。はて?
とりあえず見つけた解決法は以下のもの。
- .NET Frameworkはアセンブリが見つからないとき、AppDomain.CurrentDomain.AssemblyResolveイベントを投げるので、あらかじめハンドラをセットしておく
- 逆シリアライズ時にこのイベントが捕まるので、引数から必要とされるアセンブリの名前を引き出す
- サーバから該当するアセンブリをダウンロード
- ダウンロードしたデータをAssembly.Load(byte[])で読み込み、このメソッドの返り値を「イベントハンドラの返り値」にする
こうしておくとイベントハンドラから返されたアセンブリを使って逆シリアライズが進みます。
- 試してないけどロードしたアセンブリがさらに別のアセンブリに依存していてそれが必要なら、またイベントが飛んでくるんじゃないでしょうかねぇ。(*1 追記参照)
- 同じアプリケーションドメインである限りは、一度読み込まれたアセンブリに対してこのイベントが再び飛んでくることは無いようです。
- イベントハンドラに返り値があるのかと奇妙に思われるかもしれませんが、あります。C#にとってイベントとデリゲートの違いって、デリゲートは=演算子が使えるくらいでしょうか?
- Deserialization problem in DLL (unable to find assembly) (msdn forum)
- C# - 複数のイベントハンドラをセットしたメソッドを呼び出したときの戻り値 (オノデラの研究日記 in わんくま)
[ 2012/8/23 追記 ] 図らずも(*1)を別の方法で確認。自家製イベントハンドラがアセンブリを取ってくるためにサーバー通信用のアセンブリが必要なのですが、これが無い→通信アセンブリを取るためにAssemblyResolveイベント→同じハンドラで受け取りやっぱり通信アセンブリが無い、の無限再帰呼び出し orz。通信アセンブリはGACから取れるはずなのですが、もしかしてこのイベントの方が優先度上なの?
Comments