连接 .NET 与 Extended MAPI






4.91/5 (18投票s)
一篇关于如何从 .NET 应用程序访问 Extended MAPI 属性、字段和方法的文章。
引言
在本文中,您将学习如何实现一个 C++ 库,该库将帮助您使用 Outlook 对象模型中不可用的 Extended MAPI 函数。过去,您必须购买一个额外的库或处理 COM 技术并自行创建这个原生 DLL。另一种方法是重复造轮子,并在 .NET 端重新定义所有接口。这将允许您通过将 .NET 结构封送至非托管函数和结构来直接从 C# 或 VB.NET 使用这些接口。这里有一个方便的 C++ 库,您可以在一个环境中轻松混合托管和原生代码。
优点
- 所有 Extended MAPI 接口和函数都已在 Windows SDK 中定义
- 我们无需公开 COM 接口
- 目标系统上无需部署或注册外部组件
- .NET 类和接口对外公开
- 外部 COM 组件无需包装类
- 完全包含在您的解决方案中,并支持调试
缺点
- 需要一些 C++ 知识
- 需要一些 Extended MAPI 接口知识
背景
Microsoft Outlook 对象模型 (OOM) 功能强大,提供对许多特性(用于使用和操作 Outlook 和 Exchange Server 中存储的数据)的访问。然而,每个认真的 Outlook 开发人员都会遇到一个需要特殊函数、属性或字段的时刻,而这些功能在 OOM 中不可用,或由于安全限制而被阻止。过去,您必须使用一个外部 COM 库,例如强烈推荐的
- Dmitry Streblechenko 的 Redemption(活着的 Extended MAPI 文档)
- Add-In-Express.com 的 Security Manager
虽然这些库功能强大且灵活,因为它们可以从各种编程语言中使用,但它们仍然是需要随您的应用程序注册和部署的额外库。这有时会带来问题。当从 .NET 使用 Extended MAPI 时,存在另一个名为
设置解决方案
在您开始深入 Extended MAPI 之前,您必须在您的开发机器上安装最低要求。
必备组件
- Microsoft Outlook 2003 / Outlook 2007
- Office 主互操作程序集 (PIA) 2003 / 2007
- Microsoft Visual Studio 2005
- Windows 平台 SDK
注意:如果您已经安装了旧版 Microsoft Exchange Server 5.5 SDK,则无需 Platform SDK。
创建解决方案
为了演示其幕后工作原理,您将从一个简单的 Windows 窗体应用程序开始,该应用程序创建一封新电子邮件并为其添加一个额外的 SMTP 标头。在此示例中,您将分别找到一个 C# 和一个 VB.NET 解决方案。因此,打开 Visual Studio,选择您选择的语言并创建一个看起来像这样的应用程序
假设您是一名 Outlook 程序员,并且希望使用 Outlook 对象模型。您自然需要转到项目引用,并将以下 COM 库作为引用添加到您的项目。
- Microsoft Office 1X.0 对象库
- Microsoft Outlook 1X.0 对象库
注意:取决于安装的 Office 版本。
完成向项目添加引用后,您可以像这样将命名空间导入到代码文件中
Imports Office = Microsoft.Office.Core
Imports Outlook = Microsoft.Office.Interop.Outlook
using Office = Microsoft.Office.Core;
using Outlook = Microsoft.Office.Interop.Outlook;
Outlook 的事情
目标是创建一个新电子邮件并附加一个 SMTP 标头。因此,首先我们看看如何使用 Outlook 对象模型创建一封新电子邮件
Private Sub btnSend_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnSend.Click
' get the Outlook Application Object
Dim outlookApplication As Outlook.Application = New Outlook.Application()
' get the namespace object
Dim olNamespace As Outlook.NameSpace = _
okApplication.GetNamespace("MAPI")
' logon to Session, here we use an already opened Outlook
olNamespace.Logon(, , True, True)
' create a new email and fill it with the info provided in the Form
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
' logoff from namespace (session)
olNamespace.Logoff()
'release reference to Outlook object
outlookApplication = Nothing
' Let the Garbagecollector do his work
GC.Collect()
GC.WaitForPendingFinalizers()
End Sub
C++
private void btnSend_Click(object sender, EventArgs e)
{
object missing = System.Reflection.Missing.Value;
// get the Outlook Application Object
Outlook.Application outlookApplication = new Outlook.Application();
// get the namespace object
Outlook.NameSpace nameSpace = outlookApplication.GetNamespace("MAPI");
// Logon to Session, here we use an already opened Outlook
nameSpace.Logon(missing, missing, true, true);
// create a new email and fill it with the info provided in the Form
Outlook.MailItem newMail = _
lookApplication.CreateItem(Outlook.OlItemType.olMailItem);
newMail.To = tbxRecipientAddress.Text;
newMail.Subject = tbxSubject.Text;
newMail.Body = tbxMessage.Text;
newMail.Send();
newMail = null;
// logoff from namespace (session)
olNamespace.Logoff();
//release reference to Outlook object
outlookApplication = Nothing;
// Let the Garbagecollector do his work
GC.Collect();
GC.WaitForPendingFinalizers();
}
这段小代码应简单地向您在 **收件人:** 字段中提供的收件人发送一封电子邮件。
混合 C++ 库
现在您必须向解决方案添加一个新项目。选择 **文件|添加|新项目**,然后选择 C++ CLR 类库。
我将这个库命名为 `MAPIConcubine`,因为它能给我 Outlook 对象模型所不能给的。不过,您可以随意给它起您选择的名字。将 C++ 库添加到解决方案后,打开库项目设置,并将 *mapi32.lib* 添加为链接器的输入文件。
下一步是将 MAPI C++ 头文件添加到您的项目中。打开 *stdafx.h* 文件,并在 `#pragma once` 语句之后添加以下头文件。
#pragma once
#include <MAPIX.h>
#include <MapiUtil.h>
#include <MAPIGuid.h>
#include <MAPITags.h>
现在编译项目,看看是否编译成功。如果成功,一切顺利,您可以继续。否则,您可能缺少前提条件,例如包含在 *StdAfx.h* 中定义的 C++ 头文件的 Windows SDK。
添加功能
现在您应该有一个可编译的 .NET C++ 类库,并链接了 Extended MAPI 库。继续并开始向您的 .NET 项目公开功能。回想最初的目标,您想向外发 Outlook 电子邮件添加一个 SMTP 标头。现在您可以继续并提供一个对外公开所需功能的接口。
头文件
C++ 类通常有一个头文件和一个代码文件。在这里,您的库的头文件如下所示。您定义了一个 .NET 类 `Fabric`,其中有一个名为 `AddMessageHeader` 的 `public` 可见方法。该方法接受三个参数:第一个是 `MPIObject`,您可以将其作为属性从 Outlook 项目中获取。此属性提供对底层 MAPI `IUnknown` 接口的访问。
#pragma once
using namespace System;
using namespace System::Runtime::InteropServices;
namespace MAPIConcubine
{
///
/// The Fabric Class defines Methods and Functions to access
/// Extended MAPI Functions from .NET
///
public ref class Fabric
{
// private methods and variables
private:
// destructor
~Fabric();
///
/// Used to retrieve a human readable Errormessage from a
/// MAPI Errorcode.
///
/// [in]dw - the error number returned from MAPI
/// [returns] Returns the Errormessage to the given Errornumber.
String^ GetErrorText(DWORD dw);
// All public visible methods
public:
// construction code
Fabric();
///
/// Adds an InternetHeader to an outgoing Outlook- Email Message
///
/// [in]mapiObject - The MAPIOBJECT property of the Outlook mailItem
/// [in]headerName - The name of the header to add
/// [in]headerValue - The value of the header to add
void AddMessageHeader(Object^ mapiObject, String^ headerName,
String^ headerValue);
};
}
您只对外公开 .NET 兼容的方法。这使得从任何 .NET 项目中使用此库变得容易。现在您已经定义了接口,可以继续实现其背后的功能。让我们看看在 *MAPIConcubine.cpp* 文件中可以找到的实现。
其思路是获取 Outlook 对象中的 `IUnknown` 接口,接着是 `IMessage`,最后是 `IMAPIProp` 接口。在这里,C++ 库扮演着主导角色。您现在可以访问定义了所有接口的 MAPI 头文件,然后针对外部库(如 `mapi32.lib` 或任何其他非托管原生库)进行编译。只需浏览代码,您会看到传统 C++ 指针和非托管代码(new 和 *)以及 .NET 托管代码(`gcnew` 和 `^`)的混合。您在这里可以看到的最棘手的事情是如何将 .NET 字符串变量传递给非托管的 `LPWSTR`,即指向 Unicode 空终止字符数组的指针。.NET 已在 `System.Runtime.Interop` 命名空间中为您提供了正确的实现方法。
///
/// Adds an InternetHeader to an outgoing Outlook- Email Message
///
/// [in]mapiObject - The MAPIOBJECT property of the Outlook mailItem
/// [in]headerName - The name of the header to add
/// [in]headerValue - The value of the header to add
void Fabric::AddMessageHeader(Object^ mapiObject, String^ headerName,
String^ headerValue)
{
// Pointer to IUnknown Interface
IUnknown* pUnknown = 0;
// Pointer to IMessage Interface
IMessage* pMessage = 0;
// Pointer to IMAPIProp Interface
IMAPIProp* pMAPIProp = 0;
// a structure for the MANEDProp variable
MAPINAMEID* pNamedProp = 0;
// an array of PropertyTags as result for the GetIDdFromNames Method.
SPropTagArray* lpTags = 0;
// Convert the .Net Strings to unmanaged memory blocks
IntPtr pHeaderName = Marshal::StringToHGlobalUni (headerName);
IntPtr pHeaderValue = Marshal::StringToHGlobalUni (headerValue);
// if we have no MAPIObject everything is senseless...
if (mapiObject == nullptr)
throw gcnew System::ArgumentNullException
("mapiObject","The MAPIObject must not be null!");
try
{
// retrieve the IUnknon Interface from our MAPIObject
// coming from Outlook.
pUnknown = (IUnknown*)Marshal::GetIUnknownForObject
(mapiObject).ToPointer ();
// try to retrieve the IMessage interface,
// if we don't get it, everything else is senseless.
if ( pUnknown->QueryInterface
(IID_IMessage, (void**)&pMessage) != S_OK)
throw gcnew Exception
("QueryInterface failed on IUnknown for IID_Message");
// try to retrieve the IMAPIProp interface from IMessage Interface,
// everything else is senseless.
if ( pMessage->QueryInterface
(IID_IMAPIProp, (void**)&pMAPIProp) != S_OK)
throw gcnew Exception
("QueryInterface failed on IMessage for IID_IMAPIProp");
// double check, if we wave no pointer, exit...
if (pMAPIProp == 0)
throw gcnew Exception
("Unknown Error on receiving the Pointer to MAPI Properties.");
// A well Google documented ID used to access the SMTP Headers
// of an Outlook Message
GUID magicId = { 0x00020386, 0x0000, 0x0000,
{ 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };
// The pointer to the namedProp structure
if (MAPIAllocateBuffer(sizeof(MAPINAMEID),
(LPVOID*)&pNamedProp) != S_OK)
throw gcnew Exception("Could not allocate memory buffer.");
// The ID is used to access the named Property
pNamedProp->lpguid = (LPGUID)&magicId;
// Set the PropertyName
pNamedProp->ulKind = MNID_STRING;
pNamedProp->Kind.lpwstrName = (LPWSTR) pHeaderName.ToPointer();
// Call the GetIDsFromNames to retrieve the propertyID to use
if(pMAPIProp->GetIDsFromNames
(1, &pNamedProp, MAPI_CREATE, &lpTags ) != S_OK)
throw gcnew Exception(String::Format
("Error retrieving GetIDsFromNames: {0}.",headerName) );
// the variable that will be passed to the HrSetOneProp method.
SPropValue value;
// the PROP_TAG macro creates the correct value from the
// value type and the value ID for us
value.ulPropTag = PROP_TAG(PT_UNICODE, PROP_ID
(lpTags->aulPropTag[0]));
// here we pass a Unicode string to the method and
// therefore we set the LPSZ property
value.Value.LPSZ = (LPWSTR) pHeaderValue.ToPointer();
// the HrSetOneProp method is used to set the property
// on the MAPI object
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);
// Free allocated unmanaged memory
Marshal::FreeHGlobal (pHeaderName);
Marshal::FreeHGlobal (pHeaderValue);
// cleanup all references to COM Objects
if (pMAPIProp!=0) pMAPIProp->Release();
if (pMessage!=0) pMessage->Release();
if (pUnknown!=0) pUnknown->Release();
}
}
有趣的是您如何实现 `try`/`catch`/`finally` 块。您必须注意不要发生内存泄漏,因此将清理代码放入 `finally` 部分。您还可以抛出对调用 .NET 类更友好的异常。只需编译,您就会发现缺少一些东西。这就是您必须访问 MAPI 头文件中定义的 MAPI ID 的方式。现在只需再做一件事:转到您的 *Stdafx.h* 文件并包含以下语句。对于您使用的每个 MAPI ID,您都必须包含这些语句。
#pragma once
#define INITGUID
#define USES_IID_IMAPIProp
#define USES_IID_IMessage
#include <MAPIX.h>
#include <MapiUtil.h>
#include <MAPIGuid.h>
#include <MAPITags.h>
初始化和清理
啊!等等;你错过了什么。每当您想从 extended MAPI 获取任何东西时,都必须对其进行初始化。因此,您必须向库添加一个构造和清理例程。它看起来像这样
///
/// construction code - we can initialize the MAPI Session here
///
Fabric::Fabric ()
{
MAPIInitialize(0);
}
///
/// destructor - cleanup the MAPI session here.
///
Fabric::~Fabric ()
{
MAPIUninitialize();
}
此外,为了更轻松的错误处理和调试,您应该添加一个方法,该方法向调用方返回人类可读的错误消息。此方法可能如下面的代码所示
///
/// This method takes an MAPI error code as parameter (GetLastError())
/// and returns the error message as managed string.
///
/// [in]dw - the MAPI error code.
/// [return] returns the human readable error message as string.
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;
}
用法
让我们转向应该使用此库的 .NET 客户端。打开您之前创建的 Windows 窗体应用程序——甚至是 VSTO 或扩展性 AddIn——并向其添加一个新的引用。转到“项目”选项卡并选择现有项目 `MAPIConcubine`。
现在您可以更改 Windows 窗体应用程序的代码,并向其中添加新功能。它看起来很简单,像这样
' create a new email and fill it with the info provided in the Form
Dim newMail As Outlook.MailItem = _
outlookApplication.CreateItem(Outlook.OlItemType.olMailItem)
' here we use our C++ library
Dim fabric As MAPIConcubine.Fabric = New MAPIConcubine.Fabric()
' we pass the MAPIObject property to our library and the parameters
' note that in VB.Net you can't see the MAPIOBJECT property
' but be sure it is there
fabric.AddMessageHeader(newMail.MAPIOBJECT, tbxHeaderName.Text, _
tbxHeaderValue.Text)
' that's all
fabric = Nothing
newMail.To = tbxRecipientAddress.Text
newMail.Subject = tbxSubject.Text
newMail.Body = tbxMessage.Text
' send out the email
newMail.Send()
C++
// create a new email and fill it with the info provided in the Form
Outlook.MailItem newMail = outlookApplication.CreateItem
(Outlook.OlItemType.olMailItem) as Outlook.MailItem;
// here we use our C++ library
MAPIConcubine.Fabric fabric = new MAPIConcubine.Fabric();
// we pass the MAPIObject property to our library and the parameters
// note that in VB.Net you can't see the MAPIOBJECT property
// but be sure it is there
fabric.AddMessageHeader(newMail.MAPIOBJECT, tbxHeaderName.Text,
tbxHeaderValue.Text);
// that's all
fabric = null;
newMail.To = tbxRecipientAddress.Text;
newMail.Subject = tbxSubject.Text;
newMail.Body = tbxMessage.Text;
newMail.Send();
就这些;剩下的都是小菜一碟。您可以检查收件人邮箱中这封电子邮件的 Internet 标头,您会看到您自己的标头在那里。在 Outlook 中,您可以通过打开电子邮件并显示“选项”菜单来查看它。祝大家编程愉快。
读取标题
在本章中,您将学习如何检索保存的标头以及如何检索所有传输消息标头。理论很简单:您使用 GUID 访问 Internet 标头和 `GetIDSFromNames` 方法。这允许您检索将在 `HrGetOneProp` 方法中使用的正确 `propTagId`。如果您处于 Exchange 环境中,您可以使用以下代码读取自定义电子邮件标头。请注意,为了便于阅读,已排除 catch/finally 块。如上所述,请参阅源文件以获取更多详细信息。
///
/// Reads an InternetHeader from an Outlook- Email Message
///
/// [in] mapiObject - the MAPIOBJECT property of the outlook mailItem object.
/// [in] headerName - the name of the header to retrieve.
/// [returns] headerValue - the value of the header with the given name.
String^ Fabric::ReadMessageHeader(Object^ mapiObject, String^ headerName)
{
// Pointer to IUnknown Interface
IUnknown* pUnknown = 0;
// Pointer to IMessage Interface
IMessage* pMessage = 0;
// Pointer to IMAPIProp Interface
IMAPIProp* pMAPIProp = 0;
// a structure for the MANEDProp variable
MAPINAMEID* pNamedProp = 0;
// an array of PropertyTags as result for the GetIDdFromNames Method.
SPropTagArray* lpTags = 0;
// pointer to a structure that receives the result from HrGetOneProp
LPSPropValue lpSPropValue = 0;
// Convert the .Net Strings to unmanaged memory blocks
IntPtr pHeaderName = Marshal::StringToHGlobalUni (headerName);
// if we have no MAPIObject everything is senseless...
if (mapiObject == nullptr) throw gcnew System::ArgumentNullException (
"mapiObject","The MAPIObject must not be null!");
try
{
// retrive the IUnknon Interface from our
// MAPIObject comming from Outlook.
pUnknown =
(IUnknown*)Marshal::GetIUnknownForObject(mapiObject).ToPointer ();
// try to retrieve the IMessage interface, if
// we don't get it, everything else is sensless.
if ( pUnknown->QueryInterface (IID_IMessage,
(void**)&pMessage) != S_OK)
throw gcnew Exception(
"QueryInterface failed on IUnknown for IID_Message");
// try to retrieve the IMAPIProp interface from
// IMessage Interface, everything else is sensless.
if ( pMessage->QueryInterface (IID_IMAPIProp,
(void**)&pMAPIProp) != S_OK)
throw gcnew Exception(
"QueryInterface failed on IMessage for IID_IMAPIProp");
// double check, if we wave no pointer, exit...
if (pMAPIProp == 0) throw gcnew Exception(
"Unknown Error on receiving the Pointer to MAPI Properties.");
// A well Google documented ID used to
// access the SMTP Headers of an Outlook Message
GUID magicId =
{
0x00020386, 0x0000, 0x0000,
{
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46
}
};
// The pointer to the namedProp structure
if (MAPIAllocateBuffer(sizeof(MAPINAMEID),
(LPVOID*)&pNamedProp) != S_OK)
throw gcnew Exception("Could not allocate memory buffer.");
// The ID is used to acces the named Property
pNamedProp->lpguid = (LPGUID)&magicId;
// Set the PropertyName
pNamedProp->ulKind = MNID_STRING;
pNamedProp->Kind.lpwstrName = (LPWSTR) pHeaderName.ToPointer();
// Call the GetIDsFromNames to retrieve the propertyID to use
if(pMAPIProp->GetIDsFromNames(1, &pNamedProp,
STGM_READ, &lpTags ) != S_OK)
throw gcnew Exception(String::Format (
"Error retrieving GetIDsFromNames: {0}.",headerName) );
// The PROP_TAG macro creates the correct
// value for Type and PropertyID
ULONG propTag = PROP_TAG(PT_UNICODE, PROP_ID(lpTags->aulPropTag[0]));
// use the HrGetOneProp method to retrieve the profile name property
// the lpPropValue pointer points to the result value
if (HrGetOneProp(pMAPIProp,propTag,&lpSPropValue) != S_OK)
throw gcnew Exception("HrGetOneProp failed for named property !");
// create a managed string from the unmanaged string.
return gcnew String( lpSPropValue->Value.lpszW );
}
catch (Exception^ ex)
{
...
}
finally
{
...
}
}
用法与另一种方法相同。创建一个 fabric 对象,并将 `mailitem.MAPIOBJECT` 和自定义标题的名称传递给该方法。
读取当前的 Outlook 配置文件名
在本课中,您将学习如何解决 Outlook 开发人员面临的常见问题。您将学习如何读取当前 MAPI 会话的配置文件名。在 Outlook 对象模型中,无法确定用户已启动的配置文件的名称。使用以下代码片段,您可以解决此问题。其思路是获取会话对象并使用 `OpenProfileSection` 方法检索有关当前 Outlook 配置文件的信息。请注意,为了便于阅读,已排除 catch/finally 块。如上所述,请参阅源文件以获取更多详细信息。代码如下
///
/// Returns the MAPI profilename of the current session.
///
/// [in] mapiObject - the MAPIOBJECT property of the
/// Outlook Application.Session object.
/// [returns] - the name of the currently used profile.
String^ Fabric::GetProfileName(Object^ mapiObject)
{
// the result returned to the calling method
String^ result = nullptr;
// pointer to IUnknown interface
IUnknown* pUnknown = 0;
// pointer to the MAPI session interface
LPMAPISESSION lpMAPISession = 0;
// pointer to a profilesection interface
LPPROFSECT lpProfileSection = 0;
// pointer to a structure that receives the result from HrGetOneProp
LPSPropValue lpSPropValue = 0;
// if we have no MAPIObject everything is senseless...
if (mapiObject == nullptr)
throw gcnew System::ArgumentNullException ("mapiObject",
"The MAPIObject must not be null!");
try
{
// retrive the IUnknon Interface from our MAPIObject
// comming from Outlook.
pUnknown =
(IUnknown*)Marshal::GetIUnknownForObject(mapiObject).ToPointer ();
// try to retrieve the IMAPISession interface, if
// we don't get it, everything else is sensless.
if ( pUnknown->QueryInterface (IID_IMAPISession,
(void**)&lpMAPISession) != S_OK)
throw gcnew Exception(
"QueryInterface failed on IUnknown for IID_IMAPISession");
// use the OpenProfileSection of the MAPISession
// object to retrieve a pointer to the current
// profilesection interface
if( lpMAPISession->OpenProfileSection(
(LPMAPIUID)GLOBAL_PROFILE_SECTION_MAPIUID,
NULL,STGM_READ, &lpProfileSection) != S_OK)
throw gcnew Exception("OpenProfileSection method failed!");
// use the HrGetOneProp method to retrieve the profile name property
// the lpPropValue pointer points to the result value
if (HrGetOneProp(lpProfileSection,
PR_PROFILE_NAME_W,&lpSPropValue) != S_OK)
throw gcnew Exception(
"HrGetOneProp failed for property PR_PROFILE_NAME_W !");
// create a managed string from the unmanaged string.
return gcnew String( lpSPropValue->Value.lpszW );
}
catch (Exception^ ex)
{
...
}
finally
{
...
}
}
现在,获取与用户当前登录的配置文件相关的信息变得容易了。仅供参考,您实现了一个方法来枚举当前用户所有可用的 MAPI 配置文件。此信息存储在注册表中,因此您只需打开注册表项“HKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles”,并枚举所有子项。每个配置文件都有一个子项。
///
/// Returns the current MAPI profilenames available in the system registry.
///
/// [returns] - a string array of all available profilenames.
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();
}
}
解决方案中提供的测试应用程序演示了如何使用此方法。只需传递 `namespace.MAPIOBJECT` 属性。C# 代码如下
// Create an Outlook session
object missing = System.Reflection.Missing.Value;
// get the Outlook Application Object
Outlook.Application outlookApplication = new Outlook.Application();
// get the namespace object
Outlook.NameSpace olNamespace = outlookApplication.GetNamespace("MAPI");
// Logon to Session, here we use the name from
// the comboBox to logon to a specific session.
// If there is already an Outlook instance with
// a Session - then this profile is used.
olNamespace.Logon(comboBox1.Text , missing, true, true);
// Use the fabric to get the current profilename
MAPIConcubine.Fabric fabric = new MAPIConcubine.Fabric();
try
{
// pass along the namespace.MAPIOBJECT to retrieve the profilename
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();
}
设置约会标签颜色
在本节中,您将学习如何设置和获取 Outlook 约会项目的标签颜色。从 Outlook 2002 及更高版本开始,您可以选择为 Outlook 约会着色。有关更多信息,请参阅此处。过去,您必须使用 CDO 或上述提及的外部库之一。以下是演示如何使用 Extended MAPI 从 .NET 实现此目标的源代码。在头文件中,您定义了约会标签的可用颜色
/// Defines the available Colors for AppointmentLabels
enum class AppointmentLabelColor
{
None = 0, // Default Color
Important = 1, // Red
Business = 2, // Blue
Personal = 3, // Green
Vacation = 4, // Grey
Deadline = 5, // Orange
Travel_Required = 6, // Light-Blue
Needs_Preparation = 7, // Grey
Birthday = 8, // violett
Anniversary = 9, // turquoise
Phone_Call = 10 // Yellow
} ;
完成头文件后,继续并实现 `SetAppointMentLabelColor` 方法,如下所示。请注意,为了便于阅读,catch/finally 块已被排除。如上所述,请参阅源文件以获取更多详细信息。
///
/// Sets the AppointmentLabelColor for a Outlook- Appointment
///
/// [in] mapiObject - the MAPIOBJECT property of
/// the outlook appointmentItem object.
/// [in] the desired colortype that you wish to set
void Fabric::SetAppointmentLabelColor(Object^ mapiObject,
AppointmentLabelColor color)
{
// Pointer to IUnknown Interface
IUnknown* pUnknown = 0;
// Pointer to IMessage Interface
IMessage* pMessage = 0;
// Pointer to IMAPIProp Interface
IMAPIProp* pMAPIProp = 0;
// a structure for the NAMEDProp variable
MAPINAMEID* pNamedProp = 0;
// an array of PropertyTags as result for the GetIDdFromNames Method.
SPropTagArray* lpTags = 0;
// if we have no MAPIObject everything is senseless...
if (mapiObject == nullptr) throw gcnew System::ArgumentNullException (
"mapiObject","The MAPIObject must not be null!");
try
{
// retrive the IUnknon Interface from our MAPIObject
// comming from Outlook.
pUnknown =
(IUnknown*)Marshal::GetIUnknownForObject(mapiObject).ToPointer ();
// try to retrieve the IMessage interface, if
// we don't get it, everything else is sensless.
if ( pUnknown->QueryInterface (IID_IMessage,
(void**)&pMessage) != S_OK)
throw gcnew Exception(
"QueryInterface failed on IUnknown for IID_Message");
// try to retrieve the IMAPIProp interface from
IMessage Interface, everything else is sensless.
if ( pMessage->QueryInterface (IID_IMAPIProp,
(void**)&pMAPIProp) != S_OK)
throw gcnew Exception(
"QueryInterface failed on IMessage for IID_IMAPIProp");
// double check, if we wave no pointer, exit...
if (pMAPIProp == 0)
throw gcnew Exception(
"Unknown Error on receiving the Pointer to MAPI Properties.");
// A well Google documented ID used to access an
// appointment color label
GUID magicId =
{
0x00062002, 0x0000, 0x0000,
{
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46
}
};
LONG propertyName = 0x8214;
// The pointer to the namedProp structure
if (MAPIAllocateBuffer(sizeof(MAPINAMEID),
(LPVOID*)&pNamedProp) != S_OK)
throw gcnew Exception("Could not allocate memory buffer.");
// The ID is used to acces the named Property
pNamedProp->lpguid = (LPGUID)&magicId;
// Set the PropertyName - we have an ID as Property here
pNamedProp->ulKind = MNID_ID;
pNamedProp->Kind.lID = propertyName;
// Call the GetIDsFromNames to retrieve the propertyID to use
if(pMAPIProp->GetIDsFromNames(1, &pNamedProp,
MAPI_CREATE, &lpTags ) != S_OK)
throw gcnew Exception(String::Format (
"Error retrieving GetIDsFromNames: {0}.",propertyName) );
// A structure that is used to pass the value to the property
SPropValue value;
// The PROP_TAG macro creates the correct
// value for Type and PropertyID
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) );
// save the changes to the Item
pMessage->SaveChanges (KEEP_OPEN_READWRITE);
}
catch (Exception^ ex)
{
...
}
finally
{
...
}
}
检索约会标签颜色的方法实现如下所示
// A well Google documented ID used to access an appointment color label
GUID magicId =
{
0x00062002, 0x0000, 0x0000,
{
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46
}
};
LONG propertyName = 0x8214;
// The pointer to the namedProp structure
if (MAPIAllocateBuffer(sizeof(MAPINAMEID), (LPVOID*)&pNamedProp) != S_OK)
throw gcnew Exception("Could not allocate memory buffer.");
// The ID is used to acces the named Property
pNamedProp->lpguid = (LPGUID)&magicId;
// Set the PropertyName - we have an ID as Property here
pNamedProp->ulKind = MNID_ID;
pNamedProp->Kind.lID = propertyName;
// Call the GetIDsFromNames to retrieve the propertyID to use
if(pMAPIProp->GetIDsFromNames(1, &pNamedProp, STGM_READ, &lpTags ) != S_OK)
throw gcnew Exception(String::Format (
"Error retrieving GetIDsFromNames: {0}.",propertyName) );
// The PROP_TAG macro creates the correct value for Type and PropertyID
ULONG propTag = PROP_TAG(PT_LONG, PROP_ID(lpTags->aulPropTag[0]));
// use the HrGetOneProp method to retrieve the profile name property
// the lpPropValue pointer points to the result value
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;
注释
当您随应用程序部署此 DLL 时
- DLL 应以发布模式编译
- 您应该随之重新分发 CLR 8.0 运行时库(MSI 安装包中的先决条件)
- VSTO:对于您的加载项使用的每个 DLL,您都必须设置安全策略(MSI 安装包中的自定义操作)
Helmut Obertanner - 2007 年 6 月 X4U electronix
历史
- V.1.0 - 初始版本(2007 年 6 月 11 日)
- V.1.1 - 添加了读取 Internet 邮件头的支持
- V.1.2 - 枚举可用配置文件名称并获取当前 Outlook 配置文件名称
- V.1.3 - 获取/设置约会标签颜色(2007 年 6 月 28 日)