2012-11-15 173 views
5

我今天在我的代碼中遇到了一個問題,在我的代碼中導致了一個訪問衝突,AFAICT,通過將我的COM對象強制轉換爲IUnknown **。它被傳入的函數沒有問題,但是當調用我的對象函數之一時,它會執行一些隨機函數並破壞堆棧然後死掉。Casting COM接口

指示代碼(只是忽略它爲什麼做這樣 - 我知道這是不好的,我知道如何解決它,但是這是一個瞭解爲什麼會出現這樣的問題,一個問題):

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++強制轉換,除了多重繼承和它們帶來的調整器thunk之外,如何強制轉換COM對象導致訪問衝突等突發事件?請詳細說明。它們都是如何正確完成的好例子,但是我希望得到的是爲什麼你不應該投射COM對象(假設存在)的技術解釋,例如,鑄造將返回pMyObj2-4的情況x但QueryInterface將返回pMyObj2-8,因爲y ...或正在投射COM對象只是一個不好的練習/風格問題?

TIA

+1

您正在追逐錯誤的問題。這種問題是版本問題的經典結果,有人在更改其定義後沒有更新接口的IID。所以你要調用完全錯誤的方法或用錯誤的參數調用它。聯繫這個組件的作者來解決這個問題。 –

+0

下面有幾個答案說明了這樣做的正確方法。將一個'IUnknown'接口地址傳遞給你的GetInternalObject(),然後查詢你需要的接口的結果。打破編組是許多不以其他方式做的原因之一。 COM的規則很簡單。您通過IID查詢接口,您將返回該IID的接口指針。如果它是從基本接口(如IUnknown)派生的,則可以使用這些方法。如果一個函數通過地址返回一個IUnknown,那麼你不能做你上面的代碼安全地做的事情。 – WhozCraig

+0

@Hans我不認爲這是一個版本控制問題 - 我遇到的問題非常具體,否則我可以使用相同的COM接口而不會出現問題。 – Sparkles

回答

2

我認爲這個問題是因爲從IMyInterface*強制轉換爲IUnknown*是OK(在COM一切從IUknown權繼承?)你認爲從IMyInterface**強制轉換爲IUnknown**也行。但在C++中這不是真的,我也懷疑它在COM中也是如此。

對我來說,以下看起來更合乎邏輯,道歉如果這不是嚴格正確的,我的COM是非常生鏽的,但希望你明白了。

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

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

I.e.首先獲取一個IUnknown對象,然後將其轉換爲您的實際類型。

+1

那麼有沒有一個IMeInterface的內存佈局是這樣的:將它轉換爲IUnknown與調用IID_IUnknown的QueryInterface相同? – Sparkles

+0

@Sparkles對不起,我不知道。我只回答,因爲你沒有任何其他答案。我的COM非常生疏,我從C++的角度回答。 C++中的真實情況對於COM也是如此,我不確定。如果你認爲我在說垃圾,我會刪除我的答案。 – john

+0

C風格將「CComPtr 」轉換爲「IMyInterface *」的代碼正在尋求麻煩。在這種情況下,你應該總是使用'QueryInterface()' - 明確地(不要忘記'CComPtr'具有'QueryInterface()'成員函數)或者'CComQIPtr'形式。 – sharptooth

11

我只用CComPtrCComQIPtr來管理,而不是與C風格編寫代碼的COM接口,演員對我在COM的背景下顯得不合適:

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()「荷蘭國際集團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()

0

我在代碼片段中看不到任何問題,堆棧損壞可能有其原因,但在其他地方。

我不認爲這是你的實際代碼,因爲GetInternalObject應該是HRESULT類型,你的不是,所以你在複製/粘貼過程中丟失了某些東西。

爲了保持安全,只需避免直接調用QueryInterface,因爲與cast一起可能會誤解接口。儘管如此,從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*可言。