- PR -

[C#] デリゲートをGCの対象から外す方法

投稿者投稿内容
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2007-01-25 13:20
引用:

渋木宏明(ひどり)さんの書き込み (2007-01-25 12:00) より:

あれー? アンマネージドに渡したデリゲートがマネージ側で参照を維持してないとGCで片付けられてしまう、というのは MSDN のどこかに注意書きがあったはずです。

ですが、マネージ側でデリゲートの参照を維持している限り、デリゲートのアドレスが変わったりはしないんじゃないですかね???

移動する前提に立つと、アンマネージにデリゲートを渡すこと自体そもそも無意味な行為になってしまいませんか?


引用:

Tdnr_Symさんの書き込み (2007-01-25 12:23) より:

デリゲートのアドレスがGCによって移動させられることがあるのであれば、
.NET Framework内部はどう対処しているのでしょう?

逆アセンブルすると、以下のようなネイティブへ引き渡されるデリゲートがたくさん定義されているのですが。

コード:
public delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);



なにかアドレスを固定しておく方法がないと、おかしくないでしょうか。




あらためて GCHandleType.Normal の解説を読むと、いまいち必然性が理解できなくなりました。
Alloc(object, GCHandleType.Normal) で GCHandle を確保した場合、固定はされないので動き得る。効果は GC の対象にならないってだけ。でも結局 GCHandle オブジェクトを残しておかない限り Free を呼び出すタイミングは失われるわけで、結局いずれ解放するためにはどこかのフィールドに残す必要がある。となると直接オブジェクトの参照を残せばいい話で、敢えて GCHandle をはさむ必要は一体どこにあるんだろう? とかなんとか。メソッド内で GC させたくないのなら GC.KeepAlive で良いし。

実のところ GCHandle.Alloc さえしてやれば GC の移動の対象からも外されると思い込んでいたんですよねぇ。

MSDN の相互運用マーシャリングにも詳しい解説は無し。フィールドにデリゲートインスタンス置いとけば? 程度。
Marshal.GetFunctionPointerForDelegate メソッドの解説にも、GC の収集については注意していますが移動については触れられていません。
C++/CLI のサンプルコードにはメソッド内で GCHandle.Alloc(delegate)/Free を呼び出したりしてるのもあったり。
// .NET Framework アーキテクトのブログとかに情報があったりするのかしらん?

印象としては、デリゲートインスタンスをフィールドに置いてさえおけば問題なくやってくれるっぽいんですけど。
移動する前提に立つと色々難しい問題が出てきますしねー。
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2007-01-25 13:23
なんとなくさんこうになりそうな…
CallbackOnCollectedDelegate
http://msdn.microsoft.com/msdnmag/issues/06/05/bugbash/default.aspx#S3
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2007-01-25 13:26
ここにデリゲートをマーシャリングする例があります。
http://msdn2.microsoft.com/ja-jp/library/ektebyzx(VS.80).aspx
(C++/CLIの例なのでpin_ptr<>を使っていますけど)

なちゃさんが
>固定されている必要があるのは、アンマネージの世界から直接呼び出されるメソッド実体のアドレス
と書かれていますが、これを見るとデリゲートインスタンスをピンで留めてるんですよね。

デリゲートで参照されている限りは、その参照されているメソッド実体のアドレスは移動しないというルールがCLIの仕様にあるんでしょうか。

[ メッセージ編集済み 編集者: 一郎 編集日時 2007-01-25 13:39 ]
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2007-01-25 13:29
こんにちは。

引用:

なちゃさんの書き込み (2007-01-25 13:13) より:
なんとなく想像で考えると、デリゲートのマーシャリングは、
・デリゲートが示すコールバックを代わりに受け取るメソッドスタブを生成する
・ネイティブコードには上記スタブのアドレスを渡す
・スタブは、対象となるデリゲートを保持していて、デリゲートにデリゲートする
という感じな気もしますが…

まあ、少なくともデリゲートインスタンスが固定されている必要はないわけで。
※固定されている必要があるのは、アンマネージの世界から直接呼び出される
 メソッド実体のアドレスであって、デリゲートインスタンスではない、と

上記は単なる想像であって、実際どういう仕組みになってるのかは知りません。



実は私もそんな想像をしていました。
アンマネージで必要なのは、
メソッドのアドレスであって、デリゲートインスタンスではないよなぁと考えていました。
Blue
大ベテラン
会議室デビュー日: 2005/09/12
投稿数: 230
お住まい・勤務地: 知っている人は知っている
投稿日時: 2007-01-25 13:35
引用:

一郎さんの書き込み (2007-01-25 13:26) より:
ここにデリゲートをマーシャリングする例があります。
http://msdn2.microsoft.com/ja-jp/library/ektebyzx(VS.80).aspx
(C++/CLIの例なのでpin_ptr<>を使っていすけど)


pin_ptrはローカル変数としてしかもてないので、
そのブロックを抜けてもコールバックされるような関数(たとえばSetTimerとか)
では使えないっぽいです。

半年ほど前ですが、SetTimerをC++/CLIで使う例を書きました。
C++/CLIなんだから別にデリゲードを使わなくでも出来るんですけどね。。。
http://hpcgi1.nifty.com/MADIA/Vcbbs/wwwlng.cgi?print+200606/06060058.txt
あっているか自信はないです。
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2007-01-25 14:48
こんにちは。

ここのソースを眺めているのですが、
http://www.microsoft.com/downloads/details.aspx?FamilyId=3A1C93FA-7462-47D0-8E56-8DD34C6292F0&displaylang=en#filelist

デリゲートマーシャラの実装はこの辺なんでしょうかね?

sscli/clr/src/vm/marshaler.h(3485):
コード:
class DelegateMarshaler : public ReferenceMarshaler



sscli/clr/src/vm/comdelegate.cpp(374):
コード:
// Marshals a delegate to a unmanaged callback.
LPVOID COMDelegate::ConvertToCallback(OBJECTREF pDelegate)



うーん、C++erな私でも
ソースがまったく理解できません(-_-;)
(Java6VMのソースもそうでしたが…)

Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2007-01-25 15:18
こんにちは。

先ほどのソースで…
コード:
// Marshals a delegate to a unmanaged callback.
LPVOID COMDelegate::ConvertToCallback(OBJECTREF pDelegate)
{
    if (!pDelegate) {
        return NULL;
    } else {

        LPVOID pCode;
        GCPROTECT_BEGIN(pDelegate);

	(デリゲートからコールバックの取り出し処理)

        pCode = (LPVOID)pUMEntryThunk->GetCode();
        GCPROTECT_END();
        return pCode;
    }
}



デリゲートからアンマネージコールバックを取り出す間だけは
デリゲートインスタンスをGCから保護しているようですが、
コールバックを取り出したら、デリゲートインスタンスは保護する必要がないように見えます。
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2007-01-26 10:01
引用:

※固定されている必要があるのは、アンマネージの世界から直接呼び出される
 メソッド実体のアドレスであって、デリゲートインスタンスではない、と



引用:

実は私もそんな想像をしていました。
アンマネージで必要なのは、
メソッドのアドレスであって、デリゲートインスタンスではないよなぁと考えていました。



そうそう、もちろんそのつもりで議論してたつもりです。

そのつもりで書いてましたが、

引用:

ですが、マネージ側でデリゲートの参照を維持している限り、デリゲートのアドレスが変わったりはしないんじゃないですかね???



は読み返すとそーでもなく読めてしまいますね。ごめんなさい。

で、元々このスレッドで問題にしていたコトは、アンマネージ側に渡ったコールバックアドレスがいつまで有効なのか?ということだったはずです。

(いつのまにか「何が」固定されているのか、に流れてしまいましたが)

それに関して、少なくともマネージ側でデリゲートインスタンスの参照が有効な間は、常にデリゲートに渡されたメソッドを呼び出すはず、というのが僕の主張です。

(なので、元質問者の人が書いているような状況は本来は起きないはずで、起きているとしたら何か原因があるはず)

一度設定したコールバックアドレスが何度も呼び出されるなんてコトは Windows API の世界では日常茶目仕事(既に上がっていますが、代表例が WndProc)な訳で、そういうつくりになってないと何事も立ち行かなくなってしまいます。


[ メッセージ編集済み 編集者: 渋木宏明(ひどり) 編集日時 2007-01-26 10:07 ]

[ メッセージ編集済み 編集者: 渋木宏明(ひどり) 編集日時 2007-01-26 10:08 ]

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