在c++中使用Outlook Object Model发送邮件
一、Outlook Object Model简介
Outlook Object Model(OOM)是outlook为开发者提供的一个COM组件,我们可以在程序中使用它来发送邮件、管理邮箱等。相关介绍可以参见以下链接:
https://msdn.microsoft.com/en-us/library/ms268893.aspx
可能有人会说用shellExcute也可以实现对outlook的操作,这里正好说到为什么要用Outlook Object Model的原因之一。如果用shellExcute来操作,必须严格保证传入的参数的编码和长宽字节符合outlook的要求,也就是说在进行实际的邮件操作之前需要对字符串参数进行繁琐的判断和处理(如果是自己写的小程序玩一玩的请随意),而用OOM来操作则不需要关心这些问题。
OOM的另外一个优点就是接口简单,使用方便。要兼容所有的outlook版本也比较方便,这个下面再说。
二、在c++中的使用方式
因为微软的官网上关于OOM的例程主要是VBA和c#的,相关的例子有很多,这里就不多介绍了,主要介绍怎么在c++中使用。
可以参见下面这个链接:
http://1code.codeplex.com/SourceControl/changeset/view/60353#585447
主要有三种方式来使用OOM:
1、在程序中用#import命令来导入OOM的类型库,然后使用c++里的智能指针来调用相关的函数和属性。例程如下:
#import "C:\Program Files\Common Files\Microsoft Shared\OFFICE15\mso.dll" no_namespace \
rename("DocumentProperties","_DocumentProperties") \
rename("RGB", "MsoRGB") #import "D:\office\Office15\MSOUTL.OLB" \
rename("GetOrganizer", "GetOrganizerAE")\
rename_namespace("Outlook") using namespace Outlook;
void SendMail_UsingOOM(const std::wstring &, const std::wstring &, const std::wstring &, const std::wstring &, const std::wstring &, const std::wstring &, bool);
int main()
{
SendMail_UsingOOM(L"a@email.com", L"aa@email.com", L"aaa@email.com", L"OOM_Test", L"It`s OOM Test.Do not care about it.", L"C:\\Users\\Desktop\\aaa.pdf", true);
return ;
} void SendMail_UsingOOM(const std::wstring &to,const std::wstring &cc,const std::wstring &bcc,const std::wstring &subject,const std::wstring &body,const std::wstring &attachmentPath,bool showUI)
{
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
try
{
_ApplicationPtr spApp("Outlook.Application");
_NameSpacePtr pMAPI = spApp->GetNamespace("MAPI");
pMAPI->Logon(); _MailItemPtr olMail(spApp->CreateItem(olMailItem));
if ( == to.size())
{
pMAPI->Logoff();
return;
}
olMail->PutTo(_bstr_t(to.c_str()));
olMail->PutCC(_bstr_t(cc.c_str()));
olMail->PutBCC(_bstr_t(bcc.c_str()));
olMail->PutSubject(_bstr_t(subject.c_str()));
olMail->PutBody(_bstr_t(body.c_str()));
olMail->Save(); if ( != attachmentPath.size())
{
AttachmentsPtr olAtta = olMail->GetAttachments();
olAtta->Add(attachmentPath.c_str(), , , attachmentPath.c_str());
}
HRESULT result = NULL;
if (showUI)
{
result = olMail->Display(showUI);
}
else
{
result = olMail->Send();
}
pMAPI->Logoff();
}
catch (_com_error &err)
{
wprintf(L"Outlook throws the error: %s\n", err.ErrorMessage());
wprintf(L"Description: %s\n", (LPCWSTR)err.Description());
}
CoUninitialize();
}
要注意,这里的智能指针并不是你自己定义的,而是COM组件提供的。比如上面程序里的_ApplicationPtr、_NameSpacePtr等,因为是智能指针,也不需要手动释放,非常方便。
这里需要导入两个类型库文件:mso.dll和msoutl.olb,前者是office的,后者是outlook的。在导入后会生成mso.tlh、mso.tli、msoutl.tlh和msoutl.tli,tlh文件就相当于头文件,tli文件相当于cpp文件。实际上这个生成的过程只是最开始的时候执行了一次,生成的tlh、tli文件完全可以放到其他项目中包含(可能会产生重名问题,需要手动更改)。
在实际过程中,会发现import可能出现很多乱七八糟的错误,比如这样:
c:\OutlookAdd-In\OutlookAdd-In\debug\msoutl.tlh() : error C2556: 'Outlook::AddressEntryPtr Outlook::_AppointmentItem::GetOrganizer(void)' : overloaded function differs only by return type from '_bstr_t Outlook::_AppointmentItem::GetOrganizer(void)'
c:\OutlookAdd-In\OutlookAdd-In\debug\msoutl.tlh() : see declaration of 'Outlook::_AppointmentItem::GetOrganizer'
这其实是方法名冲突的问题,上面这个例子是GetOrganizer(void)这个方法冲突了,可以用rename关键字来修改namespace里实际生成的方法名以避免冲突。这里可以参见以下链接:
https://social.msdn.microsoft.com/Forums/office/en-US/f51cde52-0faa-42a2-bf61-c18b6f5b0e64/error-while-building-the-outlook-addin-code-with-ms-outlook-2010?forum=outlookdev
http://blog.chinaunix.net/uid-20791902-id-292054.html
2、第二种方式是使用COM组件提供的API来调用相关的函数,这种方式需要我们使用特定的API函数,将要调用的方法名和相关参数作为参数传入这些API中。例程如下:
// laterbind.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
struct Wrap_errno
{
HRESULT hr;
EXCEPINFO info;
};
Wrap_errno AutoWrap(int autoType, VARIANT *pvResult, IDispatch *pDisp,
LPOLESTR ptName, int cArgs...)
{
// Begin variable-argument list
va_list marker;
va_start(marker, cArgs); Wrap_errno err;
memset(&err, , sizeof err); if (!pDisp)
{
err.hr = E_INVALIDARG;
return err;
} // Variables used
DISPPARAMS dp = { NULL, NULL, , };
DISPID dispidNamed = DISPID_PROPERTYPUT;
DISPID dispID;
HRESULT hr; // Get DISPID for name passed
hr = pDisp->GetIDsOfNames(IID_NULL, &ptName, , LOCALE_USER_DEFAULT, &dispID);
if (FAILED(hr))
{
err.hr = hr;
return err;
} // Allocate memory for arguments
VARIANT *pArgs = new VARIANT[cArgs + ];
// Extract arguments...
for (int i = ; i < cArgs; i++)
{
pArgs[i] = va_arg(marker, VARIANT);
} // Build DISPPARAMS
dp.cArgs = cArgs;
dp.rgvarg = pArgs; // Handle special-case for property-puts
if (autoType & DISPATCH_PROPERTYPUT)
{
dp.cNamedArgs = ;
dp.rgdispidNamedArgs = &dispidNamed;
} // Make the call
EXCEPINFO excepInfo;
memset(&excepInfo, , sizeof excepInfo);
hr = pDisp->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT,
autoType, &dp, pvResult, &excepInfo, NULL);
if (FAILED(hr))
{
err.hr = hr;
err.info = excepInfo;
delete[] pArgs;
return err;
} // End variable-argument section
va_end(marker); delete[] pArgs; return err;
} Wrap_errno SendMail_UsingOOM(
const std::wstring &to,
const std::wstring &cc,
const std::wstring &bcc,
const std::wstring &subject,
const std::wstring &body,
const std::wstring &attachmentPath,
bool showUI
)
{
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // Define vtMissing for optional parameters in some calls.
VARIANT vtMissing;
vtMissing.vt = VT_EMPTY; CLSID clsid;
HRESULT hr; Wrap_errno err;
memset(&err, , sizeof err); LPCOLESTR progID = L"Outlook.Application";
hr = CLSIDFromProgID(progID, &clsid);
if (FAILED(hr))
{
CoUninitialize();
return err;
}
IDispatch *pOutlookApp = NULL;
hr = CoCreateInstance(
clsid, // CLSID of the server
NULL,
CLSCTX_LOCAL_SERVER, // Outlook.Application is a local server
IID_IDispatch, // Query the IDispatch interface
(void **)&pOutlookApp); // Output if (FAILED(hr))
{
if (pOutlookApp != NULL)
{
pOutlookApp->Release();
}
CoUninitialize();
return err;
} // pNS = pOutlookApp->GetNamespace("MAPI")
IDispatch *pNS = NULL;
{
VARIANT x;
x.vt = VT_BSTR;
x.bstrVal = SysAllocString(L"MAPI"); VARIANT result;
VariantInit(&result);
err = AutoWrap(DISPATCH_METHOD, &result, pOutlookApp, L"GetNamespace", , x);
if (err.hr < )
{
VariantClear(&result);
VariantClear(&x);
if (pOutlookApp != NULL)
{
pOutlookApp->Release();
}
if (pNS != NULL)
{
pNS->Release();
}
CoUninitialize();
return err;
}
pNS = result.pdispVal;
VariantClear(&x);
} // pNS->Logon(vtMissing, vtMissing, true, true)
{
VARIANT vtShowDialog;
vtShowDialog.vt = VT_BOOL;
vtShowDialog.boolVal = VARIANT_TRUE;
VARIANT vtNewSession;
vtNewSession.vt = VT_BOOL;
vtNewSession.boolVal = VARIANT_TRUE; AutoWrap(DISPATCH_METHOD, NULL, pNS, L"Logon", , vtNewSession,
vtShowDialog, vtMissing, vtMissing);
} // pMail = pOutlookApp->CreateItem(Outlook::olMailItem);
IDispatch *pMail = NULL;
{
VARIANT x;
x.vt = VT_I4;
x.lVal = ; // Outlook::olMailItem VARIANT result;
VariantInit(&result);
err = AutoWrap(DISPATCH_METHOD, &result, pOutlookApp, L"CreateItem", , x);
if (err.hr < )
{
if (pMail != NULL)
{
pMail->Release();
}
if (pNS != NULL)
{
pNS->Release();
}
if (pOutlookApp != NULL)
{
pOutlookApp->Release();
}
VariantClear(&x);
VariantClear(&result);
CoUninitialize();
return err;
}
pMail = result.pdispVal;
} // pMail->Subject = _bstr_t(L"Feedback of All-In-One Code Framework");
{
VARIANT x;
x.vt = VT_BSTR;
x.bstrVal = SysAllocString(subject.c_str());
AutoWrap(DISPATCH_PROPERTYPUT, NULL, pMail, L"Subject", , x);
VariantClear(&x);
} // pMail->To = _bstr_t(L"codefxf@microsoft.com");
{
VARIANT x;
x.vt = VT_BSTR;
x.bstrVal = SysAllocString(to.c_str());
AutoWrap(DISPATCH_PROPERTYPUT, NULL, pMail, L"To", , x);
VariantClear(&x);
} // pMail->cc
{
VARIANT x;
x.vt = VT_BSTR;
x.bstrVal = SysAllocString(cc.c_str());
AutoWrap(DISPATCH_PROPERTYPUT, NULL, pMail, L"Cc", , x);
VariantClear(&x);
} // pMail->bcc
{
VARIANT x;
x.vt = VT_BSTR;
x.bstrVal = SysAllocString(bcc.c_str());
AutoWrap(DISPATCH_PROPERTYPUT, NULL, pMail, L"Bcc", , x);
VariantClear(&x);
} // pMail->body
{
VARIANT x;
x.vt = VT_BSTR;
x.bstrVal = SysAllocString(body.c_str());
AutoWrap(DISPATCH_PROPERTYPUT, NULL, pMail, L"Body", , x);
VariantClear(&x);
} // pAtta = pMail->GetAttachments
// pAtta->Add(source,type,position,displayname)
IDispatch *pAtta = NULL;
{
VARIANT result;
VariantInit(&result);
err = AutoWrap(DISPATCH_PROPERTYGET, &result, pMail, L"Attachments", );
if (err.hr < )
{
if (pMail != NULL)
{
pMail->Release();
}
if (pNS != NULL)
{
pNS->Release();
}
if (pOutlookApp != NULL)
{
pOutlookApp->Release();
}
VariantClear(&result);
CoUninitialize();
return err;
}
pAtta = result.pdispVal; VARIANT path;
path.vt = VT_BSTR;
path.bstrVal = SysAllocString(attachmentPath.c_str());
VARIANT x;
x.vt = VT_I4;
x.lVal = ;
err = AutoWrap(DISPATCH_METHOD, NULL, pAtta, L"Add", , path, x, x, path);
if (err.hr < )
{
if (pAtta != NULL)
{
pAtta->Release();
}
if (pMail != NULL)
{
pMail->Release();
}
if (pNS != NULL)
{
pNS->Release();
}
if (pOutlookApp != NULL)
{
pOutlookApp->Release();
}
VariantClear(&result);
VariantClear(&path);
CoUninitialize();
return err;
}
VariantClear(&path);
} // pMail->Display(true);
{
VARIANT vtModal;
vtModal.vt = VT_BOOL;
vtModal.boolVal = VARIANT_TRUE;
err = AutoWrap(DISPATCH_METHOD, NULL, pMail, L"Display", , vtModal);
if (err.hr < )
{
if (pMail != NULL)
{
pMail->Release();
}
if (pNS != NULL)
{
pNS->Release();
}
if (pOutlookApp != NULL)
{
pOutlookApp->Release();
}
VariantClear(&vtModal);
CoUninitialize();
return err;
}
} // pNS->Logoff()
err = AutoWrap(DISPATCH_METHOD, NULL, pNS, L"Logoff", );
if (err.hr < )
{
if (pMail != NULL)
{
pMail->Release();
}
if (pNS != NULL)
{
pNS->Release();
}
if (pOutlookApp != NULL)
{
pOutlookApp->Release();
}
CoUninitialize();
return err;
} if (pMail != NULL)
{
pMail->Release();
}
if (pNS != NULL)
{
pNS->Release();
}
if (pOutlookApp != NULL)
{
pOutlookApp->Release();
} CoUninitialize();
err.hr = S_OK;
return err;
}
int main()
{
SendMail_UsingOOM(L"aaa@email.com", L"aaaa@email.com", L"aaaa@email.com", L"OOM_Test", L"It`s OOM Test.Do not care it.", L"C:\\Users\\Desktop\\aaa.pdf", true);
return ;
}
这里人工地把调用相关的API封装在了AutoWrap函数里,通过调用该函数即可调用相关操作的函数,可以看到主要的API就是CLSIDFromProgID、CoCreateInstance、GetIDsOfNames、Invoke这些COM函数,不熟悉的同学可以去看下com操作相关的资料。
要使用上面这种方式来操作OOM,你需要首先知道OOM提供的接口以及不同接口、不同属性之间的关系,因为你是通过com的API来调用OOM,它并没有生成外部代码,所以接口的逻辑在外部是不可见的。我们可以通过微软的官网来查看OOM的接口:
https://msdn.microsoft.com/en-us/library/office/microsoft.office.interop.outlook.aspx
3、第三种使用方式是在MFC工程中使用,依次打开MFC里的类向导-添加类-类型库中的MFC类,如下:
将msoutl.olb中的相关接口导入工程中,可以用哪个就导入哪个接口,类向导会为每个接口都生成一个类。一般是在接口的名字里以“C”代替“_”来命名。如下图:
之后我们就可以以类的形式来使用这些接口了,非常方便。例程如下:
void CAboutDlg::SendMail_UsingOOM(
const std::wstring &to,
const std::wstring &cc,
const std::wstring &bcc,
const std::wstring &subject,
const std::wstring &body,
const std::wstring &attachmentPath,
bool showUI
)
{
try
{
CApplication olApp;
COleException e;
if (!olApp.CreateDispatch(L"Outlook.Application", &e)) {
CString str;
str.Format(L"CreateDispatch() failed w/error 0x%08lx", e.m_sc);
AfxMessageBox(str, MB_SETFOREGROUND);
return;
}
// Logon. Doesn't hurt if you are already running and logged on...
CNameSpace olNs(olApp.GetNamespace(L"MAPI"));
COleVariant covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
olNs.Logon(covOptional, covOptional, covOptional, covOptional); // Prepare a new mail message
CMailItem olMail(olApp.CreateItem()); if ( != to.size())
olMail.put_To(to.c_str());
else
{
olNs.Logoff();
return;
} if ( != subject.size())olMail.put_Subject(subject.c_str());
if ( != body.size())olMail.put_Body(body.c_str());
if ( != cc.size())olMail.put_CC(cc.c_str());
if ( != bcc.size())olMail.put_BCC(bcc.c_str()); olMail.Save(); if ( != attachmentPath.size())
{
CAttachments olAtta(olMail.get_Attachments());
VARIANT vFile;
vFile = _variant_t(attachmentPath.c_str());//COleVariant(attachmentPath.c_str());
VARIANT vType;
vType.vt = VT_I4;
vType.lVal = ;
VARIANT vPosition;
vPosition.vt = VT_I4;
vPosition.lVal = ;
VARIANT vNick;
vNick.vt = VT_BSTR;
vNick.bstrVal = SysAllocString(attachmentPath.c_str());
olAtta.Add(vFile, vType, vPosition, vNick);
SysFreeString(vNick.bstrVal);
} if (showUI)
{
VARIANT vParam;
vParam.vt = VT_BOOL;
vParam.boolVal = showUI;
olMail.Display(vParam);
}
else
{
// Send the message!
olMail.Send();
}
olNs.Logoff();
//olApp.Quit();
}
catch (_com_error &err)
{
MessageBox(L"Outlook throws the error: %s\n", err.ErrorMessage());
MessageBox(L"Description: %s\n", (LPCWSTR)err.Description());
}
}
在COM里面传递的数据全部要通过VARIANT的类型来传递,它的vt属性表示传递的数据类型,不同的数据类型要用不同的联合属性表示,对应关系参见以下链接:
http://blog.csdn.net/heaven13483/article/details/8259110
三、eraly binding和late binding
有的同学说:在非MFC的c++项目里,用第一种方法就够了,干嘛要介绍第二种呢。的确,第一种方法有生成好的接口,方便我们理清接口逻辑关系、进行调试和维护,这种在导入后就已经生成固定接口的方式叫做early binding。程序像调用普通函数一样,在调用时知道这个函数的功能和逻辑,能够进行类型检查等安全措施。而第二种方法里,程序只是调用API告诉com组件我要调用哪个函数,至于它具体执行的功能程序并不知道,这就叫做late binding,也可以叫做动态绑定。
early binding确实有很多优点,由于接口在导入后就已经固定,它的速度比late binding快一倍;它能进行类型检查方便维护;它的逻辑代码简单......它的缺点是导入类型库并不稳定,有时候总是会出现各种错误。另外还有outlook版本兼容性的问题,这个下面再谈。
使用late binding的优点正是不需要特殊的类型库或者头文件,直接调用com的API就可以了,也就是说,这种方法在哪都能用。
详细地介绍可以参考以下链接:
https://msdn.microsoft.com/en-us/library/office/bb610234.aspx?f=255&MSPPError=-2147217396
https://support.microsoft.com/en-us/kb/245115
四、outlook版本库兼容问题
因为可能不同用户安装的的office版本是不同的,所以可能他们使用的outlook版本库是不同的。不同的outlook版本有不同的类型库,对应如下:
Outlook Version | Type Library |
---|---|
97 | msoutl8.olb |
98 | msoutl85.olb |
2000 | msoutl9.olb |
2002 | msoutl.olb |
2003 | msoutl.olb |
可以参考以下链接:
https://support.microsoft.com/en-us/kb/220600
怎么才能使我们的程序兼容不同的outlook版本呢?有以下两个方案:
1、使用early bind/MFC,使用要兼容的最早的版本的类库。因为early bind必须要选择一个类库来导入,而office版本是向下兼容的,所以如果我们最早要兼容到office 2003,就选择office 2003的版本的类型库。
2、使用late binding。这应该是late binding最大的优点了,即它不需要指定outlook版本,它会自动根据用户当前安装的office版本选择要使用的类型库。
在c++中使用Outlook Object Model发送邮件的更多相关文章
- 在C#开发中如何使用Client Object Model客户端代码获得SharePoint 网站、列表的权限情况
自从人类学会了使用火,烤制的方式替代了人类的消化系统部分功能,从此人类的消化系统更加简单,加速了人脑的进化:自从SharePoint 2010开始有了Client Side Object Model ...
- Qt 中的对象模型(Object Model)
原标题:Qt 中的对象模型(Object Model)90不太后,余生皆折腾 本节内容主要讲了 Qt 对象模型比标准 C++ 对象模型多了什么内容,并介绍了组成 Qt 对象模型基础的相关的类.最后说明 ...
- 【SSH网上商城项目实战24】Struts2中如何处理多个Model请求
转自: https://blog.csdn.net/eson_15/article/details/51465067 1. 问题的提出 Struts2中如果实现了ModelDriven<m ...
- Selenium的PO模式(Page Object Model)[python版]
Page Object Model 简称POM 普通的测试用例代码: .... #测试用例 def test_login_mail(self): driver = self.driver driv ...
- Selenium的PO模式(Page Object Model)|(Selenium Webdriver For Python)
研究Selenium + python 自动化测试有近两个月了,不能说非常熟练,起码对selenium自动化的执行有了深入的认识. 从最初无结构的代码,到类的使用,方法封装,从原始函数 ...
- 解决在使用client object model的时候报“object does not belong to a list”错误
在查看别人代码的时候,发现了个有意思的问题,使用client object model将一个文件check in 我使用的是如下语句获取file Microsoft.SharePoint.Client ...
- SharePoint Client Object Model API 介绍以及工作原理解析
CSOM和ServerAPI 的对比 SharePoint从2010开始引入了Client Object Model的API(后文中用CSOM来代替),从名字来看,我们可以简单的看出,该API是面向客 ...
- BOM (Browser Object Model) 浏览器对象模型
l对象的角色,因此所有在全局作用域中声明的变量/函数都会变成window对象的属性和方法; // PS:尝试访问未声明的变量会抛出错误,但是通过查询window对象,可以知道某个可能未声明的对象是否存 ...
- 文本对象模型(Document Object Model)
本文内容: 1. 概述 2. DOM中的节点类型 3. DOM节点的选取 4. 存取元素属性 5.DOM元素的增删 6.小结 ★ 概述 文本对象模型(DOM)是一个能够让程序和脚本动态访问和更新文档内 ...
随机推荐
- geektool--一款很geek的工具
2016/12/18 今天尝试一款很geek的工具 geektool 听名字就超级geek有木有 get it geektool website 从官网直接下载app,一键傻瓜式安装. use it ...
- Java 判断一段网络资源是否存在
package cn.ycmedia.common.utils; import java.io.InputStream; import java.net.URL; import java.net.UR ...
- Android_硬编码设置TextView字体大小
使用如下代码时,发现字号不会变大,反而会变小:size = (int) mText.getTextSize() + 1;mText.setTextSize(size);后来发现getTextSize返 ...
- HDU 3018 Ant Trip
九野的博客,转载请注明出处: http://blog.csdn.net/acmmmm/article/details/10858065 题意:n个点m条边的无向图,求用几笔可以把所有边画完(画过的边 ...
- “too many open files" ----增大打开的文件数
http://www.cnblogs.com/ibook360/archive/2012/05/11/2495405.html [root@localhost ~]# ab -n -c http:/ ...
- ubuntu 修改运行级别
只转载了成功的, 具体参见原文 http://www.2cto.com/os/201308/237632.html 第一种方法:(内核级别的) Sudo vi /etc/default/grub ...
- 转:Android中的Selector的用法
http://blog.csdn.net/shakespeare001/article/details/7788400
- cmd连接mysql连接:mysql-h主机地址-u用户名-p用户密码(注:u与root可以不用加)
MySQL导入导出命令1.导出整个数据库 mysqldump -u 用户名 -p 数据库名 > 导出的文件名 mysqldump -u wcnc -p smgp_apps_wcnc >wc ...
- jquery获取value值
$(function(){ alert(1); var a=$("#a004").val(); var a1=$("#b004").val(); //.val就 ...
- SQL 里面的COALESCE函数
在SQL里面除了is null 还有这样一个还用的方法 COALESCE(值[, ...]) select COALESCE(NULL,NULL,'AAAA') -> 'AAAA' 意思是前面的 ...