65.9K
CodeProject 正在变化。 阅读更多。
Home

连接 .NET 与 Extended MAPI

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (18投票s)

2007年6月11日

CPOL

10分钟阅读

viewsIcon

228279

downloadIcon

2323

一篇关于如何从 .NET 应用程序访问 Extended MAPI 属性、字段和方法的文章。

Screenshot - BridgingTheGap_Result.png

引言

在本文中,您将学习如何实现一个 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 库,例如强烈推荐的

虽然这些库功能强大且灵活,因为它们可以从各种编程语言中使用,但它们仍然是需要随您的应用程序注册和部署的额外库。这有时会带来问题。当从 .NET 使用 Extended MAPI 时,存在另一个名为

设置解决方案

在您开始深入 Extended MAPI 之前,您必须在您的开发机器上安装最低要求。

必备组件

创建解决方案

为了演示其幕后工作原理,您将从一个简单的 Windows 窗体应用程序开始,该应用程序创建一封新电子邮件并为其添加一个额外的 SMTP 标头。在此示例中,您将分别找到一个 C# 和一个 VB.NET 解决方案。因此,打开 Visual Studio,选择您选择的语言并创建一个看起来像这样的应用程序

Screenshot - BridgingTheGap_Application.png

假设您是一名 Outlook 程序员,并且希望使用 Outlook 对象模型。您自然需要转到项目引用,并将以下 COM 库作为引用添加到您的项目。

  • Microsoft Office 1X.0 对象库
  • Microsoft Outlook 1X.0 对象库
    注意:取决于安装的 Office 版本。

Screenshot - BridgingTheGap_Reference.png

完成向项目添加引用后,您可以像这样将命名空间导入到代码文件中

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 类库。

Screenshot - BridgingTheGap_AddConcubine.png

我将这个库命名为 `MAPIConcubine`,因为它能给我 Outlook 对象模型所不能给的。不过,您可以随意给它起您选择的名字。将 C++ 库添加到解决方案后,打开库项目设置,并将 *mapi32.lib* 添加为链接器的输入文件。

Screenshot - BridgingTheGap_LinkedLibraries.png

下一步是将 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`。

Screenshot - BridgingTheGap_Reference2.png

现在您可以更改 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 配置文件名

Screenshot - BridgingTheGap_Profile.png

在本课中,您将学习如何解决 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();
}

设置约会标签颜色

Screenshot - BridgingTheGap_AppointmentColor.png

在本节中,您将学习如何设置和获取 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 日)
© . All rights reserved.