2011年12月17日

アセンブリを動的にロードし、そして完全にアンロードする

   このエントリーをはてなブックマークに追加 Clip to Evernote
このエントリは、C# Advent Calendar jp 2011 への参加記事です。



アセンブリを動的にロードし、利用し終わったらメモリからアンロードしたいという要求って、どれくらいあるのか
わかりませんが、今回は、そのやり方を説明します。

これって簡単なようでなかなか面倒です。残念ながら、Assembly クラスには、Load系のメソッドはありますが、
Unload系のメソッドがありません。

こんな時に AppDomainを使うと、要求を満たすことができます。

まずは、以下のようなクラスが、SampleLibraryアセンブリに定義されていたとします。


この SampleClassがあるSampleLibraryをロードし、SampleClass.Executeを呼び出し、
終わったら、アセンブリをアンロードする例を考えてみましょう。

簡単に説明すると、以下の手順を踏みます。

1. 新しくアプリケーションドメインを作成する。

2. そのアプリケーションドメイン内にプロキシ―クラスをロードする

3. プロキシ―クラス内で、目的のアセンブリ(SampleLibrary.dll)をロードする。

4. ロードしたアセンブリの機能 (SampleClass.Execute)をプロキシ―経由で呼び出し処理をする。

5. アセンブリがロードされたアプリケーションドメインをアンロードする。

では、順に見ていきましょう。

1. 新しくアプリケーションドメインを作成する。

ここでは、”SampleDomain" という名前のアプリケーションドメインを作成することとしましょう。
コードを示します。



2. そのアプリケーションドメイン内にプロキシ―クラスをロードする

次に、このアプリケーションドメインにプロキシ―クラスをロードします。
このProxyクラスは、実行されているアセンブリと同じアセンブリに存在するものとします。
Proxyクラスのコードは、後述します。

ここで重要なことは、AppDomain のCreate系のメソッドを利用し、直接、SampleClass のインスタンスを
作成してしまうと、ロードされるSampleLibrary アセンブリは、デフォルトアプリケーションドメインと
"SampleDomain" アプリケーションドメインの両方にロードされてしまうということです。

そのため、appDomain をアンロードしても、SampleLibrary をアンロードすることができません。

まずは、"プロキシークラス" をロードし、そのプロキシークラスの中で、型情報の取得対象である
目的のアセンブリをロードする必要があります。


このコードは、現在実行されているアセンブリ(Assembly.GetExecutingAssembly())に定義されて
いるProxyクラス(type.FullName)のインスタンスを生成するというコードになります。
これで、Proxyクラスのインスタンスが、SampleDomain アップドメイン内に作成されます。


3. プロキシ―クラス内で、目的のアセンブリ(SampleLibrary.dll)をロードする。

プロキシ―クラスは以下のように記述します。


この時、"プロキシークラス" は、MarshalByRefObject を継承する必要があります。

また、"プロキシークラス" とやり取りするパラメータは、

MarshalByRefObject で継承

[Serializable] 属性の付与

のどちらかを満たさなくてはなりません。今回のパラメータは、System.Stringなので、
[SerializableAttribute] が付与されています。


4. ロードしたアセンブリの機能 (SampleClass.Execute)をプロキシ―経由で呼び出し処理をする。

次に示すコードで、Proxyオブジェクト経由で、SampleClass.Execute メソッドを呼び出します。



5. アセンブリがロードされたアプリケーションドメインをアンロードする。

次のコードでアプリケーションドメインをアンロードします。


これで、アプリケーションドメインと一緒に、動的にロードしたアセンブリもアンロードされるこ
とになります。



それでは、Proxyクラスを経由して、ロード、呼び出し、アンロードする一連のコード例を示します。


例えば、上記コードを WindowsFormsのボタンクリックで実行するとします。
この場合、ボタンクリックの処理が終わると、SampleLibrary.dll をエクスプローラーから
削除することができます。

一方、AppDomain.CreateInstanceAndUnwrap で直接 SampleClassのインスタンスを生成する以下の
ようなコードでは、ボタンクリックの処理が終わっても、SampleLibrary.dll が使用中になり、
エクスプロ―ラーから削除することができません。


  注意:
   このコードを実行するには、SampleClassは、MarshalByRefObject を継承するか、
    [Serializable] 属性の付与する必要があります。

上記のソースコード上は、AppDomainの配下に、SampleLibrary.SampleClass をロードしている
ように見えますし、dynamicを使い、SampleClassはソースコード上には現れないようにしていいます。
しかし上記コードを実行するには、SampleClassの情報が必要なので、このコードが実行されるデフォ
ルトのアップドメインにも、SampleLibrary.SampleClass がロードされてしまいます。
そのため、生成した AppDomainをアンロードしても、SampleLibrary.SampleClassは残ってしまうという
ことです。

これを確かめるには、AppDomain.Unload(appDomain); を実行後に、


とDLLを削除するコードを追加します。すると、アクセスが拒否されて削除することはできないことが
確認できます。

一方、正しくアンロードできるコードでは、DLLファイルを削除することが可能です。




最後に、もう一つのやり方を示します。

それは、プロキシ―クラスを作成するのではなく、インターフェースを使う方法です。
この方法は、ロードしたいアセンブリのソース(今回の例では、SampleClass)に手を加えることが
できないと使えません。
つまり、他から購入したライブラリなどをアンロードしたいときには利用できないということです。

では、そのやり方を示します。
まず、新たなクラスライブラリ・プロジェクトを作成し、


というインターフェースを定義します。
次に、SampleClassを以下のように変更します。
この場合は、MarshalByRefObject から継承させる必要があります。


そして、これを呼び出す側は、


と ISampleインターフェース経由で Execute メソッドを呼び出すようにすれば、このアップドメイン
には、SampleLibraryは読み込まれないので、ロードしたアセンブリをアンロードすることができます。



実はこの記事の下書きはずいぶん前に出来上がっていたものです。なかなか完成までこぎ着けることが
できずに、そのまま放置していたのですが、C# Advent Calendar 2011 へ参加するために、公開できる
レベルになんとかしました。ですから、内容的に古い情報が含まれているかもしれません。
そういった箇所があればコメントしていただければと思います。

  

Posted by gushwell at 20:50Comments(0)TrackBack(0)

2010年09月17日

Enum.GetValues で列挙値を列挙

   このエントリーをはてなブックマークに追加 Clip to Evernote

という enum に対して、


と表示したいときに、どんなコードを書きますか?
以下、とあるところで見つけたコード。


ネットにはこんなコードが溢れていますが、
目にしてしまうと、書き変えなくては気が済まない悪い癖がでてしまいました。
※ あえて、URLを載せるのは控えさせていただきます。

僕なら、こんな風に書きます。


  
Posted by gushwell at 23:17Comments(2)TrackBack(0)

2010年08月08日

WNetAddConnection2W

   このエントリーをはてなブックマークに追加 Clip to Evernote

ちょっとした興味から、ネットワークドライブ接続で利用しているAPIを
WNetAddConnection2A からWNetAddConnection2W に変更しようとしたが、 どうもうまくいかない。

ネットで検索しても、WNetAddConnection2A のコードばかり。

結局、WNetAddConnection2の引数である NETRESOURCE 構造体の定義で

と、CharSetに CharSet.UniCOde を設定すれば大丈夫だった。


の定義にばかり目がいってしまい、構造体の定義に問題があるとは 思わなかった。

  
Posted by gushwell at 23:40Comments(0)TrackBack(0)

2010年07月11日

実行中のアセンブリのパスを得る

   このエントリーをはてなブックマークに追加 Clip to Evernote


で、現在実行中のコードを格納しているアセンブリのパスを得ることができます が、このcodeBaseは、

file:///C:/Sample/bin/Debug/CodeBaseSample.EXE

といった形式で、Uri形式の文字列です。
これを

C:\Sample\bin\Debug\CodeBaseSample.EXE

と通常のローカルパスの形式に変換するには、


と、Uri.LocalPathプロパティを使います。
  
Posted by gushwell at 23:58Comments(2)TrackBack(0)

2010年06月30日

起動したプロセスの終了を検出する

   このエントリーをはてなブックマークに追加 Clip to Evernote

Processオブジェクトの EnableRaisingEvents を true にすると、 Exited イベントで、プロセスの終了を検出することができます。



イベントハンドラのsender引数には、そのプロセスの Processオブジェクトが 渡ってきますから、
そこから、各種情報を取得することが可能です。
ただ、プロセスは終了してしまっているので、参照できないものもあります。

  
Posted by gushwell at 23:31Comments(0)TrackBack(1)

2010年03月17日

ApplicationSettingsBaseクラス

   このエントリーをはてなブックマークに追加 Clip to Evernote
構成設定ファイルを扱う場合に、ApplicationSettingsBaseクラスって、とても良くできたクラスだと思うんだけど、
UserScopedSettingAttribute ではなく、ApplicationScopedSettingAttribute を付加した場合って、書き込みが出来ないんですね。

ApplicationScoped でも、設定ファイルに書き込みがしたいって要求はないのかな?
LocalFileSettingsProvider ではなく、自分で新たなプロバイダを書けばいいのだけれど、結構面倒そうだ。

それと、バージョン番号が違うと別のフォルダに保存してしまうという動作を変更できないのは、どうなんだろう
Upgradeメソッドがあるから、大抵は別フォルダにあっても問題ないのだろうけど、自分でバージョンコントロールを行いたいときにそれを変更する手段が無いのは困る。
こちらは、独自プロバイダーを書いても対応できないようだ。

それと、保存先を決定するバージョンが、アセンブリのバージョンっていうのも戸惑った。
ApplicationScoped の項目だけレジストリに書くような拡張をしようと思って、壁に突き当たった。
だって、Application.CommonAppDataRegistryで採用しているバージョンはProductVersionなんだもの...

結局、ApplicationSettingsBaseから派生させるのではなく、 ConfigurationSection から独自のSectionクラスを派生させて、 ConfigurationManager をラップした独自のApplicationSettingsクラスを 書いたほうが良さそうな気がしてきた。
  
Posted by gushwell at 23:27Comments(0)TrackBack(0)

2010年02月24日

Clipboard.GetData(DataFormats.Html)で文字化け

   このエントリーをはてなブックマークに追加 Clip to Evernote
クリップボードからHTMLを取得しようと、以下のようなコードを書いたら、 日本語部分が文字化けしてしまいました。


文字化けを防ぐには、次のように、MemoryStream経由して Encodingの指定をしてHTML を取得する必要があります。


でも、


とか


だとダメです。MemoryStreamに変換できません。
これってバグですか?   
Posted by gushwell at 23:28Comments(0)TrackBack(0)

2010年02月16日

RealProxyでthis経由のメソッド呼び出しはフックできない

   このエントリーをはてなブックマークに追加 Clip to Evernote
System.Runtime.Remoting.Proxies.RealProxyを使うと、メソッドの呼び出しをフックできるんですが、 あくまでも透過プロキシ経由で呼び出した場合に限られます。
なので、Privateメソッドは、フックできないんですね。
当然、publicメソッドであっても、自分自身のクラスから呼ばれた場合は、 フックできません。
冷静になって考えれば当たり前なのですが、以前、これで嵌まりました。
思いだしたので書いておきます。

  
Posted by gushwell at 22:16Comments(0)TrackBack(0)