- PR -

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

投稿者投稿内容
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2007-01-26 19:24
引用:

マネージスレッドは、OSスレッドとは違うんですね!?



常に1対1対応するものではない、と「されています」。

引用:

yieldとはなんでしょうか?(yield returnのこと??)



yield return, yield break のことです。

これらは列挙子オブジェクトの実装を助けるものですが、スタックを積極的に摩り替えることで実現されているはずです。

追記:

マネージコードだから、「スタックを操作」と言うよりは「実行コンテキストを積極的に操作して」の方がいいのかな?


[ メッセージ編集済み 編集者: 渋木宏明(ひどり) 編集日時 2007-01-26 20:44 ]
yayadon
常連さん
会議室デビュー日: 2003/07/23
投稿数: 41
投稿日時: 2007-01-26 23:43
# 用語の説明だけですが

thunk というのは,
A -> B と直接呼ぶのでなく,
A -> thunk -> B のように indirection (間接操作?) を実現するための
コードのことをいいます。

リンク先の説明が正しければ,thunk は,
相互運用マーシャラがネイティブ向けに用意したもので,
ネイティブ側は,サンクへのポインタ(a pointer to thunk)を
実際のメソッドへのポインタの代わりに受け取って呼んでいて,
thunk が実際のCallback用のメソッドを呼んでいる
という形になってるようです。
で,
delegateのインスタンスが回収される時に,
thunkもネイティブ側で相互運用マーシャラによって破棄される
という意味でしょう。

追記:
--------------------------------------------------------------------
その理屈からすると...

Callback用のメソッドを実装するものを sink object というのだけど,
その sink object の寿命 と delegeteインスタンスの寿命 を一致させるため,
そのsink objectのフィールドメンバに,delegateを加えておいて,
デリゲートのインスタンスを一旦それに入れておいてから登録するだけでいいような。
(sink object自体が,回収されないようにしないといけませんが)

NativeWindowクラスも
MulticastDelegateから派生した WndProcデリゲート クラス 型の
windowProc という名前のフィールドをメンバとして持ってるので,
探せば同じようにやってるんじゃないかと。(WindowClass::RegisterClassあたりで)

それ以上のことは,必要ないような気がするんですけどね。

[ メッセージ編集済み 編集者: 稍丼 編集日時 2007-01-27 02:02 ]
ひろし
ぬし
会議室デビュー日: 2002/09/16
投稿数: 390
お住まい・勤務地: 兵庫県
投稿日時: 2007-01-27 09:34
皆さんご回答ありがとうございます。

私も記事を参考に検索を試みていますが、解決できていません。
(固定アドレスを前提にした)アンマネージなコールバックメソッドに対する
対処方法がどこかにきっと記載されているはずですが、
見つける事ができません。
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2007-01-27 10:54
引用:

(固定アドレスを前提にした)アンマネージなコールバックメソッドに対する
対処方法がどこかにきっと記載されているはずですが、
見つける事ができません。



もう一度、正確な状況を教えてください。

アンマネージに渡されたコールバックアドレスが、いつの間にか無効になっているということなんでしょうか?

であれば、マネージ側の実装が正しければ、そんなことは起きないはずです。
そんなことが容易に起きるとすれば、Windows Forms は成立しません。

「マネージ側の実装が正しい」とは、この場合「アンマネージに渡したデリゲートの参照をマネージ側で保持している」ことを指します。
渋木宏明(ひどり)
ぬし
会議室デビュー日: 2004/01/14
投稿数: 1155
お住まい・勤務地: 東京
投稿日時: 2007-01-27 10:57
サンクもスタブもプロキシも、大雑把に言えばみんな似たようなモンですね。

ある層から別な層を呼び出す時の「踏み台」みたいなもん、でいいのかな?
囚人
ぬし
会議室デビュー日: 2005/08/13
投稿数: 1019
投稿日時: 2007-01-27 21:33
引用:

リンク先の説明が正しければ,thunk は,
相互運用マーシャラがネイティブ向けに用意したもので,
ネイティブ側は,サンクへのポインタ(a pointer to thunk)を
実際のメソッドへのポインタの代わりに受け取って呼んでいて,
thunk が実際のCallback用のメソッドを呼んでいる
という形になってるようです。
で,
delegateのインスタンスが回収される時に,
thunkもネイティブ側で相互運用マーシャラによって破棄される
という意味でしょう。


なるほど!非常に勉強になりました。

何故そんな実装になっているのか疑問なんですけど、Callback用の メソッドがまだ一度も実行されていなかったら(つまり JIT コンパイルされていなかったら)メソッドのアドレスが不定(まだスタブのアドレス)だからなんでしょうか。サンクを通すようにすれば、コンパイルされていなくてもされていても、アンマネージ側は同じアドレスで呼び出せるからとか。

で、最初の問題なのですが、やはりデリゲートインスタンスが途中で破棄されているから?
_________________
囚人のジレンマな日々
yayadon
常連さん
会議室デビュー日: 2003/07/23
投稿数: 41
投稿日時: 2007-01-27 23:21
デリゲートが,

コード:


internal delegate void Feedback(Int32 value);


とあるとすると,(C/C++的に)いわゆる関数ポインタに

コード:


typedef void (*Feedback)(Int32);


のように名前をつけた感じのような気になるので,
単なる関数ポインタならば,
ネイティブ側に直接渡せそうな気がします。

ですが,
(CLR via C# (プログラミングMicrosoft .NET Framework第2版) の Chapter 15 にあるように)
実際は,コンパイラが

コード:


internal class Feedback : System.MulticastDelegate {
public Feedback(Object object, IntPtr method); //コンストラクタ
public virtual void Invoke(Int32 value);
publlc virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object object);
public virtual void EndInvoke(IAsyncResult result);
}


のような感じでクラスを自動生成してくれています。

メソッドを複数登録して呼び出せるように,
マルチキャストデリゲートは,
デリゲートのインスタンスがリスト繋がりになれる関係で,
メソッドを呼び出すといっても,
デリゲートのInvokeメソッド経由で呼び出さないといけないだろうから,

> thunk が実際のCallback用のメソッドを呼んでいる

は,正確には,

thunk が, 
 コンパイラが生成するSystem.MulticastDelegateを継承した
 (実際のCallback用のメソッドポインタをコンストラクタで受け取り,
 ラップする形になっている)(デリゲートの)クラスの
 インスタンスのInvokeメソッドを,呼んでいる

になっていると思います。

コード:


A -> thunk
+-----> Invoke( ... ) ------------------------------> B

delegate定義を元に
コンパイラが
MulticastDelegateクラスを継承させて
自動生成したクラスの(インスタンスの)Invokeメソッドの呼び出し



[ メッセージ編集済み 編集者: 稍丼 編集日時 2007-01-28 01:11 ]
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2007-01-28 02:19
こんばんは。

皆さんマネージな世界にお詳しいですねぇ〜

私はやっぱりドキュメントを読むより、
ソースコードを読んで理解するほうが好きなので…
(公式ドキュメントのほうが正しい仕様なのは分かっていますが)

引用:

Tdnr_Symの書き込み (2007-01-25 14:48) より:
ここのソースを眺めているのですが、
http://www.microsoft.com/downloads/details.aspx?FamilyId=3A1C93FA-7462-47D0-8E56-8DD34C6292F0&displaylang=en#filelist



古いソースのほう見ていたようなので
新しいバージョンのほうを見ることにしました。

Shared Source Common Language Infrastructure 2.0 Release

いろいろソースを眺めましたが結局
サンクがどのタイミングで破棄されるのか見つけられませんでした。

でも、下記のように(大雑把ですが)
デリゲートインスタンス(Object)にサンク(UMEntryThunk)が紐づいている(包含している)のをみると
やっぱりデリゲートインスタンスとサンクの寿命は同じな気がします。
皆さんの仰るとおり、デリゲートインスタンスは破棄されないようにしないといけないように思います。

コード:
Object
 └─ObjHeader
    └─SyncBlock
       └─InteropSyncBlockInfo
          └─UMEntryThunk
             └─UMEntryThunkCode




で、サンク(UMEntryThunk,UMEntryThunkCode...)の中身ですが、
バイナリな世界で良く分かりませんでした。

JITコンパイルされたメソッドのアドレス、またはスタブのアドレスを
サンクが間接的に公開しているのではないかと想像しているのですが。


ところで…
System.Delegateクラスの中身を見ていると
コード:
public abstract class Delegate : ICloneable, ISerializable
{
   (省略)

      // Fields
      internal MethodBase _methodBase;
      internal IntPtr _methodPtr;
      internal IntPtr _methodPtrAux;
      internal object _target;
}



_methodPtr,_methodPtrAuxってどんなポインタが入っているのだろうと気になったのですが…
リフレクションで値が見れるのかなぁ?

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