8

今日、自分のコードで、自分の COM オブジェクトを IUnknown** にキャストすることによって、AFAICT というアクセス違反が発生するという問題に遭遇しました。渡された関数は問題なく実行されましたが、オブジェクトの関数の1つを呼び出すと、ランダムな関数が実行され、スタックが破損してから死にました。

示唆的なコード (なぜこのように行われたのかは無視してください - 私はそれが悪いことを知っており、それを修正する方法を知っていますが、これはなぜこのような問題が発生するのかという問題です):

void MyClass2::func(IMyInterface* pMyObj)
{
    CComPtr<IMyInterface2> pMyObj2;
    HRESULT hRes = pMyObj->GetInternalObject((IUnknown**)&pMyObj2);

    if (SUCCEEDED(hRes))
        pMyObj2->Function(); // corrupt stack
}

void MyClass::GetInternalObject(IUnknown** lpUnknown)
{
    pInternalObject->QueryInterface(IID_IMyInterface2, (void**)lpUnknown);
}

私は常に、COM オブジェクトで C/C++ キャストを使用することに少し疑いを持っていましたが、これまで (おそらく未定義の動作による) 問題に遭遇したことはありません。

簡単に調べてみたところ、継承チェーンに複数の相互関係がない限り、IUnknown へのキャストは技術的に有効であることがわかりますが、ベスト プラクティスとは見なされません。実際に IUnknown を渡してからMyClass::GetInternalObject(IUnknown** lpUnknown)、戻り値をクエリする必要があります。必要なインターフェイスの値。

私の質問は、COM オブジェクトで C/C++ キャストをいつ使用できるかについての規則はありますか? また、多重継承とそれらがもたらすアジャスター サンクは別として、COM オブジェクトのキャストがアクセス違反のような予期せぬ結果をもたらすのはどうしてでしょうか? 詳しく教えてください。

編集:これらはすべて、適切に実行する方法の良い例ですが、私が望んでいたのは、COMオブジェクトをキャストしてはならない理由の技術的な説明でした(存在する場合)。たとえば、キャストすると状況xでpMyObj2-4が返されますが、 QueryInterface はyのために pMyObj2-8 を返します ...または COM オブジェクトのキャストは単に悪い習慣/スタイルの問題ですか?

ティア

4

3 に答える 3

11

COM のコンテキストでは不適切と思われる C スタイルのキャストを使用してコードを記述する代わりに、COM インターフェイスを管理するためCComPtrに andを使用するだけです。CComQIPtr

void MyClass2::Func(IMyInterface* pMyObj)
{
    // Assuming:
    //   HRESULT IMyInterface::GetInternalObject( /* [out] */ IUnknown** )
    CComPtr<IUnknown> spUnk;       
    HRESULT hr = pMyObj->GetInternalObject(&spUnk);
    if (SUCCEEDED(hr))
    {
        // Get IMyInterface2 via proper QueryInterface() call.
        CComQIPtr<IMyInterface2> spMyObj2( spUnk );
        if ( spMyObj2 )
        {
            // QueryInterface() succeeded

            spMyObj2->Function();
        }
    }
}

さらに、私は COM の専門家ではありませんが、あなたのコードには疑いを持っています。

void MyClass::GetInternalObject(IUnknown** lpUnknown)
{
    pInternalObject->QueryInterface(IID_IMyInterface2, (void**)lpUnknown);
}

QueryInterface()'ingの場合は、ではなく にIID_MyInterface2格納する必要があります。あなたのメソッドが を返す場合、私は:IMyInterface2*IUnknown*IUnknown*QueryInterface()IID_IUnknown

// NOTE on naming convention: your "lpUnknown" is confusing.
// Since it's a double indirection pointer, you may want to use "ppUnknown".
//
void MyClass::GetInternalObject(IUnknown** ppUnknown)
{
    pInternalObject->QueryInterface(IID_IUnknown, (void**)ppUnknown);
}

またはIID_PPV_ARGSマクロを使用することをお勧めします:

void MyClass::GetInternalObject(IUnknown** ppUnknown)
{
    IUnknown* pUnk = NULL;
    HRESULT hr = pInternalObject->QueryInterface(IID_PPV_ARGS(&pUnk));
    // Check hr...

    // Write output parameter
    *ppUnknown = pUnk;
}

COM スタイルのキャストには固有の名前があります: QueryInterface().

于 2012-11-15T13:31:07.477 に答える
2

IMyInterface*問題は、 toからのキャストは問題ないのでIUnknown*(COM ではすべてが from から継承されIUknownますよね?)、IMyInterface**toからのキャストIUnknown**も問題ないと考えていることだと思います。しかし、これは C++ では当てはまりません。また、COM でも当てはまるとは思えません。

私には、次のほうがより論理的に見えます。これが厳密に正しくない場合は申し訳ありません。私の COM は非常にさびていますが、うまくいけば、アイデアが得られることを願っています。

CComPtr<IUnknown> pMyObj2;
HRESULT hRes = pMyObj->GetInternalObject(&pMyObj2);

if (SUCCEEDED(hRes))
{
    CComPtr<IMyInterface> pMyObj3 = (IMyInterface*)pMyObj2;
    pMyObj3->Function();
}

つまり、最初に IUnknown オブジェクトを取得してから、それを実際の型にダウンキャストします。

于 2012-11-15T10:54:01.237 に答える
0

コード スニペットに問題は見られません。スタックの破損にはおそらく原因がありますが、別の場所にあります。

GetInternalObjectタイプである必要があり、あなたのものではないため、実際のコードではないと思います。HRESULTそのため、コピー/貼り付け中に何かを失いました。

QueryInterface安全を保つために、キャストと一緒にインターフェイスを誤解する可能性があるため、直接呼び出しは避けてください。ただし、キャストとの間のキャストはIUnknown*避けられない場合があります。呼び出し先が IUnknown にキャストされた適切なインターフェイスを返すことを信頼できない場合、呼び出し側では、関心のあるインターフェイスを保持していることを確認するために、もう一度 QI を使用することをお勧めします。

GetInternalObject が独自の COM インターフェイス メソッドである場合、次のようにすることができます。

void MyClass2::func(IMyInterface* pMyObj)
{
    CComPtr<IUnknown> pMyObj2Unknown;
    pMyObj->GetInternalObject((IUnknown**)&pMyObj2Unknown);
    CComQIPtr<IMyInterface2> pMyObj2 = pMyObj2Unknown; // This is only needed if callee is not trusted to return you a correct pointer
    if (pMyObj2)
        pMyObj2->Function(); // corrupt stack
}

STDMETHODIMP MyClass::GetInternalObject(IUnknown** lpUnknown) // COM method is typically both HRESULT and __stdcall
{
    CComQIPtr<IMyInterface2> pMyInterface2 = pInternalObject;
    if(!pMyInterface2)
        return E_NOINTERFACE;
    *lpUnknown = pMyInterface2.Detach(); // *lpUnknown will have to me IMyInterface2 this way
    return S_OK;
}

PS GetInternalObject が COM ではなくネイティブ メソッドである場合は、キャストをIUnknown*まったく回避できます。

于 2012-11-15T13:52:00.430 に答える