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

另一个简单的 MAPI 类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (15投票s)

2004年2月28日

CPOL

5分钟阅读

viewsIcon

137763

downloadIcon

2275

为您的应用程序添加 MAPI 功能。

引言

许多应用程序都可以有用地包含发送电子邮件的功能。例如,任何 MFC SDI 或 MDI 应用程序都可以通过简单地添加消息映射条目,轻松地将底层 CDocument 作为附件发送到电子邮件中。但是,MFC 的实现会弹出一个电子邮件窗口并期望用户交互。这个类源于我需要一个应用程序能够发送电子邮件,而无需用户与电子邮件传输子系统交互。

背景

您可能以前见过这段代码。它最早于 1999 年在 CodeGuru[^] 上发布。(顺便说一句,我惊讶地发现有多少网站托管这段代码,它们都是从 CodeGuru 复制的。是的,我偶尔也会进行一些自我搜索 :))

这段代码的真正起源是 MFC 中电子邮件的实现。我复制了 MFC MAPI 代码并对其进行了修改,以实现我需要的功能。从那里发展出了这个类。正如您从我复制的注释中看到的,类中仍然保留着原始 MFC 代码的片段。

重要的环境假设

该类依赖于拥有一个可用且配置正确的 MAPI 传输。多年来,它已经过 Microsoft Outlook 和 Microsoft Outlook Express 的测试。如果您的系统包含其中一个程序的正确配置副本,那么该类应该能完美运行。

但这个陈述隐藏了很多复杂性。最主要的复杂性是 Outlook Express(我没有足够的 Outlook 经验来冒险针对 Outlook 发表具体评论)需要按用户进行配置。如果我以我自己的身份登录到我的计算机(我写这篇文章的计算机),Outlook Express 会自动配置为使用我的 POP3 入站和 SMTP 出站帐户。

如果我在这台机器上创建一个新的用户帐户,并且该用户帐户启动 Outlook Express,它将启动与我帐户相同的二进制文件。但是 Outlook Express 会引用用户特定的注册表配置单元(HKEY_CURRENT_USER)来获取其配置的电子邮件帐户列表。新用户尚未设置这些帐户,因此即使 Outlook Express 对我的主帐户运行良好,也无法为这个新用户帐户发送或接收电子邮件。(我怀疑它实际上是通过注册表中指定的配置文件实现的。对于本节的目的,数据存储在哪里并不重要,重要的是它是按用户存储的(为了使其更加复杂,还会按每个用户帐户内的配置文件存储))。

对于交互式登录会话,这通常不会造成太大麻烦。很容易注意到为什么应用程序无法发送电子邮件,注意到该用户尚未配置 Outlook Express,然后进行配置,问题就解决了。(在一句话中,我已经消除了大量的远程支持问题。)当您尝试在服务中使用该类时,它可能会造成麻烦。

服务通常在机器启动时启动,并在关机时继续运行。它们不要求登录会话才能运行。那么问题来了,如果它们不需要登录会话,那么它们是以什么身份运行的呢?最常见的答案是它们以 LocalSystem 用户身份运行。这是一个特殊的、特权极高的帐户,即使是管理员帐户也对其控制力很小。您无法以 LocalSystem 身份登录,这正是问题的所在。如果您无法以 LocalSystem 身份登录,您就无法轻松地为该帐户配置 Outlook Express。

现在请仔细阅读下一句话。该类将 **不** 在以 LocalSystem 身份运行的服务中工作。再回头读一遍。如果您想在服务中使用该类,您 **必须** 使用一个可以登录并配置邮件传输系统的帐户来运行该服务。显然,您必须至少登录过该帐户一次才能配置电子邮件。(见下文)。

现在我们已经解决了注意事项(以及大多数问题的解决方案),让我们来看看这个类。这是它的定义。
class CIMapi
{
public:
                    CIMapi();
                    ~CIMapi();

    enum errorCodes
    {
        IMAPI_SUCCESS = 0,
        IMAPI_LOADFAILED,
        IMAPI_INVALIDDLL,
        IMAPI_FAILTO,
        IMAPI_FAILCC,
        IMAPI_FAILATTACH
    };

//  Attributes
    void            Subject(LPCTSTR subject);
    void            Text(LPCTSTR text)          { m_text = text; }

    UINT            Error();
    void            From(LPCTSTR from)          { m_from.lpszName = (LPTSTR) from; }

    static BOOL     HasEmail();

//  Operations
    BOOL            To(LPCTSTR recip);
    BOOL            Attach(LPCTSTR path, LPCTSTR name = NULL);
    
    BOOL            Send(ULONG flags = 0);

private:
    BOOL            AllocNewTo();

    MapiMessage     m_message;
    MapiRecipDesc   m_from;
    UINT            m_error;
    CString         m_text;

    ULONG (PASCAL *m_lpfnSendMail)(ULONG, ULONG, MapiMessage*, FLAGS, ULONG);
    
    static HINSTANCE m_hInstMail;
    static BOOL     m_isMailAvail;
};
典型的用法如下所示。
void CBugReport::OnOK() 
{
     CIMapi mail;

    if (mail.Error() == IMAPI_SUCCESS)
    {
         mail.To("ultramaroon@cox.net");            //  Set recipient name (me)
         mail.To("someoneelse@somewhereelse.com");  //  Second recipient
         mail.Cc("cc@cc.com");                      //  CC recipient
         mail.From("user@somewhere.com");           //  Identify sender (not strictly 
                                                    //  necessary since
                                                    //  MAPI will fill this in for you)
         mail.Subject("Test Email");                //  Subject of this email
         mail.Attach("somefilename");               //  Attaching a file
         mail.Attach("someotherfile", "different_name_for_recipient");
                                                    // Attach another file but give it
                                                    // a different name inside the 
                                                    // email itself
 
         // Put text of message in body
         mail.Text("Body text for this email");     //  Set body text
         mail.Send();                               //  Now send the mail! 
    }
    else
    {
        // Do something appropriate to the error...
    }
    CDialog::OnOK();
}
看起来很简单。创建一个 CIMapi 实例,调用一些函数来设置收件人、主题、附加一些附件并发送电子邮件。

CIMapi 内部

构造函数会将一些内部结构清零,然后尝试加载 MAPI32.DLL。如果加载 DLL 失败,它会将内部错误变量设置为 IMAPI_LOADFAILED 并返回。如果成功加载 DLL,它会在 DLL 中搜索 MAPISendMail 入口点。如果找到此入口点,它会假定 DLL 有效,否则会将内部错误变量设置为 IMAPI_INVALIDDLL 并返回。

如果您只想知道 MAPI 是否可用,可以通过调用静态成员函数 HasEmail() 来避免加载 MAPI32.DLL 的开销,该函数会检查注册表是否存在特定的键,并检查 MAPI32.DLL 是否在您的路径中。

您会看到代码中有很多对 malloc()realloc()free() 的调用。MAPI 的实现历史悠久,并使用指向结构数组的指针。当您需要向数组追加新结构时,使用 newdelete 来处理这些会很麻烦。

在服务中使用该类

首先,重新阅读 重要的环境假设。现在,将源文件包含到您的项目中,并为项目定义 #define SERVER。该符号不会改变头文件中的任何内容,但它会抑制类实现中一些与 UI 相关的代码。坦白说,我不建议尝试在服务中使用此类。我发现整个主题都非常不稳定且文档模糊不清。如果您选择继续尝试在服务中使用此类,您将自行承担风险。

那么为什么使用 MAPI 而不是 SMTP 呢?

通常,如果您的邮件传输最终委托给 SMTP(例如,使用 Outlook Express),我建议在您的代码中使用 SMTP 库。尽管如此,仍有许多组织使用基于 MAPI 的电子邮件系统。我还发现许多 SMTP 库使用起来很麻烦,而且实际上,在我自己的代码中,我更喜欢使用此类,即使在处理 SMTP 时也是如此,只要我能保证 Outlook Express 可用。当然,这种偏好可能仅仅是惯性 :)。

历史

2004 年 2 月 28 日 - 首次 CodeProject 发布。

© . All rights reserved.