以编程方式向电子邮件添加附件






4.63/5 (18投票s)
一种以编程方式向电子邮件添加附件的技术。
引言
在阅读了 Stephane Rodriguez 的出色且非常巧妙的以编程方式向 Outlook 电子邮件添加附件的解决方案后,我意识到此功能是多么有用。很多时候我都需要一种方法将应用程序的文档发送给另一个用户。 显然,可以通过编写自己的对话框来模拟邮件客户端对话框来完成,但是您必须连接地址簿等等。 最好利用PC上已安装的现有邮件客户端软件。 我正好需要这个功能,以便应用程序具有文件->发送到功能 - 显然,它需要附件才能工作。
SendTo 或 MailTo
Stephane的文章提出了一个完全有效的观点,即使用 SendTo 方法在功能上优于使用 mailto 技巧。 不使用 mailto 的另一个令人信服的理由是它不支持附件。 RFC mailto 协议很简单,没有指定附件。
但是,您经常会看到代码试图像这样使用 mailto
mailto:shiver@metimbers.com?Subject=Ahoy there
shipmate&Body=Here's the shipping
manifest&Attach="D:\manifest.doc"
它可能不会附加文档,因为您可以自由使用电子邮件客户端来实施 mailto 协议并包含附件子句的解析。 您可能不知道PC上安装了什么邮件客户端,因此它可能并非总是有效 - Outlook 肯定不支持使用 mailto 添加附件。
那么,做这件事最好的(也是最简单的)方法是什么?
Stephane的解决方案很简洁。它通过使用未发布的邮件帮助程序 COM 对象来模拟拖放到 Outlook 中。但是,通过使用未发布的功能,您将受制于供应商(在本例中为 Microsoft)更改内容! 这就是发生的事情。 在 Win2K 上使用 Outlook 2002/2003 时,拖放代码可以毫无问题地工作,但在 XP 上运行时会引发“特权异常”。 推测是此操作系统和 Outlook 版本组合存在权限问题。
我需要一个支持附件的与邮件客户端版本无关的解决方案,因此我选择研究 MAPI。 事实证明,答案非常简单,它被包装在 CSendFileTo
类中。
#ifndef __SENDFILETO_H__ #define __SENDFILETO_H__ #include <mapi.h> class CSendFileTo { public: bool SendMail(HWND hWndParent, CString const &strAttachmentFileName, CString const &strSubject=_T("")) { // The attachment must exist as a file on the system // or MAPISendMail will fail, so...... if (strAttachmentFileName.IsEmpty()) return false; // You may want to remove this check, but if a valid // HWND is passed in, the mail dialog will be made // modal to it's parent. if (!hWndParent || !::IsWindow(hWndParent)) return false; HINSTANCE hMAPI = ::LoadLibraryA(_T("MAPI32.DLL")); if (!hMAPI) return false; // Grab the exported entry point for the MAPISendMail function ULONG (PASCAL *SendMail)(ULONG, ULONG_PTR, MapiMessage*, FLAGS, ULONG); (FARPROC&)SendMail = GetProcAddress(hMAPI, _T("MAPISendMail")); if (!SendMail) return false; TCHAR szFileName[_MAX_PATH]; TCHAR szPath[_MAX_PATH]; TCHAR szSubject[_MAX_PATH]; ::StrCpy(szFileName, strAttachmentFileName.GetString()); ::StrCpy(szPath, strAttachmentFileName.GetString()); ::StrCpy(szSubject, strSubject.GetString()); MapiFileDesc fileDesc; ::ZeroMemory(&fileDesc, sizeof(fileDesc)); fileDesc.nPosition = (ULONG)-1; fileDesc.lpszPathName = szPath; fileDesc.lpszFileName = szFileName; MapiMessage message; ::ZeroMemory(&message, sizeof(message)); message.lpszSubject = szSubject; message.nFileCount = 1; message.lpFiles = &fileDesc; // Ok to send int nError = SendMail(0, (ULONG_PTR)hWndParent, &message, MAPI_LOGON_UI|MAPI_DIALOG, 0); if (nError != SUCCESS_SUCCESS && nError != MAPI_USER_ABORT && nError != MAPI_E_LOGIN_FAILURE) return false; return true; } }; #endif
示例用法
这个微不足道的代码片段显示了它有多么容易使用。
#include "SendFileTo.h" ... ... CSendFileTo sendTo; sendTo.(m_hWnd, _T("c://documents//menu.doc"), _T("Here's the lunch menu")); ... ...
一切都很简单,但有几点需要注意。
- 如果附件不存在于文件系统上的文件中,则对
MAPISendMail
的调用将失败,并显示MAPI_E_ATTACHMENT_NOT_FOUND
。 因此,请在 SendMail 调用开始时进行检查。 - 通过将父
HWND
传递给MAPISendMail
函数,电子邮件客户端应该使发送邮件对话框模式化为给定的HWND
。 您可能想要删除此模态性(?)并简单地使用HWND_DESKTOP
。 - 当进行
MAPISendMail
调用时,它不会发送邮件,而只是弹出一个电子邮件客户端对话框,其中设置了可选的主题行并附加了附件。 - 此代码编写为使用 WTL(它很棒)进行编译和工作,但同样也可以很好地使用 MFC。
我已经成功地在 Win2K 和 XP 上的 Outlook 2002 和 2003 上对此进行了测试。 我很想知道它是否可以与其他我无法访问的邮件客户端(如 Eudora 等)一起使用。
此类可能需要进行更多的错误检查,但我将其留给读者练习。