Bridging the gap between .NET and Extended MAPI
Date de publication : 02/09/2008 , Date de mise à jour : 02/09/2008
Par
Florian Casabianca (mon site)
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 profile Outlook courant.
VIII. Défini la couleur du label de rendez-vous
IX. Notes
Conclusion
XI. Liens
Remerciements
Contact
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 utiliser avec 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 and fonctions et structures non managées. C'est alors que vient une géniale 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 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 fourni 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:
- Redemption de Dmitry Streblechenko (la documentation vivante d'Extended MAPI)
-
Security Manager d'Add-In-Express.com
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és 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 et 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.won't. 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 and then compile against the external libraries like mapi32.lib or any other unmanaged native library. Just walk through the code, where you can see a mix between traditionally C++ pointers and unmanaged code (new and *) and .NET managed code (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 ]));
if (HrGetOneProp (pMAPIProp,propTag,& lpSPropValue) ! = S_OK)
throw gcnew Exception (" HrGetOneProp failed for named property ! " );
return gcnew String ( lpSPropValue- > Value.lpszW );
}
catch (Exception^ ex) { ... }
finally { ... }
}
|
L'utilisation est identique à l'autre méthode. Créer un objet fabrique et passer mailitem.MAPIOBJECT à la méthode ainsi que le nom de l'entête personnalisé.
VII. Lire le nom du profile Outlook courant.
Dans cette leçon, vous apprendrez comment passer outre un problème courant pour les développeurs Outlook. Vous apprendrez comment lire le nom du profile de la session MAPI courante. Dans le modèle Outlook Object, il n'y aucun moyen de déterminer le nom du profile que l'utilisateur a démarré. Avec le snipper suivant, vous pouvez résoudre ce problème. L'idée est d'obtenir l'objet session et d'utiliser la méthode OpenProfileSection pour retrouver les informations du profile Outlook courant. Notez que pour une lecture plus aisée, le bloc catch/finally a été enlevé. Comme plus haut, référez vous aux fichiers sources pour plus de détails
|
String^ Fabric:: GetProfileName (Object^ mapiObject)
{
String^ result = nullptr;
IUnknown* pUnknown = 0 ;
LPMAPISESSION lpMAPISession = 0 ;
LPPROFSECT lpProfileSection = 0 ;
LPSPropValue lpSPropValue = 0 ;
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_IMAPISession,
(void * * )& lpMAPISession) ! = S_OK)
throw gcnew Exception (
" QueryInterface failed on IUnknown for IID_IMAPISession " );
if ( lpMAPISession- > OpenProfileSection (
(LPMAPIUID)GLOBAL_PROFILE_SECTION_MAPIUID,
NULL ,STGM_READ, & lpProfileSection) ! = S_OK)
throw gcnew Exception (" OpenProfileSection method failed! " );
if (HrGetOneProp (lpProfileSection,
PR_PROFILE_NAME_W,& lpSPropValue) ! = S_OK)
throw gcnew Exception (
" HrGetOneProp failed for property PR_PROFILE_NAME_W ! " );
return gcnew String ( lpSPropValue- > Value.lpszW );
}
catch (Exception^ ex)
{
...
}
finally
{
...
}
}
|
Maintenant, c'est facile d'obtenir l'information reliée au profil sur lequel l'utilisateur est logué. Juste pour référence, vous implémentez une méthode qui énumère tous les profils MAPI disponibles pour l'utilisateur courant. Cette information est stockée dans le registre et ainsi vous ouvrez simplement la clé 'HKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles' et énumérer toutes les sous-clés (une pour chaque profil).
|
array< string^ ,1 / > ^ Fabric:: EnumProfileNames ()
{
String^ registryPath =
" Software\\Microsoft\\Windows NT\\CurrentVersion " +
" \\Windows Messaging Subsystem\\Profiles " ;
RegistryKey^ key = nullptr;
try
{
key = Registry:: CurrentUser- > OpenSubKey ( registryPath );
return key- > GetSubKeyNames ();
}
catch (System:: Exception^ ex)
{
throw ex;
}
finally
{
key- > Close ();
}
}
|
L'application de test fourni dans les sources démontre comment utiliser cette méthode. Passez simplement la propriété namespace.MAPIOBJECT.
Voici le code C#:
|
object missing = System. Reflection. Missing. Value;
Application Outlook. Application outlookApplication = new Outlook. Application ();
Outlook. NameSpace olNamespace = outlookApplication. GetNamespace (" MAPI " );
olNamespace. Logon (comboBox1. Text , missing, true , true );
MAPIConcubine. Fabric fabric = new MAPIConcubine. Fabric ();
try
{
string currentProfileName = fabric. GetProfileName (olNamespace. MAPIOBJECT);
MessageBox. Show (this , String. Format (" The current profilename was: {0} " , currentProfileName));
}
catch (System. Exception ex) {
MessageBox. Show (this , ex. Message);
}
finally {
fabric = null ;
olNamespace. Logoff ();
olNamespace = null ;
outlookApplication = null ;
GC. Collect ();
GC. WaitForPendingFinalizers ();
}
|
VIII. Défini la couleur du label de rendez-vous
Dans cette section, vous allez apprendre comme définir et obtenir la couleur d'un label dans un Item de Rendez-vous Outlook (Appointement). Avec les versions d'Oulook 2002 et postérieur, vous avez la possibilité de colorier vos rendez-vous. Regardez ici pour plus d'information. Dans le passé, vous deviez utiliser CDO ou l'une des bibliothèques externes mentionnées plus haut. Ici se trouve le code source qui démontré comment utiliser Extented MAPI pour réaliser la même chose depuis .Net. Dans le fichier d'entête, vous définissez les couleurs disponibles pour les labels de rendez-vous :
|
enum class AppointmentLabelColor
{
None = 0 ,
Important = 1 ,
Business = 2 ,
Personal = 3 ,
Vacation = 4 ,
Deadline = 5 ,
Travel_Required = 6 ,
Needs_Preparation = 7 ,
Birthday = 8 ,
Anniversary = 9 ,
Phone_Call = 10
} ;
|
Quand vous avez fini avec le fichier d'entête, continuez et implémentez la méthode SetAppointMentLabelColor comme ci-dessous. Notez que le bloc catch/finally a été enlevé pour une lecture plus aisée
|
void Fabric:: SetAppointmentLabelColor (Object^ mapiObject,
AppointmentLabelColor color)
{
IUnknown* pUnknown = 0 ;
IMessage* pMessage = 0 ;
IMAPIProp* pMAPIProp = 0 ;
MAPINAMEID* pNamedProp = 0 ;
SPropTagArray* lpTags = 0 ;
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 =
{
0x00062002 , 0x0000 , 0x0000 ,
{
0xC0 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x46
}
} ;
LONG propertyName = 0x8214 ;
if (MAPIAllocateBuffer (sizeof (MAPINAMEID),
(LPVOID* )& pNamedProp) ! = S_OK)
throw gcnew Exception (" Could not allocate memory buffer. " );
pNamedProp- > lpguid = (LPGUID)& magicId;
pNamedProp- > ulKind = MNID_ID;
pNamedProp- > Kind.lID = propertyName;
if (pMAPIProp- > GetIDsFromNames (1 , & pNamedProp,
MAPI_CREATE, & lpTags ) ! = S_OK)
throw gcnew Exception (String:: Format (
" Error retrieving GetIDsFromNames: {0}. " ,propertyName) );
SPropValue value;
value.ulPropTag = PROP_TAG (PT_LONG, PROP_ID (lpTags- > aulPropTag[0 ]));
value.Value.l = (LONG)color;
if ( HrSetOneProp (pMAPIProp, & value) ! = S_OK)
throw gcnew Exception (String:: Format (
" Error setting AppointmentLabelColor: {0}. " ,color) );
pMessage- > SaveChanges (KEEP_OPEN_READWRITE);
}
catch (Exception^ ex)
{
...
}
finally
{
...
}
}
|
La méthode pour récupérer le label est implémentée comme suit :
|
GUID magicId =
{
0x00062002 , 0x0000 , 0x0000 ,
{
0xC0 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x46
}
} ;
LONG propertyName = 0x8214 ;
if (MAPIAllocateBuffer (sizeof (MAPINAMEID), (LPVOID* )& pNamedProp) ! = S_OK)
throw gcnew Exception (" Could not allocate memory buffer. " );
pNamedProp- > lpguid = (LPGUID)& magicId;
pNamedProp- > ulKind = MNID_ID;
pNamedProp- > Kind.lID = propertyName;
if (pMAPIProp- > GetIDsFromNames (1 , & pNamedProp, STGM_READ, & lpTags ) ! = S_OK)
throw gcnew Exception (String:: Format (
" Error retrieving GetIDsFromNames: {0}. " ,propertyName) );
ULONG propTag = PROP_TAG (PT_LONG, PROP_ID (lpTags- > aulPropTag[0 ]));
if (HrGetOneProp (pMAPIProp,propTag,& lpSPropValue) ! = S_OK)
throw gcnew Exception (" HrGetOneProp failed for named property ! " );
if (( lpSPropValue- > Value.l > 0 ) & & ( lpSPropValue- > Value.l < 11 ))
return (Fabric:: AppointmentLabelColor ) lpSPropValue- > Value.l;
else
return Fabric:: AppointmentLabelColor:: Default;
|
IX. Notes
Lorsque vous déployez cette DLL avec votre application:
- La DLL devrait être compilée en mode release
- Vous devriez redistribuer les bibliothèques du CLR 8.0 avec (Pré-requis dans le package d'installation MSI)
- VSTO: pour chaque DLL qui est utilisée depuis votre add-in, vous devez régler les stratégies de sécurité (action personnalisée du package d'installation MSI)
Conclusion
XI. Liens
Remerciements
Saisissez ici vos remerciements
Contact
Saisissez ici vos coordonnées pour pouvoir être contacté (MP forum, courriel, etc.).
Copyright © 2008 Louis-Guillaume Morand.
Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de
son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur.
Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 €
de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.