2013-06-21 42 views
7

C#で書かれた管理対象COMオブジェクトと、C++(MFCとATL)で書かれたネイティブCOMクライアントとシンクがあります。クライアントはオブジェクトを作成し、起動時にそのイベントインタフェースにアドバイスし、イベントインタフェースからは通知しないで、シャットダウン時にオブジェクトを解放します。 COMオブジェクトには、ガベージコレクションが実行されるまで解放されないシンクへの参照があり、その時点でクライアントは既に解体されているため、通常はアクセス違反が発生するという問題があります。とにかくクライアントがシャットダウンしているので、おそらくそれほど大きな取引ではありませんが、可能であれば、これをうまく解決したいと考えています。シンクオブジェクトをより適切なタイミングで解放するために私のCOMオブジェクトが必要です。私のCOMオブジェクトがシンクオブジェクトを明示的に使用していないため、どこから始めるべきか分かりません。COM相互運用機能を使用しているときのオブジェクトのライフタイムを管理する方法は?

私のCOMオブジェクト:

public delegate void TestEventDelegate(int i); 

[ComVisible(true)] 
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 
public interface ITestObject 
{ 
    int TestMethod(); 
    void InvokeTestEvent(); 
} 

[ComVisible(true)] 
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 
public interface ITestObjectEvents 
{ 
    void TestEvent(int i); 
} 

[ComVisible(true)] 
[ClassInterface(ClassInterfaceType.None)] 
[ComSourceInterfaces(typeof(ITestObjectEvents))] 
public class TestObject : ITestObject 
{ 
    public event TestEventDelegate TestEvent; 
    public TestObject() { } 
    public int TestMethod() 
    { 
     return 42; 
    } 
    public void InvokeTestEvent() 
    { 
     if (TestEvent != null) 
     { 
      TestEvent(42); 
     } 
    } 
} 

クライアントは、ATLのサポートを追加して、標準のMFCダイアログベースのプログラムです。マイシンククラス:

class CTestObjectEventsSink : public CComObjectRootEx<CComSingleThreadModel>, public ITestObjectEvents 
{ 
public: 
    BEGIN_COM_MAP(CTestObjectEventsSink) 
     COM_INTERFACE_ENTRY_IID(__uuidof(ITestObjectEvents), ITestObjectEvents) 
    END_COM_MAP() 
    HRESULT __stdcall raw_TestEvent(long i) 
    { 
     return S_OK; 
    } 
}; 

I持って私のダイアログクラスに次のメンバー:のOnInitDialog()で

ITestObjectPtr m_TestObject; 
CComObject<CTestObjectEventsSink>* m_TestObjectEventsSink; 
DWORD m_Cookie; 

HRESULT hr = m_TestObject.CreateInstance(__uuidof(TestObject)); 
if(m_TestObject) 
{ 
    hr = CComObject<CTestObjectEventsSink>::CreateInstance(&m_TestObjectEventsSink); 
    if(SUCCEEDED(hr)) 
    { 
     m_TestObjectEventsSink->AddRef(); // CComObject::CreateInstace() gives an object with a ref count of 0 
     hr = AtlAdvise(m_TestObject, m_TestObjectEventsSink, __uuidof(ITestObjectEvents), &m_Cookie); 
    } 
} 

OnDestroy()で:

if(m_TestObject) 
{ 
    HRESULT hr = AtlUnadvise(m_TestObject, __uuidof(ITestObjectEvents), m_Cookie); 
    m_Cookie = 0; 
    m_TestObjectEventsSink->Release(); 
    m_TestObjectEventsSink = NULL; 
    m_TestObject.Release(); 
} 
+0

あなたはm_TestObjectEventsSink-> Release()を忘れています。 CComObject <>へのポインタを格納しているので自動ではありません。単に漏れているだけです。それがなぜ必要なのかわからない。 –

+0

おっと、申し訳ありません。これらのことは忘れてしまいましたが、CComObject :: CreateInstance()と同じことは、refカウントが0のオブジェクトを提供します。 – Luke

+0

CComObject :: CreateInstance()は、refカウントが0のオブジェクトを返します。 AddRef()を実行するのはあなたの責任です。 – Luke

答えて

3

まず、私はあなたのあなたが記述したもののコピーを実装するためのサンプルコードですが、DebugビルドまたはReleaseビルドのいずれかをテストする際にアクセス違反はありません。

表示されている内容に代替的な説明がある可能性があります(ネイティブクライアントに他のインターフェイスを使用する場合は、Marshal.ReleaseCOMObjectに電話する必要があります)。

に電話をしない場合は、MSDN hereにはいつ、いつ、どこでいつ電話が来ますか?

はあなたのC#のCOMオブジェクトがCOMクライアントのシンクオブジェクト直接では動作しないことだね、と言ったが、それはは、C#のイベントオブジェクトを通してそれと通信しません。これにより、カスタマイズされたイベントオブジェクトを実装できるので、クライアントの呼び出しの影響をAtlAdviseAtlUnadviseにトラップできます。たとえば、次のように

、あなたは(いくつかのデバッグ出力を追加して)あなたのイベントを再実装することができます

private event TestEventDelegate _TestEvent; 
public event TestEventDelegate TestEvent 
{ 
    add 
    { 
     Debug.WriteLine("TRACE : TestObject.TestEventDelegate.add() called"); 
     _TestEvent += value; 
    } 
    remove 
    { 
     Debug.WriteLine("TRACE : TestObject.TestEventDelegate.remove() called"); 
     _TestEvent -= value; 
    } 
} 

public void InvokeTestEvent() 
{ 
    if (_TestEvent != null) 
    { 
     _TestEvent(42); 
    } 
} 

デバッグ出力を続行するには、MFC/ATLのアプリケーションと同様の診断を追加し、正確に見ることができます参照カウントがシンクインターフェイス上で更新されたとき(これは、デバッグビルドがであり、プロジェクトであることを前提としています)。ですから、例えば、私はシンク実装にDumpメソッドを追加しました:

class CTestObjectEventsSink : public CComObjectRootEx<CComSingleThreadModel>, public ITestObjectEvents 
{ 
public: 
    BEGIN_COM_MAP(CTestObjectEventsSink) 
     COM_INTERFACE_ENTRY_IID(__uuidof(ITestObjectEvents), ITestObjectEvents) 
    END_COM_MAP() 
    HRESULT __stdcall raw_TestEvent(long i) 
    { 
     return S_OK; 
    } 
    void Dump(LPCTSTR szMsg) 
    { 
     TRACE("TRACE : CTestObjectEventsSink::Dump() - m_dwRef = %u (%S)\n", m_dwRef, szMsg); 
    } 
}; 

をその後、IDEを介してデバッグクライアントアプリケーションを実行している、あなたは何が起こっているかを見ることができます。まず、COMオブジェクトの作成中:

HRESULT hr = m_TestObject.CreateInstance(__uuidof(TestObject)); 
if(m_TestObject) 
{ 
    hr = CComObject<CTestObjectEventsSink>::CreateInstance(&m_TestObjectEventsSink); 
    if(SUCCEEDED(hr)) 
    { 
     m_TestObjectEventsSink->Dump(_T("after CreateInstance")); 
     m_TestObjectEventsSink->AddRef(); // CComObject::CreateInstace() gives an object with a ref count of 0 
     m_TestObjectEventsSink->Dump(_T("after AddRef")); 
     hr = AtlAdvise(m_TestObject, m_TestObjectEventsSink, __uuidof(ITestObjectEvents), &m_Cookie); 
     m_TestObjectEventsSink->Dump(_T("after AtlAdvise")); 
    } 
} 

これは、次のデバッグ出力を提供します(あなたがそこにAtlAdvise呼び出しからC#の跡を見ることができます)

TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 0 (after CreateInstance)
TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 1 (after AddRef)
TRACE : TestObject.TestEventDelegate.add() called
TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 2 (after AtlAdvise)

これは予想どおりですが、ネイティブcのリファレンスカウントは2つですode AddRefと別のもの(おそらく)AtlAdviseから。

さて、あなたはInvokeTestEvent()メソッドが呼び出された場合に何が起こるかを確認することができます - ここで私はそれを2回行います。

m_TestObject->InvokeTestEvent(); 
m_TestObjectEventsSink->Dump(_T("after m_TestObject->InvokeTestEvent() first call")); 
m_TestObject->InvokeTestEvent(); 
m_TestObjectEventsSink->Dump(_T("after m_TestObject->InvokeTestEvent() second call")); 

これは、あなたが追加することを見ることができ、対応するトレース

TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (after m_TestObject->InvokeTestEvent() first call) 
TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (after m_TestObject->InvokeTestEvent() second call) 

あるAddRef事件が起こったのは初めてです。私はガベージコレクションまで解放されないリファレンスであると推測しています。

最後に、OnDestroyでは、参照カウントが再び低下することがわかります。コードは

if(m_TestObject) 
{ 
    m_TestObjectEventsSink->Dump(_T("before AtlUnadvise")); 
    HRESULT hr = AtlUnadvise(m_TestObject, __uuidof(ITestObjectEvents), m_Cookie); 
    m_TestObjectEventsSink->Dump(_T("after AtlUnadvise")); 
    m_Cookie = 0; 
    m_TestObjectEventsSink->Release(); 
    m_TestObjectEventsSink->Dump(_T("after Release")); 
    m_TestObjectEventsSink = NULL; 
    m_TestObject.Release(); 
} 

で、トレース出力が

TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (before AtlUnadvise)
TRACE : TestObject.TestEventDelegate.remove() called
TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (after AtlUnadvise)
TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 2 (after Release)

あるので、あなたはAtlUnadviseは、参照カウント(also noted by other people)に影響を与えていないことがわかりますが、また、C#COM obのremoveアクセサからトレースを取得したことにも注意してくださいいくつかのガベージコレクションまたは他の解体タスクを強制する可能性のあるイベントです。

要約すると:

  1. をあなたが投稿コードでアクセス違反を報告したが、私はそのエラーを再現することができませんでしたので、あなたが表示されるエラーは、あなたが説明した問題とは無関係であることも可能です。
  2. COMクライアントシンクとの対話方法を尋ねました。カスタマイズされたイベント実装を使用して潜在的な方法を示しました。これは、2つのCOMコンポーネントがどのように相互作用するかを示すデバッグ出力でサポートされています。

本当に役に立ちそうです。 this old but otherwise excellent blog postには、いくつかの代替COM処理のヒントと詳細な説明があります。

+0

ブログの投稿が適切かどうかはわかりません。マネージコードでCOMオブジェクトを消費し、確定的にリリースするようにカスタマイズしています。私の場合、COMオブジェクト自体が管理され、ネイティブコードで消費されています。マネージコードでは、イベントシンクへの参照がないので、ReleaseComObject()を呼び出すことはできません。私はevent.remove()中にGCを強制的に見て、それが何かをするかどうかを見ます。 – Luke

+0

event.remove()中に強制的にGCを実行しても、参照カウントは減少しません。 – Luke

+0

@Lukeはい、申し訳ありませんが、私はそれが過去に便利だと思ったので、ブログの投稿はありますが、あなたはそれがあなたのものとは逆の場合に適しています。また、GC.Collectも試しましたが、COMラッパーはオブジェクト自体がなくなるまで開いていると思います。私の主な関心事はアクセス違反の欠如でした。だから私はなぜデバッグしようとすることができませんでした。私は、AtlUnadviseを罠に掛けることができれば、そこに問題を解決することができるかもしれないと思っていました。 –

関連する問題