Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Pointeur intelligent en paramètre de fonction

14 réponses
Avatar
Michael
Bonjour à tous,

C'est un peu HS puisqu'en l'occurence les pointeurs intelligents sont des
CComPtr, issus de la bibliothèque ATL, et qui permet de gérer des objets
COM.

Mais je pense que le principe est le même que pour des boost::shared_ptr
par exemple.

Passons au vif du sujet:

Pourquoi est ce que le code suivant ne fonctionne pas?
Est-ce qu'il existe une autre manière de passer des CComPtr en
paramètre de fonction?

class toto
{
private:
CComPtr<IGraphBuilder> pGraph;
public:
void SetGraph(CComPtr<IGraphBuilder> graph)
{
pGraph = graph;
}
};

class foo
{
private:
toto t;

CComPtr<IGraphBuilder> pGraph;

// blablabla...
public:
foo()
{
t.SetGraph(pGraph);
}
};

Après l'appel de t.SetGraph(pGraph), un appel à
IGraphBuilder::Release() est déclenché, ce qui fait que mon objet
pGraph est réinitialisé...

Comment je dois passer ça?

1) Passer le pointeur brut:

void toto::SetGraph(IGraphBuilder * graph)
{
pGraph = graph;
}

foo::foo()
{
t.SetGraph(pGraph);
}

2) Passer par un pointeur de CComPtr?

void toto::SetGraph(CComPtr<IGraphBuilder> * ppgraph)
{
pGraph = *ppgraph;
}

foo::foo()
{
t.SetGraph(&pGraph);
}

J'avoue être un peu perdu avec le comptage des références...

Est-ce que quelqu'un peut éclairer ma lanterne?

Merci d'avance

10 réponses

1 2
Avatar
Arnaud Meurgues
Michael wrote:

C'est un peu HS puisqu'en l'occurence les pointeurs intelligents sont des
CComPtr, issus de la bibliothèque ATL, et qui permet de gérer des objets
COM.

Mais je pense que le principe est le même que pour des boost::shared_ptr
par exemple.


Peut-être. Comment est supposé fonctionner un CComPtr ?

La documentation msdn
(http://msdn2.microsoft.com/en-us/library/ezzw7k98.aspx) n'est pas très
claire.

N'est-ce pas plutôt l'objet COM lui-même qui gère son comptage de
référence avec AddRef() qui incrémente le compteur et Release() qui le
décrémente ?

Dans ce cas là, il est normal que la destruction d'un CComPtr déclenche
un Release() mais ce Release ne devrait pas avoir d'effet s'il a été
précédé d'un AddRef() (dans le constructeur ou l'opérateur =).

En tout cas, c'est ce que (me) suggère le code de CComPtr et de sa
classe de base CComPtrBase qui ne semblent embarquer ni l'un ni l'autre
un compteur de référence, mais qui semblent utiliser AddRef() et
Release() pour ce but. Mais comme je ne connais pas COM, je ne suis pas
certain de ce que doivent faire AddRef() et Release().

Voilà le code :

template <class T>
class CComPtrBase
{
protected:
CComPtrBase() throw()
{
p = NULL;
}
CComPtrBase(__in int nNull) throw()
{
ATLASSERT(nNull == 0);
(void)nNull;
p = NULL;
}
CComPtrBase(__in T* lp) throw()
{
p = lp;
if (p != NULL)
p->AddRef();
}
public:
typedef T _PtrClass;
~CComPtrBase() throw()
{
if (p)
p->Release();
}
operator T*() const throw()
{
return p;
}
T& operator*() const
{
ATLENSURE(p!=NULL);
return *p;
}
//The assert on operator& usually indicates a bug. If this is really
//what is needed, however, take the address of the p member explicitly.
T** operator&() throw()
{
ATLASSERT(p==NULL);
return &p;
}
_NoAddRefReleaseOnCComPtr<T>* operator->() const throw()
{
ATLASSERT(p!=NULL);
return (_NoAddRefReleaseOnCComPtr<T>*)p;
}
bool operator!() const throw()
{
return (p == NULL);
}
bool operator<(__in_opt T* pT) const throw()
{
return p < pT;
}
bool operator!=(__in_opt T* pT) const
{
return !operator==(pT);
}
bool operator==(__in_opt T* pT) const throw()
{
return p == pT;
}

// Release the interface and set to NULL
void Release() throw()
{
T* pTemp = p;
if (pTemp)
{
p = NULL;
pTemp->Release();
}
}
// Compare two objects for equivalence
bool IsEqualObject(__in_opt IUnknown* pOther) throw()
{
if (p == NULL && pOther == NULL)
return true; // They are both NULL objects

if (p == NULL || pOther == NULL)
return false; // One is NULL the other is not

CComPtr<IUnknown> punk1;
CComPtr<IUnknown> punk2;
p->QueryInterface(__uuidof(IUnknown), (void**)&punk1);
pOther->QueryInterface(__uuidof(IUnknown), (void**)&punk2);
return punk1 == punk2;
}
// Attach to an existing interface (does not AddRef)
void Attach(__in T* p2) throw()
{
if (p)
p->Release();
p = p2;
}
// Detach the interface (does not Release)
T* Detach() throw()
{
T* pt = p;
p = NULL;
return pt;
}
__checkReturn HRESULT CopyTo(__deref_out_opt T** ppT) throw()
{
ATLASSERT(ppT != NULL);
if (ppT == NULL)
return E_POINTER;
*ppT = p;
if (p)
p->AddRef();
return S_OK;
}
__checkReturn HRESULT SetSite(__in_opt IUnknown* punkParent) throw()
{
return AtlSetChildSite(p, punkParent);
}
__checkReturn HRESULT Advise(__in IUnknown* pUnk, __in const IID& iid,
__out LPDWORD pdw) throw()
{
return AtlAdvise(p, pUnk, iid, pdw);
}
__checkReturn HRESULT CoCreateInstance(__in REFCLSID rclsid, __in_opt
LPUNKNOWN pUnkOuter = NULL, __in DWORD dwClsContext = CLSCTX_ALL) throw()
{
ATLASSERT(p == NULL);
return ::CoCreateInstance(rclsid, pUnkOuter, dwClsContext,
__uuidof(T), (void**)&p);
}
__checkReturn HRESULT CoCreateInstance(__in LPCOLESTR szProgID,
__in_opt LPUNKNOWN pUnkOuter = NULL, __in DWORD dwClsContext CLSCTX_ALL) throw()
{
CLSID clsid;
HRESULT hr = CLSIDFromProgID(szProgID, &clsid);
ATLASSERT(p == NULL);
if (SUCCEEDED(hr))
hr = ::CoCreateInstance(clsid, pUnkOuter, dwClsContext, __uuidof(T),
(void**)&p);
return hr;
}
template <class Q>
__checkReturn HRESULT QueryInterface(__deref_out_opt Q** pp) const throw()
{
ATLASSERT(pp != NULL);
return p->QueryInterface(__uuidof(Q), (void**)pp);
}
T* p;
};

template <class T>
class CComPtr : public CComPtrBase<T>
{
public:
CComPtr() throw()
{
}
CComPtr(int nNull) throw() :
CComPtrBase<T>(nNull)
{
}
CComPtr(T* lp) throw() :
CComPtrBase<T>(lp)

{
}
CComPtr(__in const CComPtr<T>& lp) throw() :
CComPtrBase<T>(lp.p)
{
}
T* operator=(__in_opt T* lp) throw()
{
if(*this!=lp)
{
return static_cast<T*>(AtlComPtrAssign((IUnknown**)&p, lp));
}
return *this;
}
template <typename Q>
T* operator=(__in const CComPtr<Q>& lp) throw()
{
if( !IsEqualObject(lp) )
{
return static_cast<T*>(AtlComQIPtrAssign((IUnknown**)&p, lp,
__uuidof(T)));
}
return *this;
}
T* operator=(__in const CComPtr<T>& lp) throw()
{
if(*this!=lp)
{
return static_cast<T*>(AtlComPtrAssign((IUnknown**)&p, lp));
}
return *this;
}
};

--
Arnaud

Avatar
Sylvain
Michael wrote on 23/06/2006 13:34:

Après l'appel de t.SetGraph(pGraph), un appel à
IGraphBuilder::Release() est déclenché, ce qui fait que mon objet
pGraph est réinitialisé...


je ne pense pas que toto::pGraph soit réinitialisé.

avant point: il manque quelque chose dans le constructeur de foo() car
ici aucun objet COM implémentant IGraphBuilder n'est construit; avec
cette écriture pGraph est un ComPtr contenant un pointeur d'interface nul.
il faudrait un: pGraph.CoCreateInstance(CLSID_???); avant l'appel à
toto::setGraph

en supposant foo::pGraph créé (son ref count est à 1 après
CoCreateInstance):

- l'appel à toto::setGraph utilise un paramètre par valeur donc une
copie de pGraph est réalisée, ceci consiste à incrémenter son ref count
(=2) avant d'appeler setGraph avec cette "copie" (fausse copie car c'est
bien la même instance d'interface)

- dans toto::setGraph tu affectes ce param reçu à toto::pGraph,
toto:pGrapg est alors releasé, s'il existe, car il ne peut plus être
référencé par toto, puis l'interface reçue "graph" est incrémentée (=3)
et stocké dans pGraph
** ça là où le ComPtr sert, il évite d'avoir à tester le pGraph local et
le releasé si on l'avait référencé; ComPtr ne sert qu'à gérer les
addRef, Release à notre place **

- au retour de setGraph le paramètre temporaire transmis est "détruit"
par un release (=2), on a bien /un/ IGraphBuilder::Release mais 2 AddRef

interface est bien référencée par toto::pGraph et foo::pGraph
(même si, sur la portion de code vue, cela semble bizarre de transmettre
à une instance membre une autre donnée membre; toto a ebvie d'être vu
comme une inner classe utilitaire se chargeant de la réalisation
complète du travail).

Sylvain.

Avatar
Michael
(même si, sur la portion de code vue, cela semble bizarre de transmettre
à une instance membre une autre donnée membre; toto a ebvie d'être vu
comme une inner classe utilitaire se chargeant de la réalisation
complète du travail).


C'est exactement cela... toto initialise une partie du travail à faire sur
pGraph.

J'ai aussi une deuxième classe, disons toto2, membre de foo, à qui je passe
également le membre pGraph.

Et je mets en relation ces deux classes avec un toto.Connect(toto2) pour
qu'elles se mettent en relation l'une avec l'autre...

Ca te semble bizarre parce qu'il existe une manière de faire plus correcte?

Avatar
Michael
N'est-ce pas plutôt l'objet COM lui-même qui gère son comptage de
référence avec AddRef() qui incrémente le compteur et Release() qui le
décrémente ?


C'est tout à fait ça...

Avatar
Sylvain
Michael wrote on 25/06/2006 23:52:

Ca te semble bizarre parce qu'il existe une manière de faire plus correcte?


j'indiquais "au vu du fragment de code fourni".

si foo n'utilise pas souvent pGraph, elle pourrait le créer et le
distribuer à toto, toto2, ..., même quitte à le redemander à son
instance membre toto quand elle en a vraiment besoin.

au delà, si ces classes fournissent une implémentation de ton modèle
fournisseur de data / visualisateur; je pense que le modèle évoluera
encore car tu devras (dans le cadre d'utilisation de DX) en effet
multiplier les objets utilitaires et tous leur communiquer ton interface
de base - ce qui nuit forcément à l'isolation des roles.

enfin de mon expérience, hein ...

Sylvain.

Avatar
Michael
au delà, si ces classes fournissent une implémentation de ton modèle
fournisseur de data / visualisateur; je pense que le modèle évoluera
encore car tu devras (dans le cadre d'utilisation de DX) en effet
multiplier les objets utilitaires et tous leur communiquer ton interface
de base - ce qui nuit forcément à l'isolation des roles.


Il est vrai que même si pour l'instant ce modèle est bien défini, il pourrait
effectivement évoluer.

Mais je ne vois pas comment faire autrement.

L'objet pGraph doit être unique. Et la seule manière de le rendre unique me
semblait être de le créer avec la classe qui chapeaute le tout, et de le
passer aux inner classes qui l'utilisent.

Avatar
Sylvain
Michael wrote on 26/06/2006 12:15:

Mais je ne vois pas comment faire autrement.


avec les "contraintes" DirectX je n'ai pas non plus vraiment réussi.

L'objet pGraph doit être unique. Et la seule manière de le rendre unique me
semblait être de le créer avec la classe qui chapeaute le tout, et de le
passer aux inner classes qui l'utilisent.


et cela n'a pas de raison de ne pas être pertinent.

certains services peuvent déclarés comme un fournisseur/gérant
d'interface à connecter (par le chapeau) à pGraph, mais d'autres ont des
besoins qui justifient de posséder pGraph, so.

Sylvain.

Avatar
Michael
Mais je ne vois pas comment faire autrement.


avec les "contraintes" DirectX je n'ai pas non plus vraiment réussi.


C'est rassurant alors ;)

L'objet pGraph doit être unique. Et la seule manière de le rendre
unique me semblait être de le créer avec la classe qui chapeaute le
tout, et de le passer aux inner classes qui l'utilisent.


et cela n'a pas de raison de ne pas être pertinent.


Très bien, je garde donc ça en l'état...

Merci :)


Avatar
Arnaud Meurgues
Michael wrote:
N'est-ce pas plutôt l'objet COM lui-même qui gère son comptage de
référence avec AddRef() qui incrémente le compteur et Release() qui le
décrémente ?


C'est tout à fait ça...


Mais du coup, je ne comprends pas pourquoi un appel à Release
réinitialiserait l'objet.
Ai-je raté quelque chose dans l'énoncé de la question ?

--
Arnaud


Avatar
Michael
Mais du coup, je ne comprends pas pourquoi un appel à Release
réinitialiserait l'objet.
Ai-je raté quelque chose dans l'énoncé de la question ?


Ben je ne suis pas sûr, mais je crois que, lorsque Release amène le compteur
interne de l'objet COM IGraphBuilder à 0, celui-ci efface tous les filtres
qui y sont connectés...

C'est ce que j'appelle réinitialiser. L'objet est toujours présent, mais
vide. J'ai fait un abus de language en fait :)

1 2