Domanda

Se ho un IUnknown *ptr, devo chiamare? Release() su ogni interfaccia che ottengo ptr->QueryInterface(), Inoltre a chiamare ptr->Release() quando avrò finito ptr?

Pensavo che la risposta fosse "Sì", ma questa citazione da MSDN mi ha confuso:

Occasionalmente potrebbe essere necessario ottenere un riferimento debole a un oggetto (ovvero, potresti voler ottenere un puntatore a una delle sue interfacce senza incrementare il conteggio dei riferimenti), ma non è accettabile farlo chiamando QueryInterface seguito da Release.

Non capisco perché sia ​​problematico, se chiamo ptr->QueryInterface() e poi chiama Release sul puntatore risultante, il conteggio dei riferimenti sull'oggetto non dovrebbe essere ancora positivo?In che modo ciò si traduce in un puntatore non valido?

È stato utile?

Soluzione

La documentazione è corretta.E devi seguire le regole di conteggio delle referenze, incluse le chiamate Release sulle interfacce ottenute da QueryInterface oltre a dopo aver creato l'oggetto.

Per chiarire perché non puoi usare i puntatori deboli Release - Esiste una condizione di gara nella chiamata QueryInterface poi Release subito dopo.

  • Thread1 crea l'oggetto: conteggio dei riferimenti 1
  • Chiamate Thread2 QueryInterface per riferimento debole - conteggio dei riferimenti 2
  • Thread1 rilascia l'oggetto: conteggio dei riferimenti 1
  • Chiamate Thread2 Release per riferimento debole - conteggio dei riferimenti 0.L'oggetto è distrutto.
  • Thread2 tenta di utilizzare l'oggetto: errore.

L'avvertimento è lì per proteggersi da quanto sopra: presumibilmente alcuni programmatori pensano di poter "call ptr->QueryInterface() e poi chiama Release sul puntatore risultante" e quindi utilizzare l'oggetto...

Altri suggerimenti

Metodo IUnknown::QueryInterface

Recupera i puntatori alle interfacce supportate su un oggetto.

Questo metodo chiama IUnknown::AddRef sul puntatore che restituisce.

Direttamente dal riferimento IUnknown::QueryInterface suhttp://msdn.microsoft.com/en-us/library/ms682521%28v=vs.85%29.aspx

L'intestazione non è l'unico scenario;Oserei dire che il threading non è affatto lo scenario principale:queste regole COM risalgono a Win16 prima che il multithreading preventivo fosse aggiunto a Windows.

La questione fondamentale è che, per quanto riguarda COM, i conteggi dei riferimenti sono per-interfaccia, non per-oggetto.Un'implementazione COM è libera di implementare effettivamente un conteggio dei riferimenti implementandolo per oggetto - questo è forse il modo più semplice per farlo in C++, specialmente quando un oggetto COM è mappato a un singolo oggetto C++ - ma non è altro che un dettaglio di implementazione, e il codice client COM non può fare affidamento su questo.

Esistono molti oggetti COM in circolazione che possono generare interfacce al volo secondo necessità e quindi distruggerle non appena non sono più necessarie.In questi casi, se chiami QI per ottenere una di queste interfacce, una volta chiamato Release, la memoria per quell'interfaccia può essere deallocata, quindi il suo utilizzo potrebbe portare a un errore/arresto anomalo/ecc.

In generale, devi considerare qualsiasi chiamata a ->Release() come una potenziale deallocazione della memoria dietro il puntatore.

(Inoltre, tieni presente che COM non ha realmente un concetto di riferimenti deboli con cui cominciare:ci sono riferimenti (forti) contati, e basta.)

Il suggerimento di evitare riferimenti deboli non risolve la questione razziale.

T1 operator new, create object, references: 1
T1     passes interface object reference to T2, thinking it can "share" ownership
T1     suspends
T2     resumes
T2 QueryInterface
T2     suspends before InterlockedIncrement, references: 1
T1     resumes
T1 Calls Release
T1     suspends between InterlockedDecrement and operator delete, references: 0
T2     resumes, InterlockedIncrement occurs, references 1
T2     suspends
T1     resumes, operator delete executes, references 1 !!!
T1     suspends
T2     resumes
T2 Any reference to the interface is now invalid since it has been deleted with reference count 1.

Questo è risolvibile nel server COM.Il client COM, tuttavia, non dovrebbe dipendere dal server che impedisce questa race condition.Pertanto, i client COM NON DEVONO condividere oggetti di interfaccia tra thread.L'unico thread a cui dovrebbe essere consentito accedere all'oggetto interfaccia è l'UNO thread che attualmente "possiede" l'oggetto interfaccia.

T1 NON avrebbe dovuto chiamare Release.Sì, avrebbe potuto chiamare AddRef prima di passare l'oggetto interfaccia a T2.Ma questo potrebbe non risolvere la gara, ma solo spostarla da qualche altra parte.La pratica migliore è mantenere sempre il concetto di un oggetto con un'interfaccia e un proprietario.

Se il server COM desidera supportare il concetto che due (o più) interfacce possono fare riferimento a uno stato condiviso interno al server, il server COM dovrebbe pubblicizzare il contratto fornendo un metodo CreateCopyOfInstance e gestire il conflitto internamente.Naturalmente ci sono esempi di server che gestiscono questo tipo di "fan-out".Dai un'occhiata alle interfacce di archiviazione persistente di Microsoft.Lì le interfacce NON si "distribuiscono" ..ogni interfaccia dovrebbe essere di proprietà di un singolo utente (thread/processo/qualunque cosa) e il "fan-out" è gestito, internamente, dal server, con metodi forniti ai client COM per controllare alcuni aspetti dei problemi di contesa.I server COM di Microsoft devono, quindi, affrontare le condizioni di gara come parte dei loro contratti con i loro clienti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top