Cet article détaille la création d'un complément Microsoft Office à l'aide de la bibliothèque ATL (Active Template Library), en couvrant la connexion au processus hôte via l'interface IDTExtensibility2, l'enregistrement du complément dans le registre Windows, et l'intégration d'un ruban personnalisé par le biais de IRibbonExtensibility. L'environnement de développement utilisé est Visual Studio 2010.
Fondamentaux : le cycle de vie d'un complément Office
Le cycle de vie d'un complément Offfice est gouverné par l'interface IDTExtensibility2. Celle-ci expose cinq méthodes qui jalonnent l'existence du complément, de son instanciation par l'application hôte jusqu'à sa déconnexion :
- OnConnection — déclenchée lors du chargement du complément.
- OnDisconnection — déclenchée lors du déchargement.
- OnAddInsUpdate — déclenchée quand la liste des compléments change.
- OnStartupComplete — déclenchée quand l'application hôte a terminé son démarrage.
- OnBeginShutdown — déclenchée au début de la fermeture de l'application hôte.
Ces méthodes ne sont pas spécifiques à la suite Microsoft Office ; tout application hôte compatible COM peut les utiliser. Associée à l'interface IRibbonExtensibility, elles forment la base nécessaire pour développer un complément fonctionnel.
Référence MSDN : IDTExtensibility2 Interface
Création du projet ATL
Initialisation du projet
Dans Visual Studio, créez un nouveau projet ATL nommé OfficeAtlAddin. Le type d'application doit être une DLL (Dynamic Link Library). Les autres options peuvent rester par défaut.
Ajoutez ensuite un objet ATL simple (Add Class → ATL Simple Object) avec les paramètres suivants :
- Short Name :
HostConnector - ProgID :
OfficeAtlAddin.HostConnector
Cet objet servira de point d'entrée pour toutes les interactions avec l'application Office hôte.
Implémentation de IDTExtensibility2
Dans la vue de classes, faites un clic droit sur CHostConnector et sélectionnez Implement Interface. Dans l'assistant, choisissez la bibliothèque de types Microsoft Add-In Designer <1.0> et ajoutez l'interface _IDTExtensibility2.
Après cette opération, le fichier HostConnector.h contient désormais les déclarations des cinq méthodes :
class ATL_NO_VTABLE CHostConnector :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CHostConnector, &CLSID_HostConnector>,
public IDispatchImpl<IHostConnector, &IID_IHostConnector, &LIBID_OfficeAtlAddinLib>,
public IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &LIBID_AddInDesignerObjects, 1>
{
public:
STDMETHOD(OnConnection)(LPDISPATCH appHost, ext_ConnectMode connMode, LPDISPATCH addinInstance, SAFEARRAY** psaCustom)
{
return S_OK;
}
STDMETHOD(OnDisconnection)(ext_DisconnectMode discMode, SAFEARRAY** psaCustom)
{
return S_OK;
}
STDMETHOD(OnAddInsUpdate)(SAFEARRAY** psaCustom)
{
return S_OK;
}
STDMETHOD(OnStartupComplete)(SAFEARRAY** psaCustom)
{
return S_OK;
}
STDMETHOD(OnBeginShutdown)(SAFEARRAY** psaCustom)
{
return S_OK;
}
};
Attention : la méthode OnConnection doit retourner S_OK et non E_NOTIMPL. Sans cela, le complément sera bien enregistré mais Office signalera une erreur de chargement COM.
Enregistrement du complément dans Microsoft Word
Pour que Word reconnaisse et charge le complément, il faut ajouter des entrées dans le registre Windows, sous la clé :
HKEY_CURRENT_USER\Software\Microsoft\Office\Word\Addins
Trois valeurs sont obligatoires :
- FriendlyName (REG_SZ) — nom affiché du complément
- Description (REG_SZ) — description textuelle
- LoadBehavior (REG_DWORD) — mode de chargement. La valeur
3indique un chargement automatique au démarrage.
Dans un projet ATL, l'enregistrement s'effectue via un fichier .rgs. Ajoutez le contenu suivant à la fin de HostConnector.rgs :
HKCU
{
NoRemove Software
{
NoRemove Microsoft
{
NoRemove Office
{
NoRemove Word
{
NoRemove Addins
{
ForceRemove OfficeAtlAddin.HostConnector
{
val Description = s 'Complément ATL pour Office'
val FriendlyName = s 'Office ATL Addin'
val LoadBehavior = d 3
}
}
}
}
}
}
}
Après compilation et enregistrement, le complément doit apparaître dans la liste Compléments COM de Word. Un point d'arrêt dans OnConnection permet de vérifier que Word l'invoque bien au démarrage.
Note : le même mécanisme s'applique à Outlook, Excel et PowerPoint en remplaçant simplement Word par le nom de l'application concernée dans le chemin du registre.
Intégration du ruban personnalisé
Implémentation de IRibbonExtensibility
Procédez comme pour _IDTExtensibility2 : clic droit sur CHostConnector → Implement Interface, puis sélectionnez la bibliothèque Microsoft Office 14.0 Object Library <2.5> et ajoutez l'interface IRibbonExtensibility.
La version minimum de la bibliothèque de types correspond à Office 2007 (Microsoft Office 12.0 Object Library <2.4>). Pour Office 2013, utilisez la version 2.7.
L'en-tête HostConnector.h intègre alors la déclaration suivante :
public IDispatchImpl<IRibbonExtensibility, &__uuidof(IRibbonExtensibility), &LIBID_Office, 2, 5>
// IRibbonExtensibility
STDMETHOD(GetCustomUI)(BSTR ribbonId, BSTR* ribbonXml)
{
return E_NOTIMPL;
}
Des erreurs de compilation peuvent survenir en raison de conflits de types liés à l'import de MSO.DLL. Dans stdafx.h, ajustez la directive #import :
#import "C:\\Program Files\\Common Files\\Microsoft Shared\\OFFICE14\\MSO.DLL" \
raw_interfaces_only, raw_native_types, no_namespace, named_guids, auto_search, \
exclude("IAccessible"), exclude("DocumentProperties")
Chargement du XML du ruban
Copiez un fichier CustomRibbon.xml dans le répertoire du projet et ajoutez-le aux ressources (type XML). La méthode GetCustomUI doit charger ce contenu et le retourner à Office pour qu'il dessine le ruban.
STDMETHOD(GetCustomUI)(BSTR ribbonId, BSTR* ribbonXml)
{
if (!ribbonXml)
return E_POINTER;
*ribbonXml = LoadXmlFromResource(IDR_XML1);
return S_OK;
}
HRESULT ExtractResource(int resId, LPCTSTR resType, LPVOID* outData, DWORD* outSize)
{
HMODULE modHandle = _AtlBaseModule.GetModuleInstance();
if (!modHandle)
return E_UNEXPECTED;
HRSRC resInfo = FindResource(modHandle, MAKEINTRESOURCE(resId), resType);
if (!resInfo)
return HRESULT_FROM_WIN32(GetLastError());
HGLOBAL resData = LoadResource(modHandle, resInfo);
if (!resData)
return HRESULT_FROM_WIN32(GetLastError());
*outSize = SizeofResource(modHandle, resInfo);
*outData = LockResource(resData);
return S_OK;
}
BSTR LoadXmlFromResource(int resId)
{
LPVOID rawData = nullptr;
DWORD byteCount = 0;
HRESULT hr = ExtractResource(resId, TEXT("XML"), &rawData, &byteCount);
if (FAILED(hr))
return nullptr;
CComBSTR bstrResult(byteCount, reinterpret_cast<LPCSTR>(rawData));
return bstrResult.Detach();
}
Concernant le contenu XML du ruban, deux points méritent attention :
- Encodage : pour utiliser des libellés en caractères chinois ou non ASCII, précisez
encoding="gb2312"(ou l'encodage approprié) dans la déclaration XML. - Espace de noms : Office 2007 utilise
xmlns="http://schemas.microsoft.com/office/2006/01/customui"; Office 2010 et versions ultérieures utilisentxmlns="http://schemas.microsoft.com/office/2009/07/customui".
Gestion des rappels (callbacks) du ruban
Contrairement à VSTO, ATL ne dispose pas d'un support natif pour le routage des commandes du ruban. La technique consiste à rediriger l'interface IDispatch vers une interface personnalisée et à surcharger la méthode Invoke pour intercepter les appels de rappel.
1. Définition des attributs onAction dans le XML
<?xml version="1.0" encoding="gb2312"?>
<customUI onLoad="Ribbon_Load" xmlns="http://schemas.microsoft.com/office/2006/01/customui">
<ribbon>
<tabs>
<tab id="TabAddIns" label="Démo ATL">
<group id="grpMain" label="Groupe principal">
<button id="btnAction1"
label="Action 1"
imageMso="HappyFace"
onAction="CallbackBtn1"
size="large" />
<button id="btnAction2"
label="Action 2"
onAction="CallbackBtn2"
showImage="false" />
</group>
</tab>
</tabs>
</ribbon>
</customUI>
2. Modification de la table d'interfaces COM
Il faut rediriger IDispatch vers l'interface personnalisée IHostConnector plutôt que vers _IDTExtensibility2 :
BEGIN_COM_MAP(CHostConnector)
COM_INTERFACE_ENTRY(IHostConnector)
COM_INTERFACE_ENTRY2(IDispatch, IHostConnector)
COM_INTERFACE_ENTRY(_IDTExtensibility2)
COM_INTERFACE_ENTRY(IRibbonExtensibility)
END_COM_MAP()
3. Déclaration des méthodes de rappel dans le fichier IDL
interface IHostConnector : IDispatch {
[id(1)] HRESULT CallbackBtn1([in] IDispatch* ribbonControl);
[id(2)] HRESULT CallbackBtn2([in] IDispatch* ribbonControl);
};
4. Implémentation des méthodes de rappel
STDMETHODIMP CHostConnector::CallbackBtn1(IDispatch* ribbonControl)
{
MessageBoxW(nullptr, L"Bouton 1 cliqué", L"ATL Addin", MB_OK);
return S_OK;
}
STDMETHODIMP CHostConnector::CallbackBtn2(IDispatch* ribbonControl)
{
MessageBoxW(nullptr, L"Bouton 2 cliqué", L"ATL Addin", MB_OK);
return S_OK;
}
5. Surcharge de la méthode Invoke
Office appelle IDispatch::Invoke avec un identifiant de dispatch (DISPID) correspondant à l'attribut onAction du ruban. La surcharge de Invoke permet de router ces appels vers les méthodes appropriées :
STDMETHOD(Invoke)(DISPID dispidMember, const IID& riid, LCID lcid,
WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult,
EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
switch (dispidMember)
{
case 1:
CallbackBtn1(nullptr);
break;
case 2:
CallbackBtn2(nullptr);
break;
default:
break;
}
return S_OK;
}
Après compilation, le ruban personnalisé apparaît dans Word avec les deux boutons. Leurs clics déclenchent respectivement les boîtes de message associées.
L'approche ATL demande une maîtrise fine de COM, des directives #import et du registre Windows. Les erreurs liées à l'import de bibliothèques de types sont souvent ardues à diagnostiquer en raison de la rareté de la documentation en ligne sur ce sujet.