- PR -

COMオブジェクトの廃棄時に・・・

投稿者投稿内容
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-05-17 02:49
マネージクラスでSystem.Runtime.InteropServices.ComTypes.IStreamを実装して、
COMクライアントにIStreamを渡そうとしています。
IStreamの中身(?)はWebのリソース(NetworkStream)なので、
使い終わったらすぐに開放したいと思っています。

COMのIStreamはIUknown::Releaseでリソースを開放するのが普通だったと思い、
マネージクラスでもそう実装しようと思ったのですが、
IUknown::Releaseはマネージでは実装できないようなのです。

COMに渡したIStreamの終了を知る方法をご存知ないでしょうか?
参照カウントを調べたりFinalizeで通知するのを作ってみたのですが、
シンプルにできません。

普通のCOMオブジェクトはReleaseを上書きできなくても
あまり問題にならないのかもしれませんが、
IStreamなど一部のオブジェクトではReleaseを上書きできないと
困ることが多々あると思うのですが・・・
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2007-05-17 11:41
こんにちは。

VS2003で少しテストをしてみましたが、
マネージ側で作ったCOMオブジェクトの参照カウンタはだたインクリメント・デクリメントされているだけのようですね。
参照カウンタが0になっても、オブジェクトは破棄されていないようです。

テスト用COMクライアントソース(C++)
コード:
extern "C"
__declspec(dllexport) void WINAPI TestStream(IStream* pStream)
{
	// 引数にはマネージで作ったCOMオブジェクトが渡される

	printf("Enter TestStream\\n");

	printf("pStream = 0x%X\\n", reinterpret_cast<long>(pStream));

	// 参照カウンタのチェック
	ULONG refCount;
	pStream->AddRef();
	refCount = pStream->Release();
	printf("refCount = %d\\n", refCount);

	// 強制的にRelease
	refCount = pStream->Release();
	printf("refCount = %d\\n", refCount);

	// さらににRelease
	refCount = pStream->Release();
	printf("refCount = %d\\n", refCount);

	printf("Leave TestStream\\n");
}



結果は
コード:
Enter TestStream
pStream = 0x325002C
refCount = 1
refCount = 0
refCount = -1
Leave TestStream



マネージ側の作法でリソースを破棄するのが妥当のような気がします。

コード:
[ClassInterface(ClassInterfaceType.AutoDual)]
public class NetworkStream : IStream, IDisposable
{
}


れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-05-17 18:05
引用:

Tdnr_Symさんの書き込み (2007-05-17 11:41) より:
マネージ側で作ったCOMオブジェクトの参照カウンタはだたインクリメント・デクリメントされているだけのようですね。
参照カウンタが0になっても、オブジェクトは破棄されていないようです。



マネージ側で作ったCOMオブジェクトの参照カウンタは
0を返すまでReleaseを読んだ後さらにReleaseを返すと何回読んでも-1を返すようです。

MSDNによると、参照カウンタが0になるとCCWが破棄されるようで、
マネージ側でオブジェクトの参照を保持していない場合、
参照カウンタが0になって、次の次くらいのGCでオブジェクトが破棄されるようです。
たぶん、こんな具合かと。

参照カウンタ0 -> CCWが破棄される -> マネージオブジェクトが破棄される

その際、IDisposableを実装していても、Finalizeしか呼ばれません。
もちろん、マネージ側でオブジェクトの参照を保持してると、Finalizeも呼ばれません。

COM時代は参照カウントの管理で大変でしたが、
マネージとの相互運用では参照カウントをいじる必要がなくて、
便利だなぁと思っていたのですが、
IStreamやIStorageを使うとこれではまったくうまくいかないのです。

IStreamやIStorageはファイルやデータベース接続に使うので、
使い終わったらすぐに破棄する必要があると思うのですが、
これに普通IUnknown::Releaseを使うのです。

マネージからCOMにIStreamやIStorageを公開すると、
Releaseが呼ばれたか判断できないので、
それはつまりマネージ側ではオブジェクトを破棄していいか判断できない、
ということになってしまうのです。

引用:

マネージ側の作法でリソースを破棄するのが妥当のような気がします。



マネージ側でオブジェクトの使用終了を判断できれば
Disposeが呼べるのですが、
Releaseが呼ばれたかどうかを調べられないと
それもできないのです。

閉じてないStreamの参照がGCされずに大量に残ってるのは
気持ち悪くて気持ち悪くて…
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2007-05-17 18:37
こんばんは。

引用:

れいさんの書き込み (2007-05-17 18:05) より:
マネージ側でオブジェクトの使用終了を判断できれば
Disposeが呼べるのですが、
Releaseが呼ばれたかどうかを調べられないと
それもできないのです。



ああ、なるほど。れいさんの抱えている悩みがようやく理解できました。
COMクライアント側(アンマネージ側)からどれだけ参照されているか分からないから、
Disposeも呼び出せないんですね。

参照カウンタを調べるだけなら出来なくもなさそうですが、
参照カウンタが0になったタイミングというかイベント/コールバックがないですかね?
れいさんのおっしゃるとおりReleaseを自分で実装できれば一番良さそうですが。

れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-05-17 21:20
Tdnr_Symさんこんばんは。

引用:

参照カウンタを調べるだけなら出来なくもなさそうですが、
参照カウンタが0になったタイミングというかイベント/コールバックがないですかね?
れいさんのおっしゃるとおりReleaseを自分で実装できれば一番良さそうですが。



今は参照カウントを定期的にしらべて廃棄しています。
イベントもコールバックも見当たらなくってお手上げです。

どなたかうまい方法ご存知ないでしょうか…
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2007-05-19 09:49
.NET 1.x 時代に同じ問題で悩みましたが、結局、当時は有効な解決策はみつけられませんでした。

.NET 2.x でも COM 互換層まわりはそれほど変わっていないはずので、現時点でも望み薄な気がします。

もし今後も同じ問題に取り組むようなら、MSDN Froum などで .NET の COM 互換性に関する不備として報告するのをお勧めします。

内容的に hotfix 等の提供は無さそうに思いますが、.NET の将来のバージョンで改良される可能性が高まります。
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-05-21 15:06
引用:

渋木宏明(ひどり)さんの書き込み (2007-05-19 09:49) より:
.NET 1.x 時代に同じ問題で悩みましたが、結局、当時は有効な解決策はみつけられませんでした。

.NET 2.x でも COM 互換層まわりはそれほど変わっていないはずので、現時点でも望み薄な気がします。



あれからいろいろ探しましたが、Releaseの瞬間を知る方法は見つかりませんでした。
私は不可能と結論しました。
最良の方法は参照カウンタをMarshal.AddRefとMarshal.Releaseで定期的に調べて、0になったら廃棄するという手です。
当面はこれで我慢します。

エクスプローラーにデータをドロップしてファイルを作成したかっただけなんですが、.Netでサポートされていないのはこの辺の事情があったのかもしれません。

引用:

もし今後も同じ問題に取り組むようなら、MSDN Froum などで .NET の COM 互換性に関する不備として報告するのをお勧めします。

内容的に hotfix 等の提供は無さそうに思いますが、.NET の将来のバージョンで改良される可能性が高まります。



hotfixするほど大きな問題でもないですし、COMは捨てる方向で動いているようですので、将来の改良も可能性は少なそうですね。

報告めんどくさいなぁ…
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2007-05-21 16:41
引用:

最良の方法は参照カウンタをMarshal.AddRefとMarshal.Releaseで定期的に調べて、0になったら廃棄するという手です。
当面はこれで我慢します。



あまり良い手ないのは承知のことと思いますが、まぁ仕方の無いところではありますね。

ちなみに、COM オブジェクトを作る上で .NET のライブラリが使いたいだけならば、COM オブジェクトの「ガラ」は従来どおり C++ で書いて、内部実装で C++/CLI 経由で .NET の機能呼び出しを行うのがスマートと思います。

引用:

COMは捨てる方向で動いているようですので、将来の改良も可能性は少なそうですね。



COM の全廃には相当時間が掛かると思いますが、何もアクションをとらないと改良される可能性はかなり低いですね。

引用:

報告めんどくさいなぁ…



MSDN Forum なら、ここのリンクを貼り付けて「ATL の FinalRelease 相当のタイミングでユーザコードが実行できないと困る場合がある」ということだけ示せば十分だと思いますよ。

スレッドを立ててもらえれば、私の方でほんの少しだけ後押しすることは可能です。


[ メッセージ編集済み 編集者: 渋木宏明(ひどり) 編集日時 2007-05-21 16:41 ]

[ メッセージ編集済み 編集者: 渋木宏明(ひどり) 編集日時 2007-05-21 16:42 ]

スキルアップ/キャリアアップ(JOB@IT)