TN065 : prise en charge d'une interface double pour les serveurs Automation OLE
Remarque
La note technique suivante n'a pas été mise à jour depuis son inclusion initiale dans la documentation en ligne. Par conséquent, certaines procédures et rubriques peuvent être obsolètes ou incorrectes. Pour obtenir les informations les plus récentes, il est recommandé de rechercher l'objet qui vous intéresse dans l'index de la documentation en ligne.
Cette note indique comment ajouter la prise en charge d'interfaces doubles à un serveur Automation MFC. L’exemple ACDUAL illustre la prise en charge de double interface, et l’exemple de code de cette note est extrait d’ACDUAL. Les macros décrites dans cette note, telles que DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART et IMPLEMENT_DUAL_ERRORINFO, font partie de l’exemple ACDUAL et sont disponibles dans MFCDUAL.H.
Interfaces doubles
Bien que l'objet OLE Automation vous permet d'implémenter une interface IDispatch
, une interface VTBL ou une double interface (qui comprend les deux), Microsoft recommande vivement d'implémenter des interfaces doubles pour tous les objets OLE Automation accessibles. Les interfaces doubles offrent d'importants avantages par rapport aux interfaces IDispatch
uniquement ou uniquement VTBL :
La liaison peut avoir lieu au moment de la compilation via l'interface VTBL, ou à l'aide de
IDispatch
.Les contrôleurs OLE Automation qui peuvent utiliser l'interface VTBL peuvent tirer parti de meilleures performances.
Les contrôleurs OLE Automation existants qui utilisent l'interface
IDispatch
continuent à fonctionner.Il est plus facile d'appeler l'interface VTBL en C++.
Les interfaces doubles sont requises pour la compatibilité avec les fonctionnalités de prise en charge des objets Visual Basic.
Ajout de la prise en charge des interfaces doubles à une classe basée sur CCmdTarget
Une interface double n'est qu'une simple interface personnalisée dérivée de IDispatch
. La méthode la plus simple pour implémenter la prise en charge des interfaces doubles dans une classe basée sur CCmdTarget
consiste d'abord à implémenter l'interface de dispatch normale sur votre classe à l'aide de MFC et ClassWizard, puis à ajouter l'interface personnalisée ultérieurement. Dans la plupart des cas, l'implémentation de l'interface utilisateur personnalisée délègue simplement à l'implémentation IDispatch
MFC.
D'abord, modifiez le fichier ODL pour que votre serveur définisse des interfaces pour les objets. Pour définir une interface double, vous devez utiliser une instruction Interface, au lieu de l'instruction ALTER DISPINTERFACE
que les assistants Visual C++ génèrent. Plutôt que de supprimer l'instruction DISPINTERFACE
existante, ajoutez une nouvelle instruction Interface. En conservant la forme DISPINTERFACE
, vous pouvez continuer à utiliser ClassWizard pour ajouter des propriétés et des méthodes à votre objet, mais vous devez ajouter les propriétés et les méthodes équivalentes à votre instruction Interface.
Une instruction d’interface pour une interface double doit avoir les attributs OLEAUTOMATION et DUAL , et l’interface doit être dérivée de IDispatch
. Vous pouvez utiliser l’exemple GUIDGEN pour créer un IID pour l’interface double :
[ uuid(0BDD0E81-0DD7-11cf-BBA8-444553540000), // IID_IDualAClick
oleautomation,
dual
]
interface IDualAClick : IDispatch
{
};
Une fois que vous avez mis en place l'instruction Interface, commencez à ajouter les entrées des méthodes et des propriétés. Pour les interfaces doubles, vous devez réorganiser les listes de paramètres afin que vos méthodes et fonctions d’accesseur de propriété dans l’interface double retournent un HRESULT et passent leurs valeurs de retour en tant que paramètres avec les attributs [retval,out]
. N’oubliez pas que pour les propriétés, vous devez ajouter à la fois une fonction d’accès en lecture (propget
) et en écriture (propput
) avec le même ID. Par exemple :
[propput, id(1)] HRESULT text([in] BSTR newText);
[propget, id(1)] HRESULT text([out, retval] BSTR* retval);
Après avoir défini vos méthodes et propriétés, vous devez ajouter une référence à l’instruction Interface dans l’instruction Coclass. Par exemple :
[ uuid(4B115281-32F0-11cf-AC85-444553540000) ]
coclass Document
{
dispinterface IAClick;
[default] interface IDualAClick;
};
Une fois votre fichier ODL mis à jour, utilisez le mécanisme de mappage d'interface de MFC pour définir une classe d'implémentation pour l'interface double dans votre classe d'objets et effectuer les entrées correspondantes dans le mécanisme QueryInterface
de MFC. Vous avez besoin d'une entrée dans le bloc INTERFACE_PART
pour chaque entrée dans l'instruction Interface de l'ODL, en plus des entrées valides pour une interface de dispatch. Chaque entrée ODL avec l’attribut propput a besoin d’une fonction nommée put_propertyname
. Chaque entrée avec l’attribut propget a besoin d’une fonction nommée get_propertyname
.
Pour définir une classe d'implémentation pour votre interface double, ajoutez un bloc DUAL_INTERFACE_PART
à la définition de classe d'objets. Par exemple :
BEGIN_DUAL_INTERFACE_PART(DualAClick, IDualAClick)
STDMETHOD(put_text)(THIS_ BSTR newText);
STDMETHOD(get_text)(THIS_ BSTR FAR* retval);
STDMETHOD(put_x)(THIS_ short newX);
STDMETHOD(get_x)(THIS_ short FAR* retval);
STDMETHOD(put_y)(THIS_ short newY);
STDMETHOD(get_y)(THIS_ short FAR* retval);
STDMETHOD(put_Position)(THIS_ IDualAutoClickPoint FAR* newPosition);
STDMETHOD(get_Position)(THIS_ IDualAutoClickPoint FAR* FAR* retval);
STDMETHOD(RefreshWindow)(THIS);
STDMETHOD(SetAllProps)(THIS_ short x, short y, BSTR text);
STDMETHOD(ShowWindow)(THIS);
END_DUAL_INTERFACE_PART(DualAClick)
Pour connecter l’interface double au mécanisme QueryInterface de MFC, ajoutez une entrée au mappage d’interface INTERFACE_PART
:
BEGIN_INTERFACE_MAP(CAutoClickDoc, CDocument)
INTERFACE_PART(CAutoClickDoc, DIID_IAClick, Dispatch)
INTERFACE_PART(CAutoClickDoc, IID_IDualAClick, DualAClick)
END_INTERFACE_MAP()
Ensuite, vous devez effectuer l'implémentation de l'interface. Dans la plupart des cas, vous pouvez déléguer à l'implémentation de l'interface IDispatch
MFC existante. Par exemple :
STDMETHODIMP_(ULONG) CAutoClickDoc::XDualAClick::AddRef()
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
return pThis->ExternalAddRef();
}
STDMETHODIMP_(ULONG) CAutoClickDoc::XDualAClick::Release()
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
return pThis->ExternalRelease();
}
STDMETHODIMP CAutoClickDoc::XDualAClick::QueryInterface(
REFIID iid,
LPVOID* ppvObj)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
return pThis->ExternalQueryInterface(&iid, ppvObj);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::GetTypeInfoCount(
UINT FAR* pctinfo)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
ASSERT(lpDispatch != NULL);
return lpDispatch->GetTypeInfoCount(pctinfo);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::GetTypeInfo(
UINT itinfo,
LCID lcid,
ITypeInfo FAR* FAR* pptinfo)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
ASSERT(lpDispatch != NULL);
return lpDispatch->GetTypeInfo(itinfo, lcid, pptinfo);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::GetIDsOfNames(
REFIID riid,
OLECHAR FAR* FAR* rgszNames,
UINT cNames,
LCID lcid,
DISPID FAR* rgdispid)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
ASSERT(lpDispatch != NULL);
return lpDispatch->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::Invoke(
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS FAR* pdispparams,
VARIANT FAR* pvarResult,
EXCEPINFO FAR* pexcepinfo,
UINT FAR* puArgErr)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
ASSERT(lpDispatch != NULL);
return lpDispatch->Invoke(dispidMember, riid, lcid,
wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr);
}
Pour les méthodes et les fonctions d’accesseur de propriété de vos objets, vous devez effectuer l’implémentation. Les fonctions de méthodes et de propriétés peuvent généralement redéléguer aux méthodes générées par ClassWizard. Toutefois, si vous installez des propriétés pour accéder aux variables directement, vous devez écrire du code pour obtenir/placer la valeur dans la variable. Par exemple :
STDMETHODIMP CAutoClickDoc::XDualAClick::put_text(BSTR newText)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
// MFC automatically converts from Unicode BSTR to
// Ansi CString, if necessary...
pThis->m_str = newText;
return NOERROR;
}
STDMETHODIMP CAutoClickDoc::XDualAClick::get_text(BSTR* retval)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
// MFC automatically converts from Ansi CString to
// Unicode BSTR, if necessary...
pThis->m_str.SetSysString(retval);
return NOERROR;
}
Passage des pointeurs d'interface double
Le passage du pointeur d'interface double n'est pas simple, surtout si vous devez appeler CCmdTarget::FromIDispatch
. FromIDispatch
fonctionne uniquement sur les pointeurs IDispatch
de MFC. Une méthode pour contourner ce point consiste à rechercher l'installation du pointeur IDispatch
d'origine mis en place par MFC et de passer le pointeur aux fonctions qui en ont besoin. Par exemple :
STDMETHODIMP CAutoClickDoc::XDualAClick::put_Position(
IDualAutoClickPoint FAR* newPosition)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDisp = NULL;
newPosition->QueryInterface(IID_IDispatch, (LPVOID*)&lpDisp);
pThis->SetPosition(lpDisp);
lpDisp->Release();
return NOERROR;
}
Avant de retransmettre un pointeur via la méthode d'interface double, vous devrez peut-être le convertir à partir du pointeur IDispatch
de MFC en pointeur d'interface double. Par exemple :
STDMETHODIMP CAutoClickDoc::XDualAClick::get_Position(
IDualAutoClickPoint FAR* FAR* retval)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
LPDISPATCH lpDisp;
lpDisp = pThis->GetPosition();
lpDisp->QueryInterface(IID_IDualAutoClickPoint, (LPVOID*)retval);
return NOERROR;
}
Inscription du type de bibliothèque de l'application
AppWizard ne génère pas de code pour enregistrer la bibliothèque de types d'une application serveur OLE Automation par le système. Lorsqu'il existe d'autres façons d'inscrire la bibliothèque de types, il est plus commode de faire en sorte que le registre de l'application enregistre la bibliothèque de types lorsqu'il met à jour ses informations de type OLE, c'est-à-dire chaque fois que l'application autonome est exécutée.
Pour inscrire la bibliothèque de types d'application chaque fois que l'application autonome est exécutée :
Ajoutez AFXCTL.H à votre fichier d'en-tête includes standard, STDAFX.H, pour accéder à la définition de la fonction
AfxOleRegisterTypeLib
.Dans la fonction
InitInstance
de votre application, recherchez l'appel àCOleObjectFactory::UpdateRegistryAll
. Après cet appel, ajoutez un appel àAfxOleRegisterTypeLib
, en spécifiant le LIBID correspondant à votre bibliothèque de types, ainsi que le nom de votre bibliothèque de types :// When a server application is launched stand-alone, it is a good idea // to update the system registry in case it has been damaged. m_server.UpdateRegistry(OAT_DISPATCH_OBJECT); COleObjectFactory::UpdateRegistryAll(); // DUAL_SUPPORT_START // Make sure the type library is registered or dual interface won't work. AfxOleRegisterTypeLib(AfxGetInstanceHandle(), LIBID_ACDual, _T("AutoClik.TLB")); // DUAL_SUPPORT_END
Modification des paramètres de génération des projets pour tenir compte des changements de la bibliothèque de types ;
Pour modifier les paramètres de génération d’un projet afin qu’un fichier d’en-tête contenant des définitions UUID soit généré par MkTypLib chaque fois que la bibliothèque de types est reconstruite :
Dans le menu Générer, cliquez sur Paramètres, puis sélectionnez le fichier ODL dans la liste des fichiers pour chaque configuration.
Cliquez sur l’onglet Types OLE et spécifiez un nom de fichier dans le champ nom de fichier d’en-tête de sortie. Utilisez un nom de fichier qui n'est pas déjà utilisé par votre projet, car MkTypLib remplacera un fichier déjà existant. Cliquez sur OK pour fermer la boîte de dialogue Générer Paramètres.
Pour ajouter les définitions UUID du fichier d’en-tête généré par MkTypLib à votre projet :
Incluez le fichier d’en-tête généré par MkTypLib dans votre fichier d’en-tête standard, stdafx.h.
Créez un fichier, INITIIDS.CPP, et ajoutez le à votre projet. Dans ce fichier, ajoutez votre fichier d'en-tête généré par MkTypLib après avoir inclus OLE2.H et INITGUID.H :
// initIIDs.c: defines IIDs for dual interfaces // This must not be built with precompiled header. #include <ole2.h> #include <initguid.h> #include "acdual.h"
Dans le menu Générer, cliquez sur Paramètres, puis sélectionnez INITIIDS. CPP à partir de la liste de fichiers pour chaque configuration.
Cliquez sur l’onglet C++, cliquez sur les en-têtes précompilés de catégorie, puis sélectionnez la case d’option Not using precompiled headers. Cliquez sur OK pour fermer la boîte de dialogue Générer Paramètres.
Spécification du nom de classe d'objets correct dans une bibliothèque de types
Les assistants fournis avec Visual C++ utilisent incorrectement le nom de classe d'implémentation pour spécifier la coclasse dans le fichier ODL du serveur pour les classes pouvant être créées par OLE. Si cette opération fonctionne, le nom de classe d'implémentation n'est probablement pas le nom de classe que vous souhaitez que les utilisateurs de votre objet utilisent. Pour spécifier le bon nom, ouvrez le fichier ODL, recherchez chaque instruction Coclass et remplacez le nom de classe d'implémentation par le nom externe correct.
Notez que lorsque l’instruction de coclasse est modifiée, les noms de variables des CLSIDdans le fichier d’en-tête généré par MkTypLib changent en conséquence. Vous devez mettre à jour votre code afin d'utiliser les nouveaux noms de variables.
Gestion des exceptions et des interfaces d'erreur Automation
Les méthodes et les fonctions d’accesseur de propriété de votre objet automation peuvent lever des exceptions. Dans ce cas, vous devez les gérer dans votre implémentation à double interface et transmettre des informations sur l’exception au contrôleur via l’interface de gestion des erreurs OLE Automation. IErrorInfo
Cette interface fournit des informations d'erreur détaillées et contextuelles à travers à la fois de IDispatch
et des interfaces VTBL. Pour indiquer qu’un gestionnaire d’erreurs est disponible, vous devez implémenter l’interface ISupportErrorInfo
.
Pour illustrer le mécanisme de gestion des erreurs, supposez que les fonctions générées par ClassWizard permettant d'implémenter la prise en charge de la distribution (dispatch) standard lèvent des exceptions. L’implémentation IDispatch::Invoke
de MFC intercepte généralement ces exceptions et les convertit en une structure EXCEPTINFO retournée par le biais de l’appel Invoke
. Toutefois, lorsque l'interface VTBL est utilisée, vous êtes chargé d'intercepter les exceptions vous-même. Voici un exemple de protection des méthodes d'interface double :
STDMETHODIMP CAutoClickDoc::XDualAClick::put_text(BSTR newText)
{
METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
TRY_DUAL(IID_IDualAClick)
{
// MFC automatically converts from Unicode BSTR to
// Ansi CString, if necessary...
pThis->m_str = newText;
return NOERROR;
}
CATCH_ALL_DUAL
}
CATCH_ALL_DUAL
prend soin de retourner un code d'erreur approprié lorsqu'une exception se produit. CATCH_ALL_DUAL
convertit une exception MFC en informations de gestion des erreurs OLE Automation à l’aide de l’interface ICreateErrorInfo
. (Un exemple de CATCH_ALL_DUAL
macro se trouve dans le fichier MFCDUAL. H dans l’exemple ACDUAL . La fonction qu’elle appelle pour gérer les exceptions, DualHandleException
se trouve dans le fichier MFCDUAL. CPP.) CATCH_ALL_DUAL
détermine le code d’erreur à retourner en fonction du type d’exception qui s’est produit :
COleDispatchException : dans ce cas,
HRESULT
est construit à l’aide du code suivant :hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, (e->m_wCode + 0x200));
Cela crée un
HRESULT
spécifique à l'interface qui a provoqué l'exception. Le code d'erreur est décalé de 0x200 pour éviter tout conflit avec lesHRESULT
définis par le système pour les interfaces OLE standard.CMemoryException : dans ce cas,
E_OUTOFMEMORY
est retourné.Toute autre exception : dans ce cas,
E_UNEXPECTED
elle est retournée.
Pour indiquer que le gestionnaire d’erreurs OLE Automation est utilisé, vous devez également implémenter l’interface ISupportErrorInfo
.
Tout d’abord, ajoutez du code à votre définition de classe Automation pour l’afficher.ISupportErrorInfo
Ensuite, ajoutez du code au mappage d’interface de votre classe Automation pour associer la ISupportErrorInfo
classe d’implémentation au mécanisme de QueryInterface
MFC. L’instruction INTERFACE_PART
correspond à la classe définie pour ISupportErrorInfo
.
Enfin, implémentez la classe définie pour prendre en charge ISupportErrorInfo
.
(Le L’exemple ACDUAL contient trois macros pour vous aider à effectuer ces trois étapes, DECLARE_DUAL_ERRORINFO
et DUAL_ERRORINFO_PART
, toutes IMPLEMENT_DUAL_ERRORINFO
contenues dans MFCDUAL.H.)
L’exemple suivant implémente une classe définie pour prendre en charge ISupportErrorInfo
. CAutoClickDoc
est le nom de votre classe Automation et IID_IDualAClick
est l’IID de l’interface qui est la source des erreurs signalées via l’objet d’erreur OLE Automation :
STDMETHODIMP_(ULONG) CAutoClickDoc::XSupportErrorInfo::AddRef()
{
METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
return pThis->ExternalAddRef();
}
STDMETHODIMP_(ULONG) CAutoClickDoc::XSupportErrorInfo::Release()
{
METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
return pThis->ExternalRelease();
}
STDMETHODIMP CAutoClickDoc::XSupportErrorInfo::QueryInterface(
REFIID iid,
LPVOID* ppvObj)
{
METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
return pThis->ExternalQueryInterface(&iid, ppvObj);
}
STDMETHODIMP CAutoClickDoc::XSupportErrorInfo::InterfaceSupportsErrorInfo(
REFIID iid)
{
METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo)
return (iid == IID_IDualAClick) S_OK : S_FALSE;
}