使用 MAPI 实现邮件发送
使用 MAPI 实现邮件发送
原 作:deltacat
创 建:2004.09.16
最后更新:2004.11.01
版权声明:作者保留版权。转载时敬请保持文档及说明的完整性。
关键字: Visual C++ 邮件发送 Simple MAPI
一、问题提出
我们经常需要在自己的应用程序中添加邮件支持,主要是发送邮件。目前,常用的方法有调用外部程序,直接对SMTP协议编程,或者使用 MAPI 接口。MAPI 是微软提供的一套用于邮件收发的接口。打开 MSDN Library,你可以在 Messaging and Collaboration Services 下面找到它。
MAPI 使用比较复杂,于是微软提供了几个比较简单的解决方案,这里包括 CMC(Common Messaging Calls)和 Simple MAPI。
本文主要阐述使用 Simple MAPI 进行开发一个简单的,用于发送一个带附件的邮件的功能实现。文章后面有一小块简单讲述了使用完整 MAPI 的开发,出于某些原因,并不推荐使用,如果需要更复杂的功能,我认为还是老老实实封装SMTP和POP3比较好。
二、Simple MAPI 介绍
Simple MAPI 包括一组很容易使用的函数以及相关的数据结构,可以在C/C++、VB等多种语言中使用。本文是基于VC设计的。
实现一个完整的邮件发送过程,实际上只需要一个函数“MAPISendMail()”,完整函数表及相关介绍参查阅MSDN Library->Platform SDK->Messaging and Collaboration Services。
MAPI 与邮件系统关系密切,要能够使用MAPI的功能,系统必须安装有支持 MAPI 的邮件系统,比如Outlook、Outlook Express、Eudora、Netscape等. 在这里不得不提一下,使用非常广泛的 FoxMail (5.0版本)似乎并不支持 MAPI,不过并没有深入研究,如果有哪位高手发现实际上是支持的,麻烦告知我如何做。
Windows提供了一个文件 MAPI.H,包含所有的相关数据类型的定义。
三、功能的设计
我需要实现如下功能:可定制一封邮件,包括至少一个收件人,可选项目有标题、正文、若干抄送人、密件抄送人、附件。能够根据需要选择自动发送(无用户干预),或弹出邮件编写窗口。
为了便于使用和扩展,我用了一个class来实现。
1、MAPI库的初始化
包括两个步骤:装载MAPI库、得到函数入口地址。
//-------------------------------------------------------------------------------------
m_hLibrary = ::LoadLibrary(_T("MAPI32.DLL"));
if(NULL == m_hLibrary)
{
return ::GetLastError();
}
// LPMAPISENDMAIL 等均定义在 <MAPI.H>
m_lpfnMAPISendMail = (LPMAPISENDMAIL)::GetProcAddress(m_hLibrary, _T("MAPISendMail"));
// 可以根据需要添加其他函数入口。我的做法是用了一个 InitMapi() 函数,一次性将所有函数入口得出,作为类的成员变量保存。随时可以使用
//-------------------------------------------------------------------------------------
2、发送邮件 MAPISendMail()
发送邮件功能就是对MAPISendMail()的封装。下面解释这个API函数的参数定义。
ULONG FAR PASCAL MAPISendMail(LHANDLE lhSession, ULONG ulUIParam, lpMapiMessage lpMessage, FLAGS flFlags, ULONG ulReserved);
ulUIParam 可以为 0,如果设置了 MAPI_DIALOG 标志,则最好传给它父窗口句柄。
lhSession和ulReserved这两个参数,简单地设置为0就可以了。
flFlags有三个有效位,分别是 MAPI_DIALOG,MAPI_LOGON_UI,MAPI_NEW_SESSION。如果希望程序弹出一个标准的邮件撰写对话框,请设置 MAPI_DIALOG,建议不要设置 MAPI_LOGON_UI 和 MAPI_NEW_SESSION。
重点是 lpMessage 这个参数,它指向一个MapiMessage类型的结构,详细地定义了一个邮件的全部信息。
3、结构 MapiMessage
使用时首先定义一个MapiMessage类型的变量,将其清零。现在我们只要简单地设定lpszSubject(标题)、lpszNoteText(正文)、lpOriginator(发件人)、nRecipCount(收件人计数,包含TO、CC、GCC),lpRecips(一个包含全部收件人的数组),nFileCount(附件计数)、lpFiles(包含每个附件信息的数组)。
nRecipCount 和 nFileCount 的值一定要与实际的收件人和附件数目相符。
4、结构 MapiRecipDesc
必须设置 ulRecipClass、lpszName、lpszAddress
5、结构 MapiFileDesc
必须设置的有 lpszPathName、nPosiotion 两个参数
这里有个地方需要特别注意。我在实际编码过程中,有几次 Outlook 不能正确弹出窗口(自动发送也不行),但是同样的过程,将邮件客户设为 OutlookExpress 就没有问题。十分头痛。经过反复检查,发现 nPosiotion 这个参数十分重要。它指示附件在邮件中的位置。
对于一些邮件客户端,比如OutlookExpress,也许忽略了这个参数,由客户端自动安排。所以没有问题。对于另一些客户端,比如Outlook,总是按照这个值的指示来安排的。如果附件数多于一个,这个值如果相同,那么就会造成错误。但是自行计算挺麻烦而且没什么意义。解决的方法是,将其设为 -1,指示客户软件自行安排。:)
四、如何工作?
MAPISendMail() 会调用系统默认的邮件客户程序来发送邮件。对于弹出编辑窗的方式,它的行为和另一个函数 MAPISendDocuments() 差不多,但是可以定制标题、收件人等等。而 MAPISendDocuments() 只是简单地准备一个空白的邮件(包含附件),有关 MAPISendDocuments() 的介绍参见 MSDN。
对于自动发送。需要在 Outlook Express 的安全设置中,取消“当有其他应用程序试图发送邮件时警告”这个选项。对于 Outlook,似乎使用 Simple MAPI 还没有禁止安全警告的方法。
我设计的类包括三个接口函数,Send(), AddFiles(), AddRecips(),其中只有 Send() 是必须的。使用时声明一个对象,然后就直接调用 Send() 函数发送邮件。两个 Add 函数只要根据需要在 Send() 之前调用即可。
五、遗留问题
我的开发环境是 Windows Xp Sp2 CHS,有如下几个问题,希望有高手可以解决。
1、自动发送时的警告问题。使用 Simple MAPI 能否解决?
2、默认Outlook Express为系统邮件客户端,可以立即发送,如果是Outlook,是先放到Outlook的发送队列了,这时如果Outlook未运行,就一直不会发送。怎样可以保证无论邮件客户软件是否在运行,我的程序都可以立即将邮件发送出去呢?
3、对于默认是“Hotmail”的情况,只有以 @hotmail.com 结尾的帐号可以正常发送,而 @msn.com 的则不行。同时,在发送时,hotmail会将硬盘上的附件改名(末尾添加“^”符号)并将文件属性改为只读,不过如果它正常上传了附件,会将文件复原。
六、使用 MAPI
之所以使用 MAPI,起初是为了取得系统的地址本以及结局上面第五节的问题2。
MAPI 相对于 Simple MAPI 要复杂的多。具体的使用方法可以参考 MSDN 文档“Handling an Outgoing Message”、“Handling the Address Book”以及“Sending or Receiving a Message on Demand”。这里只对几个关键的问题加以说明。
1、仅 Microsoft Outloook 比较好地支持了 MAPI,包括 Microsoft Outlook Express 均不支持 MAPI 调用。
2、MAPI 看似复杂,实际上,他的几乎所有数据存储和访问,都是一个关键的接口:IMAPITable。也就是说,所有的数据存储都是类似表的结构。搞清楚了这个,再编码就很顺了,不管是发送接收邮件还是操作地址本,存取数据流程都是搞表格。:P
3、上面的问题2仍然不能解决。根据“Sending or Receiving a Message on Demand”的说明,关键的是 FlushQueues() 函数,但是我尝试了各种方法,均无法使它生效。若干天对自己的痛苦反思之后,终于怀疑微软有问题。于是我在网上翻出了一篇问答,中间有这么一段话:
“The IMAPIStatus::FlushQueues method appears not to work at all any more. Call it and you'll get a successful return code but nothing happens as a result. ” --Pete Maclean.
于是我彻底放弃。:(
七、结束语
本文是在给应用程序中添加邮件发送功能的心得。过程中遇到了很多问题,将他写出来,主要是给自己一个记录,也是我第一次将自己的编程过程整理成文档。
若本文还能帮到有同样需要的朋友,会令我很开心。也希望有这方面经验的朋友可以解决我的遗留问题。
欢迎发送邮件至 catking@163.com 共同探讨编程中的各种问题,分享乐趣。