Traduction de l'article: L'utilisation de MAPI Extended au sein de vos applications
Date de publication : 04/09/2008
Par
Louis-Guillaume MORAND (Page perso de Louis-Guillaume MORAND)
Introduction
0-A. Bénéfices
0-B. Drawbacks
0-C. Background
I. Préparation de la solution
I-A. Pré-requis
I-B. Créer une solution
II. Le truc Outlook
III. La bibliothèque C++ mixte
IV. Ajout de fonctionnalités
IV-A. Le fichier d'entête
IV-B. Initialisation et nettoyage
V. The usage
VI. Lire l'entête
VII. Lire le nom du profil Outlook courant.
VIII. Définit la couleur du label de rendez-vous
IX. Notes
Introduction
Dans cet article, vous allez apprendre comment implémenter une librairie C++ qui aidera à utiliser les fonctions Extended MAPI qui ne sont pas disponibles via Outlook Object Model. Dans le passé, vous deviez acheter une bibliothèque supplémentaire ou deviez utiliser la technologie COM et créer cette DLL native par vous-même. Une autre façon est de réinventer la roue et de définir toutes les interfaces du côté .Net. Ceci vous autorisera d'utiliser ces interfaces directement depuis le C# ou VB.Net en triant les structures .Net et fonctions et structures non managées. C'est alors que vient une superbe bibliothèque C++ avec dans laquelle vous pouvez aisément mélanger du code manage et du code natif dans un unique environnement.
0-A. Bénéfices
- Toutes les interfaces et fonctions sont déjà définies dans le SDK Windows
- Nous n'avons pas besoin d'exposer une interface COM
- Aucun composant externe de ne doit pas être déployé ou enregistré sur les systèmes cibles
- Classes et interfaces .NET exposées publiquement
- Aucunes classes wrapper nécessaires pour les composants COM externes
- Entièrement intégré dans votre solution, avec le support du Debug
0-B. Drawbacks
- Une connaissance du C++ est requise
- Une connaissance des interfaces Extended MAPI est requise
0-C. Background
Le Modèle Outlook Object Model (OOM) est puissant et fournit l'accès à plusieurs fonctionnalités qui utilisent et manipulent les données stockées dans Outlook et Exchange Server. Pourtant, chaque développeur Outlook sérieux arrive à un point où il a besoin de certaines fonctionnalités, propriétés ou champs qui ne sont pas accessibles via le OOM ou qui sont bloqués par des restrictions de sécurité. Dans le passé, vous deviez utiliser une bibliothèque COM externe très réputée comme:
Pendant que ces bibliothèques sont très puissantes et flexibles car sont utilisables depuis tout langage de programmation, elles sont néanmoins des bibliothèques externes qui ont besoin d'être déployées et enregistrées avec votre application. Ceci peut parfois être problématique. Quand vous utilisez Extended MAPI depuis .NET, il existe une autre bibliothèque nommée:
I. Préparation de la solution
Avant que vous ne commenciez à utiliser Extended MAPI, vous devez installer certains éléments obligatoires sur votre machine de développement.
I-A. Pré-requis
Note: Si vous avez déjà installé une ancienne version du SDK Microsoft Exchange Server 5.5, vous n'avez pas besoin de la plateforme SDK.
I-B. Créer une solution
Pour démontrer comment cela marche sous le capot, commencez avec une simple application Windows Form qui créé un nouvel email et ajoute un nouveau entête SMTP à celui-ci. Dans cet exemple, vous trouverez un projet disponible en C# et en VB.Net. Ainsi, ouvrez-le avec Visual Studio, choisissez le langage de votre choix et créez une application ressemblant à ceci :
Imaginons que vous soyez un développeur Outlook et vous voulez utiliser le modèle Outlook Object. Vous devez naturellement dans les références de votre projet ajouter les références suivantes:
- Microsoft Office 1X.0 Object Library
- Microsoft Outlook 1X.0 Object Library Note: cela dépend de la version d'Office installée.
Quand vous avez fini avec l'ajout des références, vous pouvez importer les espaces de nom dans votre projet de cette façon:
| Code VB.NET |
Imports Office = Microsoft.Office.Core
Imports Outlook = Microsoft.Office.Interop.Outlook
|
| Code C# |
using Office = Microsoft.Office.Core;
using Outlook = Microsoft.Office.Interop.Outlook;
|
II. Le truc Outlook
Le but est de créer un nouvel email et d'y attacher un nouvel header SMTP. Regardons d'abord comment créer un nouvel email avec le Modèle Outlook Object:
| Modèle Outlook Object |
Private Sub btnSend_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnSend.Click
Dim outlookApplication As Outlook.Application = New Outlook.Application()
Dim olNamespace As Outlook.NameSpace = _
okApplication.GetNamespace("MAPI")
olNamespace.Logon(, , True, True)
Dim newMail As Outlook.MailItem = _
lookApplication.CreateItem(Outlook.OlItemType.olMailItem)
newMail.To = tbxRecipientAddress.Text
newMail.Subject = tbxSubject.Text
newMail.Body = tbxMessage.Text
newMail.Send()
newMail = Nothing
olNamespace.Logoff()
outlookApplication = Nothing
GC.Collect()
GC.WaitForPendingFinalizers()
End Sub
|
La version C++
| Version C++ |
private void btnSend_Click(object sender, EventArgs e)
{
object missing = System.Reflection.Missing.Value;
Outlook.Application outlookApplication = new Outlook.Application();
Outlook.NameSpace nameSpace = outlookApplication.GetNamespace("MAPI");
nameSpace.Logon(missing, missing, true, true);
Outlook.MailItem newMail = _
lookApplication.CreateItem(Outlook.OlItemType.olMailItem);
newMail.To = tbxRecipientAddress.Text;
newMail.Subject = tbxSubject.Text;
newMail.Body = tbxMessage.Text;
newMail.Send();
newMail = null;
olNamespace.Logoff();
outlookApplication = Nothing;
GC.Collect();
GC.WaitForPendingFinalizers();
}
|
Ce petit snippet devrait simplement envoyer un Email au destinataire que vous aurez renseigné dans le champ To:
III. La bibliothèque C++ mixte
Maintenant, vous devez ajouter un nouveau projet à la solution. Choisissez Fichier > Ajouter > Nouveau projet et sélectionnez C++ CLR Class Library.
J'ai nommé la bibliothèque MAPIConcubine parce qu'elle me donner que le Model Outlook Object Model ne me donnera pas. Bien sûr, vous pouvez lui donner le nom de votre choix. Après que vous ayez ajouté la bibliothèque C++ à la solution, ouvrez les propriétés du projet et ajouter mapi32.lib en tant que fichier d'entrée sur le linker.
L'étape suivante est d'ajouter les fichiers d'entête MAPI C++ à votre projet. Ouvrez le fichier stdafx.h et ajouter les entêtes suivants dans ce dernier, juste après la déclaration #pragma once.
|
#pragma once
#include <MAPIX.h>
#include <MapiUtil.h>
#include <MAPIGuid.h>
#include <MAPITags.h>
|
Compilez ensuite le projet et regardez si cela compile correctement. Si tout se passe bien, vous pouvez continuer. Sinon, vous avez probablement oublié un pré-requis comme le SDK Windows contenant les fichiers d'entête C++ définis dans StdAfx.h.
IV. Ajout de fonctionnalités
Maintenant, vous devriez avoir une bibliothèque .Net compilée et liée à la bibliothèque Extended MAPI. Allez-y et commencez à ajouter des fonctionnalités à vos projets .Net. Rappelez-vous votre but initial, vous voulez ajouter un entête http à un mail sortant et envoyé via Outlook. Maintenant, vous pouvez continuer et fournir une interface au monde extérieur, qui expose les fonctionnalités désirées.
IV-A. Le fichier d'entête
Les classes C++ ont généralement un fichier d'entête et un fichier de code. Ici, dans votre bibliothèque, le fichier d'entête ressemble à ce qui suit. Vous avez défini une classe .Net, Fabric, avec une méthode publique nommée AddMessageHeader. La méthode prend trois paramètres : le premier est MPIObject, que vous pouvez obtenir depuis les Items Outlook en tant que propriété. Cette propriété fourni l'accès à l'interface MAPI IUnknown.
|
#pragma once
using namespace System;
using namespace System::Runtime::InteropServices;
namespace MAPIConcubine
{
public ref class Fabric
{
private:
~Fabric();
String^ GetErrorText(DWORD dw);
public:
Fabric();
void AddMessageHeader(Object^ mapiObject, String^ headerName,
String^ headerValue);
};
}
|
Vous exposez seulement les méthodes conformes .Net à l'extérieur. Cela rend facile d'utiliser cette bibliothèque depuis n'importe quel projet .Net. Maintenant vous avez défini l'interface et vous pouvez aller implémenter des fonctionnalités derrière celle-ci. Regardons l'implémentation qui peut être trouvé dans le fichier MAPIConcubine.cpp.
L'idée est d'obtenir l'interface IUnknown depuis l'objet Outlook, suivi par le IMessage et, non pas des moindre, l'interface IMAPIProp. Ici, la bibliothèque C++ joue un rôle maitre. Vous pouvez maintenant accéder aux fichiers d'entête où toutes les interfaces sont définies et alors compilez à 'laide de bibliothèques externes comme mapi32.lib ou toute autre bibliothèque native non managée. Continuez un peu dans le code, là où vous pouvez voir un mix entre les pointeurs tradictionnels du C++ et le code non managé (new et *), et le code .Net managé (gcnew and ^). Le piège que vous pouvez voir ici est comment vous passez les variables de chaînes de caractères .Net en LPWSTR non managé, un pointeur vers un null Unicode terminant chaque tableau de caractères. .Net vous donne déjà les méthodes correctes dans l'espace de nom System.Runtime.Interop.
|
void Fabric::AddMessageHeader(Object^ mapiObject, String^ headerName,
String^ headerValue)
{
IUnknown* pUnknown = 0;
IMessage* pMessage = 0;
IMAPIProp* pMAPIProp = 0;
MAPINAMEID* pNamedProp = 0;
SPropTagArray* lpTags = 0;
IntPtr pHeaderName = Marshal::StringToHGlobalUni (headerName);
IntPtr pHeaderValue = Marshal::StringToHGlobalUni (headerValue);
if (mapiObject == nullptr)
throw gcnew System::ArgumentNullException
("mapiObject","The MAPIObject must not be null!");
try
{
pUnknown = (IUnknown*)Marshal::GetIUnknownForObject
(mapiObject).ToPointer ();
if ( pUnknown->QueryInterface
(IID_IMessage, (void**)&pMessage) != S_OK)
throw gcnew Exception
("QueryInterface failed on IUnknown for IID_Message");
if ( pMessage->QueryInterface
(IID_IMAPIProp, (void**)&pMAPIProp) != S_OK)
throw gcnew Exception
("QueryInterface failed on IMessage for IID_IMAPIProp");
if (pMAPIProp == 0)
throw gcnew Exception
("Unknown Error on receiving the Pointer to MAPI Properties.");
GUID magicId = { 0x00020386, 0x0000, 0x0000,
{ 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };
if (MAPIAllocateBuffer(sizeof(MAPINAMEID),
(LPVOID*)&pNamedProp) != S_OK)
throw gcnew Exception("Could not allocate memory buffer.");
pNamedProp->lpguid = (LPGUID)&magicId;
pNamedProp->ulKind = MNID_STRING;
pNamedProp->Kind.lpwstrName = (LPWSTR) pHeaderName.ToPointer();
if(pMAPIProp->GetIDsFromNames
(1, &pNamedProp, MAPI_CREATE, &lpTags ) != S_OK)
throw gcnew Exception(String::Format
("Error retrieving GetIDsFromNames: {0}.",headerName) );
SPropValue value;
value.ulPropTag = PROP_TAG(PT_UNICODE, PROP_ID
(lpTags->aulPropTag[0]));
value.Value.LPSZ = (LPWSTR) pHeaderValue.ToPointer();
if( HrSetOneProp(pMAPIProp, &value) != S_OK)
throw gcnew Exception(String::Format
("Error setting Header: {0}.",headerName) );
}
catch (Exception^ ex)
{
DWORD dw = GetLastError();
throw gcnew Exception(GetErrorText(dw),ex);
}
finally
{
if (pNamedProp != 0) MAPIFreeBuffer(pNamedProp);
if (lpTags != 0) MAPIFreeBuffer(lpTags);
Marshal::FreeHGlobal (pHeaderName);
Marshal::FreeHGlobal (pHeaderValue);
if (pMAPIProp!=0) pMAPIProp->Release();
if (pMessage!=0) pMessage->Release();
if (pUnknown!=0) pUnknown->Release();
}
}
|
La chose intéressante est comment vous implémentez un bloc try/catch/finally. Vous devez faire attention à ne pas obtenir une fuite mémoire, et donc vous devez placer votre code de nettoyage dans la section finally. Vous pouvez aussi lancer (throw) des exceptions aux classes .Net appelantes. Compilez et vous verrez que vous oubliez quelque chose.
C'est la façon dont vous devez accéder aux ID MAPI définis dans les fichiers d'entête MAPI. Il reste juste une action complémentaire à faire maintenant : allez dans votre fichier Stdafx.h et incluez les définitions suivantes. Vous devez inclure ces définitions pour chaque ID MAPI que vous utilisez
|
#pragma once
#define INITGUID
#define USES_IID_IMAPIProp
#define USES_IID_IMessage
#include <MAPIX.h>
#include <MapiUtil.h>
#include <MAPIGuid.h>
#include <MAPITags.h>
|
IV-B. Initialisation et nettoyage
Ahh! Attendez, vous oubliez quelque chose. A chaque fois que vous voulez utiliser quelque chose depuis Mapi Extended, vous devez l'initialiser. Ainsi, vous devez ajouter une routine de construction et une routine de nettoyage à la bibliothèque. Voici, ce à quoi ça ressemble:
|
Fabric::Fabric ()
{
MAPIInitialize(0);
}
Fabric::~Fabric ()
{
MAPIUninitialize();
}
|
Egalement, pour une gestion facile des erreurs et du débogage, vous devriez ajouter une méthode qui retourne un message lisible pour l'humain, à l'appelant. Cette méthode pourrait ressembler au code suivant :
|
String^ Fabric::GetErrorText(DWORD dw)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
String^ result = Marshal::PtrToStringAuto (IntPtr::IntPtr (lpMsgBuf));
LocalFree(lpMsgBuf);
return result;
}
|
V. The usage
Passons aux clients .Net qui pourraient utiliser cette bibliothèque. Ouvrez l'application WinForm - ou simplement VSTO ou un AddIn d'extensibilité - que vous avez créé plus tôt et ajoutez lui une nouvelle référence. Allez dans l'onglet Projets et sélectionné le projet existant : MAPIConcubine.
Maintenant, vous pouvez changer le code de votre application Windows Forms et ajoutez lui la nouvelle fonctionnalité. Ca ressemble simplement à ceci:
|
Dim newMail As Outlook.MailItem = _
outlookApplication.CreateItem(Outlook.OlItemType.olMailItem)
Dim fabric As MAPIConcubine.Fabric = New MAPIConcubine.Fabric()
fabric.AddMessageHeader(newMail.MAPIOBJECT, tbxHeaderName.Text, _
tbxHeaderValue.Text)
fabric = Nothing
newMail.To = tbxRecipientAddress.Text
newMail.Subject = tbxSubject.Text
newMail.Body = tbxMessage.Text
newMail.Send()
|
Version C++
|
Outlook.MailItem newMail = outlookApplication.CreateItem
(Outlook.OlItemType.olMailItem) as Outlook.MailItem;
MAPIConcubine.Fabric fabric = new MAPIConcubine.Fabric();
fabric.AddMessageHeader(newMail.MAPIOBJECT, tbxHeaderName.Text,
tbxHeaderValue.Text);
fabric = null;
newMail.To = tbxRecipientAddress.Text;
newMail.Subject = tbxSubject.Text;
newMail.Body = tbxMessage.Text;
newMail.Send();
|
C'est tout. Le reste est très facile. Vous pouvez vérifier les entêtes du mail dans la boîte de réception et vous y verrez vos propres entêtes. Dans Outlook, vous pouvez les en ouvrant l'email et en affichant le menu des Options.
VI. Lire l'entête
Dans ce chapitre, vous aller apprendre comment retrouver les entêtes sauvegardés et comment retrouver tous les entêtes de message de transport. La théorie est simple : vous utilisez le GUID pour accéder aux entêtes Internet et la méthode GetIDSFromNames. Cela vous permet de retrouver le bon propTagId que vous utilisez dans la méthode HrGetOneProp. Si vous êtes dans un environnement Exchange, vous pouvez lire les entêtes d'email personnalisé avec le code suivant : Notez que pour une lecture aisée, le bloc catch/finally a été supprimé. Comme plus haut, référez vous aux fichiers sources pour plus de détails.
|
String^ Fabric::ReadMessageHeader(Object^ mapiObject, String^ headerName)
{
IUnknown* pUnknown = 0;
IMessage* pMessage = 0;
IMAPIProp* pMAPIProp = 0;
MAPINAMEID* pNamedProp = 0;
SPropTagArray* lpTags = 0;
LPSPropValue lpSPropValue = 0;
IntPtr pHeaderName = Marshal::StringToHGlobalUni (headerName);
if (mapiObject == nullptr)
throw gcnew System::ArgumentNullException ("mapiObject","The MAPIObject must not be null!");
try
{
pUnknown = (IUnknown*)Marshal::GetIUnknownForObject(mapiObject).ToPointer ();
if ( pUnknown->QueryInterface (IID_IMessage, (void**)&pMessage) != S_OK)
throw gcnew Exception("QueryInterface failed on IUnknown for IID_Message");
if ( pMessage->QueryInterface (IID_IMAPIProp, (void**)&pMAPIProp) != S_OK)
throw gcnew Exception("QueryInterface failed on IMessage for IID_IMAPIProp");
if (pMAPIProp == 0)
throw gcnew Exception("Unknown Error on receiving the Pointeur versMAPI Properties.");
GUID magicId =
{
x00020386, 0x0000, 0x0000,
{
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46
}
};
if (MAPIAllocateBuffer(sizeof(MAPINAMEID), (LPVOID*)&pNamedProp) != S_OK)
throw gcnew Exception("Could not allocate memory buffer.");
pNamedProp->lpguid = (LPGUID)&magicId;
pNamedProp->ulKind = MNID_STRING;
NamedProp->Kind.lpwstrName = (LPWSTR) pHeaderName.ToPointer();
if(pMAPIProp->GetIDsFromNames(1, &pNamedProp,STGM_READ, &lpTags ) != S_OK)
throw gcnew Exception(String::Format ( "Error retrieving GetIDsFromNames: {0}.",headerName) );
ULONG propTag = PROP_TAG(PT_UNICODE, PROP_ID(lpTags->aulPropTag[0]));
|