另一个简单的 MAPI 类






4.77/5 (15投票s)
为您的应用程序添加 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 的实现历史悠久,并使用指向结构数组的指针。当您需要向数组追加新结构时,使用 new
和 delete
来处理这些会很麻烦。
在服务中使用该类
首先,重新阅读 重要的环境假设。现在,将源文件包含到您的项目中,并为项目定义#define SERVER
。该符号不会改变头文件中的任何内容,但它会抑制类实现中一些与 UI 相关的代码。坦白说,我不建议尝试在服务中使用此类。我发现整个主题都非常不稳定且文档模糊不清。如果您选择继续尝试在服务中使用此类,您将自行承担风险。那么为什么使用 MAPI 而不是 SMTP 呢?
通常,如果您的邮件传输最终委托给 SMTP(例如,使用 Outlook Express),我建议在您的代码中使用 SMTP 库。尽管如此,仍有许多组织使用基于 MAPI 的电子邮件系统。我还发现许多 SMTP 库使用起来很麻烦,而且实际上,在我自己的代码中,我更喜欢使用此类,即使在处理 SMTP 时也是如此,只要我能保证 Outlook Express 可用。当然,这种偏好可能仅仅是惯性 :)。历史
2004 年 2 月 28 日 - 首次 CodeProject 发布。