C#からVisual SourceSafe 6.0a(6.0cも同様)を操作しようとして、_NewEnumの扱い方にちょっと、はまったので記録として書いときます。
VSSでは各ファイルをIVSSItemで表現しています。
これのVersionsプロパティを使うと、そのファイルの履歴(IVSSVersion)が取り出せます。
ただ、Versionsプロパティは直接IVSSVersionを返すわけではなく、IVSSVersionsを返します。
IVSSVersionsは、要するにIVSSVersionのコレクションで、中身は_NewEnumメソッドだけです。
IDLは下記のような感じです。
|
VB6のコードはこんな感じになります。
|
IVSSItem.Versionsプロパティが返すIVSSVersionsに_NewEnumがあるからFor Each で回せるわけです。
C#でも
|
という感じでSSAPI.DLLからラッパーを作れば簡単に呼び出せます。
C#のコードはこんな感じになります。
|
簡単ですね。
ところがです。複数のIVSSItemを操作しようと
|
こんな風にすると、2回目のget_Versions()で「System.Runtime.InteropServices.COMException (0x8004D75C): 履歴の操作が既に実行されています。」という例外が出てしまいます。
いろいろ試してみると、どうやら、IVSSVersion._NewEnumが返すIUnknownをRelease()する前に、再び_NewEnumを呼び出すとこの例外が発生するようです。
ところが、C#ではガーベジコレクタ(GC)によってオブジェクトが解放されるまではRelease()は呼ばれません。
そのために例外が出ちゃうようです。
ただ、やはりCOMが相手のときはGC任せばかりにはしていられないので、ちゃんとSystem.Runtime.InteropServices.MarshalクラスにReleaseComObjectメソッドが用意されています。 このメソッドを使えば、
|
こんな風に、任意の時点で(GC任せにせず)COMをReleaseできます。
ちなみに、参照をなくしておいて(上記の例だとItem = nullとしておいて) GC.Collectメソッド、GC.WaitForPendingFinalizersメソッド、Marshal.ReleaseThreadCacheメソッドなんかを呼び出してみましたが、効果はありませんでした。 誰からも参照されない状態でGCが動けば、オブジェクトが破棄されてReleaseされても良さそうに思ったんですが...(自分が参照してないつもりでも、マーシャラとかどっかで参照してるのかもしれません)
とりあえず、どんな風にラップされているのかtlbimpでできたDLLをildasmで見てみると
|
こんな風になっています。
ということは、IVSSVersions.GetEnumeratorメソッドが返したIEnumeratorをMarshal.ReleaseComObjectすればいいってことになります。
ところが、
|
としてもMarshal.ReleaseComObjectのところで「System.ArgumentException: オブジェクトの型は __ComObject か、または __ComObject から派生しなければなりません。」という例外が出てしまいます。 __ComObjectは、ildasmで探したところ、mscorlib.dll内にあるprivateなクラスです(リファレンスには載っていません)。 privateなので直接キャストするわけにもいきません(キャストできるんだったら、そもそもこんな例外出ないんですが)。
ところで、IVSSVersions.GetEnumeratorメソッドが返すオブジェクトは何者なんでしょうか? GetEnumeratorメソッド自体の戻り値はIEnumeratorですが、IEnumeratorはインターフェースなので、このインターフェースを実装したクラスがあるはずです。 COMの_NewEnumはIUnknownを返しますが、QueryInterfaceしてみるとIEnumVARIANTです。 きっとIEnumVARIANTをラップしたクラスがどこかにあり、それのIEnumeratorインターフェースを返してるはずです。
|
こうすれば簡単にクラス名を確認できます。
実際に試してみると、System.Runtime.InteropServices.CustomMarshalers.EnumeratorViewOfEnumVariantクラスでした。
このクラスはCustomMarshalers.dll内にあるprivateなクラスです(リファレンスには載っていません)。
ildasmで確認するとSystem.Runtime.InteropServices.ICustomAdapterインターフェースとSystem.Runtime.InteropServices.UCOMIEnumVARIANTインターフェースを実装しています。
EnumeratorViewOfEnumVariantクラス自体はprivateなのでどうにもなりませんが、これらのインターフェースはpublicですし、リファレンスにも載っています。
リファレンスを見てみるとICustomAdapter.GetUnderlyingObjectメソッドが使えそうです。
|
こうして確認したところ、ICustomAdapter.GetUnderlyingObjectメソッドが返すobjectは__ComObjectクラスであることがわかりました。
となれば、
|
こうしてやれば、めでたく、_NewEnumが返すIUnknownをReleaseすることができました。