2012-11-15 8 views
5

Oggi mi sono imbattuto in un problema nel mio codice in cui è stata provocata una violazione di accesso, AFAICT, eseguendo un mio oggetto COM su un IUnknown **. La funzione è stata passata in esecuzione senza problemi, ma quando si chiama una delle funzioni del mio oggetto, si eseguirà una funzione casuale e si corrompe lo stack, quindi morirà.Interfacce COM di trasmissione

codice indicativo (basta ignorare perché è fatto in questo modo - so che è male e so come risolvere il problema, ma questa è una questione del perché si possono verificare problemi come questo):

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); 
} 

Ho sempre stato un po 'diffidente nell'usare cast di C/C++ su oggetti COM ma non ho mai incontrato (forse attraverso un comportamento indefinito) alcun problema fino ad ora.

ho avuto un rapido sguardo e da quello che posso dire colata a IUnknown è tecnicamente valida fino a quando non v'è alcuna interitance multipla nella catena di ereditarietà, tuttavia non è considerata best practice - io in realtà dovrebbe passare un IUnknown a MyClass::GetInternalObject(IUnknown** lpUnknown) e quindi eseguire una query sul valore restituito per l'interfaccia desiderata.

La mia domanda è, ci sono regole su quando C/C++ calchi possono essere utilizzati su oggetti COM, ea parte l'ereditarietà multipla e le thunk regolatore che portano, come può colata oggetti COM causare sorprese come violazioni di accesso? Si prega di essere dettagliato.

Modifica: Sono tutti buoni esempi di come dovrebbe essere fatto correttamente, ma quello che speravo era una spiegazione tecnica del perché non dovresti lanciare oggetti COM (supponendo che ne esista uno) ad es. il cast restituirà pMyObj2-4 nella situazione x ma QueryInterface restituirà pMyObj2-8 a causa di y ... o sta lanciando oggetti COM semplicemente per una questione di cattiva pratica/stile?

TIA

+1

Stai inseguendo il problema sbagliato. Questo tipo di problema è un risultato classico di un problema di versione, qualcuno non ha aggiornato l'IID dell'interfaccia dopo averne modificato la definizione. Quindi stai chiamando il metodo completo sbagliato o chiamandolo con gli argomenti sbagliati. Mettiti in contatto con l'autore di questo componente per risolvere questo problema. –

+0

Di seguito sono riportate diverse risposte che dimostrano il modo corretto per eseguire questa operazione. Passa un indirizzo di interfaccia 'IUnknown' al tuo GetInternalObject() e interroga il risultato per l'interfaccia di cui hai bisogno. Spezzare il marshalling è uno dei tanti motivi per NON farlo in altro modo. Le regole della COM sono semplici. Si esegue una query per un'interfaccia da IID, si ottiene un puntatore a interfaccia per quello IID. Se è derivato da un'interfaccia di base (come IUnknown) puoi usare questi metodi. Se una funzione restituisce un indirizzo IUnknown per indirizzo, NON puoi fare ciò che il tuo codice sopra fa in modo sicuro. – WhozCraig

+0

@Hans Non penso che sia un problema di versione - il problema che stavo avendo era piuttosto specifico e altrimenti avrei potuto usare le stesse interfacce COM senza problemi. – Sparkles

risposta

2

Penso che il problema è che perché un cast IMyInterface*-IUnknown* è OK (in COM tutto eredita da IUknown giusto?) Si pensa che un cast IMyInterface**-IUnknown** è anche OK. Ma non è vero in C++, e dubito che sia vero anche in COM.

Per me il seguente aspetto è più logico, mi scuso se questo non è strettamente corretto, il mio COM è molto arrugginito, ma si spera che si ottiene l'idea.

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

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

I.e. ottenere prima un oggetto IUnknown, quindi eseguire il cast del tipo corrente.

+1

Esiste quindi una garanzia che il layout di memoria di un IMyInterface sia tale da inviarlo a IUnknown equivale a chiamare QueryInterface per IID_IUnknown? – Sparkles

+0

@Sparkles Mi dispiace, non lo so. Ho solo risposto perché non avevi altre risposte. La mia COM è molto arrugginita e stavo rispondendo dal punto di vista del C++. Se ciò che è vero in C++ è vero anche per COM non sono sicuro. Se pensi che sto parlando di spazzatura, cancellerò la mia risposta. – john

+0

Il codice in cui lo stile C ha lanciato un 'CComPtr ' in 'IMyInterface *' sta causando problemi. Dovresti sempre usare 'QueryInterface()' in questi casi - esplicitamente (non dimenticare 'CComPtr' ha la funzione membro' QueryInterface() 'o in forma di' CComQIPtr'. – sharptooth

11

mi basta usare CComPtr e CComQIPtr per gestire interfacce COM, invece di scrivere codice con C-stile getta che a me sembra inadeguato nel contesto di 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(); 
     } 
    } 
} 

Inoltre, io non sono un esperto di COM, ma vedo con sospetto il tuo codice:

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

Se siete QueryInterface() 'ing IID_MyInterface2, è necessario memorizzare che in un IMyInterface2*, non in un IUnknown*. Se il metodo restituisce un IUnknown*, poi mi QueryInterface() un 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); 
} 

o l'uso migliore IID_PPV_ARGS macro:

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

    // Write output parameter 
    *ppUnknown = pUnk; 
} 

stile COM getta ha un nome preciso: QueryInterface().

0

Non vedo alcun problema nei frammenti di codice, la corruzione dello stack forse ha la sua causa ma è altrove.

Non penso che sia il tuo codice effettivo perché GetInternalObject dovrebbe essere del tipo HRESULT e il tuo non lo è, quindi hai perso qualcosa durante la copia/incolla.

Per rimanere più sicuri, è sufficiente evitare le chiamate dirette QueryInterface perché, insieme alla trasmissione, potrebbero interpretare erroneamente le interfacce. La trasmissione da e verso IUnknown* potrebbe essere inevitabile. Se non ci si può fidare del callee per restituire l'interfaccia corretta castata a IUnknown, sul lato del chiamante potresti preferire nuovamente il QI per assicurarti di mantenere l'interfaccia di tuo interesse.

A condizione che GetInternalObject è un metodo di interfaccia COM per conto suo, si potrebbe avere in questo modo:

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 Se GetInternalObject era un metodo nativo, non COM, si dovrebbe evitare di lanciare a IUnknown* a tutti.