2010-01-15 20 views
4

J'ai une interface COM avec une méthode qui retourne un objet:Comment mieux initialiser un compteur de référence pour un objet COM non créable?

interface ICreatorInterface { 
    HRESULT CreateObject(IObjectToCreate**); 
}; 

La clé est que l'appel ICreatorInterface::CreateObject() est le seul moyen de récupérer un objet qui implémente l'interface IObjectToCreate.

En C++ je pouvais le faire de cette façon:

HRESULT CCreatorInterfaceImpl::CreateObject(IObjectToCreate** result) 
{ 
    //CObjectToCreateImpl constructor sets reference count to 0 
    CObjectToCreateImpl* newObject = new CObjectToCreateImpl(); 
    HRESULT hr = newObject->QueryInterface(__uuidof(IObjectToCreate), (void**)result); 
    if(FAILED(hr)) { 
     delete newObject; 
    } 
    return hr; 
} 

ou cette façon

HRESULT CCreatorInterfaceImpl::CreateObject(IObjectToCreate** result) 
{ 
    //CObjectToCreateImpl constructor sets reference count to 1 
    CObjectToCreateImpl* newObject = new CObjectToCreateImpl(); 
    HRESULT hr = newObject->QueryInterface(__uuidof(IObjectToCreate), (void**)result); 
    // if QI() failed reference count is still 1 so this will delete the object 
    newObject->Release(); 
    return hr; 
} 

La différence est la façon dont le compteur de référence est initialisé et comment la suppression de l'objet est mis en œuvre en cas QueryInterface() échoue . Depuis que je contrôle complètement à la fois CCreatorInterfaceImpl et CObjectToCreateImpl je peux aller de deux façons.

IMO la première variante est plus claire - tout le matériel de comptage de référence est dans un morceau de code. Ai-je supervisé quelque chose? Pourquoi la deuxième approche pourrait-elle être meilleure? Lequel de ce qui précède est meilleur et pourquoi?

Répondre

3

Les deux variantes viole un principe fondamental de COM

  • jamais appeler une méthode, autre que AddRef, sur un objet COM qui a un compte de référence de zéro.

Faire autrement conduit à toutes sortes d'erreurs. Tout simplement parce que cela empêche les gens de faire des opérations complètement légales sur l'objet. Comme les mettre dans un pointeur intelligent. Le pointeur intelligent appelait AddRef, mettait le nombre à 1 et libérait plus tard le compte à 0 et provoquait l'auto-destruction de l'objet.

Oui, je réalise que 90% des implémentations de QueryInterface ne le font pas. Mais je vous garantis également qu'il y en a quelques-uns qui font :)

Je pense que l'approche la plus simple consiste à appeler AddRef immédiatement après avoir créé l'objet. Cela permet à l'objet de se comporter comme un objet COM normal le plus tôt possible.

J'ai déjà rencontré ce problème par le passé et j'ai écrit une petite méthode d'aide (en supposant que l'objet est implémenté dans ATL).

template <class T> 
static 
HRESULT CreateWithRef(T** ppObject) 
{ 
    CComObject<T> *pObject; 
    HRESULT hr = CComObject<T>::CreateInstance(&pObject); 
    if (SUCCEEDED(hr)) 
    { 
     pObject->AddRef(); 
     *ppObject = pObject; 
    } 

    return hr; 
} 
+0

Je vois votre point, mais le même comportement peut être réalisé beaucoup plus simple: init refcount avec 0 dans le constructeur et utiliser un pointeur intelligent dans le code en question - il suffit de lier l'objet new'ed à la puce pointeur, alors appelez QI() - ATL :: CComPtr le permet, peut-être que _com_ptr_t le permet aussi. – sharptooth

0

J'utilise toujours le scénario de code suivant pour créer des objets com retournés pour éviter des problèmes avec la mémoire. Bien sûr, cela fonctionne car mes objets sont référencés = 0 lors de leur création. Cela me semble toujours plus clair que d'essayer de gérer la condition d'erreur en utilisant l'opérateur de suppression.

HRESULT CCreatorInterfaceImpl::CreateObject(IObjectToCreate** result) 
{ 
    //CObjectToCreateImpl constructor sets reference count to 0 
    CObjectToCreateImpl* newObject = new CObjectToCreateImpl(); 

    newObject->AddRef(); 

    HRESULT hr = newObject->QueryInterface(__uuidof(IObjectToCreate), (void**)result); 

    newObject->Release(); // release my addref, if QI succeeded it AddRef'd, if not the object is destroyed 

    return hr; // return result from QI 
} 
+1

Le même effet pourrait être obtenu avec deux fois moins de code si vous utilisiez un pointeur intelligent comme CComPtr. – sharptooth

+0

Vrai, seul problème mineur serait si le code nécessaire pour appeler une méthode non-interface basée sur l'objet (Comme une initialisation interne - qui n'aurait pas été traitée dans le constructeur en raison de situations d'erreur potentielles - ou qu'avez-vous) vous auriez toujours besoin d'un pointeur vers le bon type plutôt que vers l'interface. – Ruddy

+0

Dans la plupart des cas, vous pouvez paramétrer CComPtr avec le type le plus dérivé, et non l'interface. – sharptooth