如何在 C++ 中使用 gSOAP 使用 Exchange Web Service






4.87/5 (9投票s)
如何使用 gSOAP 连接 EWS 来发送、删除和检索电子邮件。
引言
本文将描述使用 gSOAP 连接到 Exchange Web Service 的过程。本文并未涵盖 EWS 的所有功能,但阅读本文应能为您提供使用 C++ 和 gSOAP 访问 EWS 操作的坚实基础。
gSOAP 是一个工具包,它通过为 C 和 C++ 创建 SOAP/XML 数据绑定来简化 SOAP/XML Web 服务的开发。有了 gSOAP,调用 SOAP Web 服务就变得非常容易,因为该工具包会自动生成 C/C++ 代码来处理发送和接收的 SOAP 消息。这消除了开发人员编写复杂代码来解析大型 XML 消息的需要。
EWS 是一项 Web 服务,用于访问预先存在的电子邮件帐户中的电子邮件。它是一个 SOAP API,因此它是展示 gSOAP 有多大用处的完美示例。
本文附带的代码由我和 Ning Xie 一人一半编写。
安装
gSOAP
请访问此页面了解如何在您的系统上下载和安装 gSOAP。
OpenSSL
您需要 OpenSSL 才能将 gSOAP 与 EWS 连接。您可以在他们的网站上找到安装方法。
Outlook 电子邮件帐户
您需要一个 Outlook 电子邮件帐户才能测试您的 EWS 客户端。如果您还没有帐户,可以在此页面上创建一个。
使用 gSOAP 从 EWS WSDL 生成 C++ 绑定
获取 .wsdl 文件
为了让 wsdl2h 工具(在下一步中介绍)运行,您需要您希望为其创建头文件的 Web 服务的 wsdl 文件。您需要下载三个文件:Services.wsdl
、messages.xsd
和 types.xsd
。要下载这些文件,您需要知道您的 Exchange 服务器。
您的 Exchange 服务器的格式应类似于以下示例:exchange.your_company.com、exchange.your_shcool.edu。
要下载Services.wsdl
、messages.xsd
和 types.xsd
,请在您的网络浏览器中输入以下链接
https://{your-exchange-server}/ews/Services.wsdl https://{your-exchange-server}/ews/messages.xsd https://{your-exchange-server}/ews/types.xsd
当您访问这些链接时,系统会提示您输入用户名和密码。使用您的 Exchange 电子邮件凭据,您就可以查看和下载文件。
请确保将文件保存在同一目录中。此外,您可能需要修改 Services.wsdl 以确保 messages.xsd
和 types.xsd
的位置不附加绝对路径(如下所示)。
<wsdl:types> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:import namespace="http://schemas.microsoft.com/exchange/services/2006/messages" schemaLocation="messages.xsd"/> <xs:schema/> <wsdl:types/>
并且 message.xsd 中使用 schemaLocation="types.xsd" 且不带路径
<xs:import namespace="http://schemas.microsoft.com/exchange/services/2006/types" schemaLocation="types.xsd"/>
步骤 1
要生成 EWS 头文件,请使用 EWS WSDL 文件和您期望的头文件名称(我们将使用 service.h)运行 wsdl2h
工具。您需要在工作目录中放置 typemap.dat
文件才能使该工具正常工作。
注意:在示例中,您的 typemap.dat 文件只需包含以下两行即可正确运行
ewsmsg = "http://schemas.microsoft.com/exchange/services/2006/messages"
ewstype = "http://schemas.microsoft.com/exchange/services/2006/types"
wsdl2h -o service.h -u Services.wsdl
第二步
现在我们将使用 soapcpp2
来生成将我们的代码连接到服务操作的数据绑定。请确保 stdsoap2.h
和 stdsoap2.cpp
位于您的工作目录中,或者 gsoap 文件夹的路径已包含在以下命令中。
soapcpp2 -j -CL -I /path/to/gSOAP/import service.h
选项 -j 生成 C++ 代理类,而 -CL 表示我们只创建客户端。您需要 gSOAP 导入文件的路径来处理 service.h
中的 #import 语句。将生成多个文件。
将 gSOAP 与 EWS 连接
以下示例将向您展示如何以编程方式查找电子邮件帐户中的项目并删除该特定项目。我们将使用生成的代理类来实现这一点。
示例 1:SendItem 服务操作(从“草稿”文件夹发送电子邮件)
在开始之前,请打开您的 Outlook 电子邮件帐户。创建一封电子邮件并将自己列为收件人。您可以设置主题和消息,但这不是必需的。现在,放弃这封电子邮件,使其保存在“草稿”文件夹中。这是我们将在下一步中发送的电子邮件。为了发送电子邮件,我们将需要知道它的 ID 和 Change Key。为了获取这些信息,我们将运行一个在本文附带的 tar 文件中定义的服务操作:testClient_FindItem.cpp
。此代码将在您的“草稿”文件夹中查找并返回所有项目 ID。要获取项目的 ID 和 Change Key,请从命令行运行 FindItem 操作,并像这样传入您的用户名和密码(我使用的是 clang)
clang++ -Wno-undefined-bool-conversion -o fi -DWITH_OPENSSL testClient_FindItem.cpp soapC.cpp soapExchangeServiceBindingProxy.cpp /Path/to/stdsoap2.cpp -I. /path/to/gSOAP/gsoap/custom/duration.c -lssl -lcrypto ./fi emailusername@outlook.com email_password
注意:在此步骤的末尾有更详细的编译说明。
您的草稿的 ID 和 Change Key 将打印到您的屏幕上。
注意:在测试 EWS 客户端时,我倾向于处理在“草稿”文件夹中找到的电子邮件,因为“草稿”文件夹通常比其他文件夹包含的内容少得多。您可以继续使用您喜欢的任何电子邮件位置,但您用来查找电子邮件 ID 的代码设置为在“草稿”文件夹中查找。
现在我们已经有了要发送的预先存在的电子邮件,我们可以开始编码了。以下代码可以在本文附带的 tar 文件中的 testClient_SendItem.cpp
中找到。
我们必须包含命名空间映射文件以及生成的代理头文件,以便 gSOAP 的生成代码能够帮助我们访问服务操作。
#include "ExchangeServiceBinding.nsmap"
#include "soapExchangeServiceBindingProxy.h"
#include "soapH.h"
#include <iostream>
#include <string>
接下来,我们将编写代码,以便从命令行传入用户名和密码参数,并处理登录您的电子邮件帐户的安全措施。请注意,这也是声明 ewsBinding 代理变量的地方。这将允许我们调用 SendItem 函数,以便将我们的草稿发送到收件箱!
static const char *userid;
static const char *passwd;
int main(int argc, char **argv)
{
if(argc>=3)
{
userid = argv[1];
passwd = argv[2];
}
ExchangeServiceBindingProxy ewsBinding("https://outlook.office365.com/EWS/Exchange.asmx");
soap_init1(ewsBinding.soap, SOAP_IO_DEFAULT | SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING | SOAP_XML_INDENT | SOAP_XML_NOTYPE);
ewsBinding.soap->userid = userid;
ewsBinding.soap->passwd = passwd;
soap_ssl_init();
if(soap_ssl_client_context(ewsBinding.soap,SOAP_SSL_DEFAULT | SOAP_SSL_SKIP_HOST_CHECK,NULL,NULL,"cacerts.pem",NULL,NULL))
{
soap_print_fault(ewsBinding.soap, stderr);
exit(1);
}
// set request server version
_ewstype__RequestServerVersion serVer;
enum ewstype__ExchangeVersionType exchangeVer = ewstype__ExchangeVersionType__Exchange2010_USCORESP2;
serVer.Version = exchangeVer;
ewsBinding.soap_header(NULL, NULL, NULL, &serVer,NULL,NULL);
itemtosend.Id
设置为 FindItem 返回的 ID,并将 change
设置为 FindItem 返回的 Change Key。 ewstype__NonEmptyArrayOfBaseItemIdsType itemArray;
itemArray.__size_NonEmptyArrayOfBaseItemIdsType = 1;
__ewstype__union_NonEmptyArrayOfBaseItemIdsType member;
ewstype__ItemIdType itemtosend;
itemtosend.Id = "";
std::string change = "";
itemtosend.ChangeKey = &change;
member.ItemId = &itemtosend;
itemArray.__union_NonEmptyArrayOfBaseItemIdsType = &member;
ewsmsg__SendItemType request;
__ewsmsg__SendItemResponse response;
request.ItemIds = &itemArray;
request.SaveItemToFolder = true;
现在是调用服务并处理结果的时候了。
if(ewsBinding.SendItem(&request, response)==SOAP_OK)
{
ewsmsg__ResponseMessageType* iirmt
= response.ewsmsg__SendItemResponse->ResponseMessages->__union_ArrayOfResponseMessagesType->SendItemResponseMessage;
if(iirmt != NULL)
{
if(iirmt->ResponseClass == ewstype__ResponseClassType__Error)
std::cout << *iirmt->__ResponseMessageType_sequence->MessageText <<std::endl;
else
std::cout << "Message Sent!";
}
}
else
ewsBinding.soap_stream_fault(std::cerr);
ewsBinding.destroy();
}
这将发送变量 request
中的 SOAP 请求,并将 SOAP 响应消息发送回 response
。调用 ewsBinding.destroy()
会删除所有托管数据。
这是完整的 testClient_SendItem.cpp
#include "ExchangeServiceBinding.nsmap"
#include "soapExchangeServiceBindingProxy.h"
#include "soapH.h"
#include <iostream>
#include <string>
static const char *userid;
static const char *passwd;
int main(int argc, char **argv)
{
if(argc>=3)
{
userid = argv[1];
passwd = argv[2];
}
ExchangeServiceBindingProxy ewsBinding("https://outlook.office365.com/EWS/Exchange.asmx");
soap_init1(ewsBinding.soap, SOAP_IO_DEFAULT | SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING | SOAP_XML_INDENT | SOAP_XML_NOTYPE);
ewsBinding.soap->userid = userid;
ewsBinding.soap->passwd = passwd;
soap_ssl_init();
if(soap_ssl_client_context(ewsBinding.soap,SOAP_SSL_DEFAULT | SOAP_SSL_SKIP_HOST_CHECK,NULL,NULL,"cacerts.pem",NULL,NULL))
{
soap_print_fault(ewsBinding.soap, stderr);
exit(1);
}
// set request server version
_ewstype__RequestServerVersion serVer;
enum ewstype__ExchangeVersionType exchangeVer = ewstype__ExchangeVersionType__Exchange2010_USCORESP2;
serVer.Version = exchangeVer;
ewsBinding.soap_header(NULL, NULL, NULL, &serVer,NULL,NULL);
ewstype__NonEmptyArrayOfBaseItemIdsType itemArray;
itemArray.__size_NonEmptyArrayOfBaseItemIdsType = 1;
__ewstype__union_NonEmptyArrayOfBaseItemIdsType member;
ewstype__ItemIdType itemtosend;
itemtosend.Id = "AAMkAGQ0MDg4MjMwLTYyZjEtNDc1Zi1hNWQwLWY4ZGFiODIyY2ZiOABGAAAAAADQJ5K0osuKQatv50kfuGI4BwALbeOJ+btaT4I3Qf8SQ+rtAAAAAAEQAAALbeOJ+btaT4I3Qf8SQ+rtAAKA2ysiAAA=";
std::string change = "CQAAABYAAAALbeOJ+btaT4I3Qf8SQ+rtAAKBBRvI";
itemtosend.ChangeKey = &change;
member.ItemId = &itemtosend;
itemArray.__union_NonEmptyArrayOfBaseItemIdsType = &member;
ewsmsg__SendItemType request;
__ewsmsg__SendItemResponse response;
request.ItemIds = &itemArray;
request.SaveItemToFolder = true;
if(ewsBinding.SendItem(&request, response)==SOAP_OK)
{
ewsmsg__ResponseMessageType* iirmt
= response.ewsmsg__SendItemResponse->ResponseMessages->__union_ArrayOfResponseMessagesType->SendItemResponseMessage;
if(iirmt != NULL)
{
if(iirmt->ResponseClass == ewstype__ResponseClassType__Error)
std::cout << *iirmt->__ResponseMessageType_sequence->MessageText <<std::endl;
else
std::cout << "Message Sent!";
}
}
else
ewsBinding.soap_stream_fault(std::cerr);
ewsBinding.destroy();
}
编译 SendItem 示例
我们必须使用 OPENSSL 进行编译,以确保我们的凭据安全传输。这需要 OpenSSL 和 OpenSSL 加密库。
您需要将 stdsoap2.cpp
和 duration.c
文件路径包含在 gSOAP 库中。
与 FindItem 编译类似,您需要将用户名和密码参数传递给可执行文件。
clang++ -Wno-undefined-bool-conversion -o si -DWITH_OPENSSL testClient_SendItem.cpp soapC.cpp soapExchangeServiceBindingProxy.cpp /Path/to/stdsoap2.cpp -I. /path/to/gSOAP/gsoap/custom/duration.c -lssl -lcrypto
./si emailusername@outlook.com email_password
当您运行 SendItem 操作时,您应该会在收件箱中收到一封新电子邮件!
示例 2:DeleteItem 服务操作
如前所述,为简化起见,我倾向于处理“草稿”文件夹,因此我们将删除“草稿”文件夹中的一封电子邮件。如果您希望从收件箱或其他位置删除,您可以轻松修改 testClient_FindItem.cpp
中的代码以搜索其他文件夹(将变量 dfit
设置为 ewstype__DistinguishedFolderIdNameType__inbox
在第 80 行搜索收件箱)。
像以前一样,在您的电子邮件帐户中创建一个草稿,然后运行 FindItem 服务操作来获取项目的 ID。Deleteitem 服务操作不需要 Change Key。
clang++ -Wno-undefined-bool-conversion -o fi -DWITH_OPENSSL testClient_FindItem.cpp soapC.cpp soapExchangeServiceBindingProxy.cpp /Path/to/stdsoap2.cpp -I. /path/to/gSOAP/gsoap/custom/duration.c -lssl -lcrypto ./fi emailusername@outlook.com email_password
现在我们已经有了要发送的预先存在的电子邮件,我们可以开始编码了。以下代码可以在本文附带的 tar 文件中的 testClient_DeleteItem.cpp
中找到。
我们必须包含命名空间映射文件以及生成的代理头文件,以便 gSOAP 的生成代码能够帮助我们访问服务操作。
#include "ExchangeServiceBinding.nsmap" #include "soapExchangeServiceBindingProxy.h" #include "soapH.h" #include <iostream> #include <string>
现在我们设置凭据,以便登录电子邮件帐户,同时创建我们将用于调用 Web 服务并接收结果的代理。
static const char *userid;
static const char *passwd;
int main(int argc, char **argv)
{
if(argc>=3)
{
userid = argv[1];
passwd = argv[2];
}
ExchangeServiceBindingProxy ewsBinding("https://outlook.office365.com/EWS/Exchange.asmx");
ewsmsg__DeleteItemType request;
__ewsmsg__DeleteItemResponse response;
soap_init1(ewsBinding.soap, SOAP_IO_DEFAULT | SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING | SOAP_XML_INDENT | SOAP_XML_NOTYPE);
ewsBinding.soap->userid = userid;
ewsBinding.soap->passwd = passwd;
soap_ssl_init();
if(soap_ssl_client_context(ewsBinding.soap,SOAP_SSL_DEFAULT | SOAP_SSL_SKIP_HOST_CHECK,NULL,NULL,"cacerts.pem",NULL,NULL))
{
soap_print_fault(ewsBinding.soap, stderr);
exit(1);
}
// set request server version
_ewstype__RequestServerVersion serVer;
enum ewstype__ExchangeVersionType exchangeVer = ewstype__ExchangeVersionType__Exchange2010_USCORESP2;
serVer.Version = exchangeVer;
ewsBinding.soap_header(NULL, NULL, NULL, &serVer,NULL,NULL);
现在是时候设置 DeleteItem 服务执行其任务所需的所有属性了。不要忘记将 item.Id
设置为 FindItem 服务返回的项目 ID。
ewstype__ItemIdType item; item.Id = ""; __ewstype__union_NonEmptyArrayOfBaseItemIdsType member; member.ItemId = &item; ewstype__NonEmptyArrayOfBaseItemIdsType itemArray; itemArray.__size_NonEmptyArrayOfBaseItemIdsType = 1; itemArray.__union_NonEmptyArrayOfBaseItemIdsType = &member; request.ItemIds = &itemArray; request.DeleteType = ewstype__DisposalType__SoftDelete; ewstype__AffectedTaskOccurrencesType ATOType = ewstype__AffectedTaskOccurrencesType__SpecifiedOccurrenceOnly; request.AffectedTaskOccurrences = &ATOType; // Identify how meeting cancellations are handled. ewstype__CalendarItemCreateOrDeleteOperationType CalType = ewstype__CalendarItemCreateOrDeleteOperationType__SendOnlyToAll; request.SendMeetingCancellations = &CalType;
现在剩下的就是调用 Web 服务了!
if(ewsBinding.DeleteItem(&request, response)==SOAP_OK) { ewsmsg__ResponseMessageType *iirmt = response.ewsmsg__DeleteItemResponse->ResponseMessages->__union_ArrayOfResponseMessagesType->DeleteItemResponseMessage; if(iirmt != NULL) { if (iirmt->ResponseClass == ewstype__ResponseClassType__Error) std::cout << *iirmt->__ResponseMessageType_sequence->MessageText <<std::endl; else std::cout << "Message deleted!"; } } else ewsBinding.soap_stream_fault(std::cerr); ewsBinding.destroy(); }
同样,不要忘记调用 ewsBinding.destroy()
来清理所有托管数据!
这是完整的 testClient_DeleteItem.cpp
#include "ExchangeServiceBinding.nsmap"
#include "soapExchangeServiceBindingProxy.h"
#include "soapH.h"
#include <iostream>
#include <string>
static const char *userid;
static const char *passwd;
int main(int argc, char **argv)
{
if(argc>=3)
{
userid = argv[1];
passwd = argv[2];
}
ExchangeServiceBindingProxy ewsBinding("https://outlook.office365.com/EWS/Exchange.asmx");
ewsmsg__DeleteItemType request;
__ewsmsg__DeleteItemResponse response;
soap_init1(ewsBinding.soap, SOAP_IO_DEFAULT | SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING | SOAP_XML_INDENT | SOAP_XML_NOTYPE);
ewsBinding.soap->userid = userid;
ewsBinding.soap->passwd = passwd;
soap_ssl_init();
if(soap_ssl_client_context(ewsBinding.soap,SOAP_SSL_DEFAULT | SOAP_SSL_SKIP_HOST_CHECK,NULL,NULL,"cacerts.pem",NULL,NULL))
{
soap_print_fault(ewsBinding.soap, stderr);
exit(1);
}
// set request server version
_ewstype__RequestServerVersion serVer;
enum ewstype__ExchangeVersionType exchangeVer = ewstype__ExchangeVersionType__Exchange2010_USCORESP2;
serVer.Version = exchangeVer;
ewsBinding.soap_header(NULL, NULL, NULL, &serVer,NULL,NULL);
ewstype__ItemIdType item;
item.Id = "AAMkAGQ0MDg4MjMwLTYyZjEtNDc1Zi1hNWQwLWY4ZGFiODIyY2ZiOABGAAAAAADQJ5K0osuKQatv50kfuGI4BwALbeOJ+btaT4I3Qf8SQ+rtAAAAAAEQAAALbeOJ+btaT4I3Qf8SQ+rtAAKA2yshAAA=";
__ewstype__union_NonEmptyArrayOfBaseItemIdsType member;
member.ItemId = &item;
ewstype__NonEmptyArrayOfBaseItemIdsType itemArray;
itemArray.__size_NonEmptyArrayOfBaseItemIdsType = 1;
itemArray.__union_NonEmptyArrayOfBaseItemIdsType = &member;
request.ItemIds = &itemArray;
request.DeleteType = ewstype__DisposalType__SoftDelete;
ewstype__AffectedTaskOccurrencesType ATOType = ewstype__AffectedTaskOccurrencesType__SpecifiedOccurrenceOnly;
request.AffectedTaskOccurrences = &ATOType;
// Identify how meeting cancellations are handled.
ewstype__CalendarItemCreateOrDeleteOperationType CalType = ewstype__CalendarItemCreateOrDeleteOperationType__SendOnlyToAll;
request.SendMeetingCancellations = &CalType;
if(ewsBinding.DeleteItem(&request, response)==SOAP_OK)
{
ewsmsg__ResponseMessageType *iirmt
= response.ewsmsg__DeleteItemResponse->ResponseMessages->__union_ArrayOfResponseMessagesType->DeleteItemResponseMessage;
if(iirmt != NULL)
{
if (iirmt->ResponseClass == ewstype__ResponseClassType__Error)
std::cout << *iirmt->__ResponseMessageType_sequence->MessageText <<std::endl;
else
std::cout << "Message deleted!";
}
}
else
ewsBinding.soap_stream_fault(std::cerr);
ewsBinding.destroy();
}
编译 DeleteItem 示例
我们必须使用 OPENSSL 进行编译,以确保我们的凭据安全传输。这需要 OpenSSL 和 OpenSSL 加密库。
您需要将 stdsoap2.cpp
和 duration.c
文件路径包含在 gSOAP 库中。
与 FindItem 编译类似,您需要将用户名和密码参数传递给可执行文件。
clang++ -Wno-undefined-bool-conversion -o di -DWITH_OPENSSL testClient_DeleteItem.cpp soapC.cpp soapExchangeServiceBindingProxy.cpp /Path/to/stdsoap2.cpp -I. /path/to/gSOAP/gsoap/custom/duration.c -lssl -lcrypto
./di emailusername@outlook.com email_password
通过软删除选项,电子邮件将不会发送到您的删除文件夹。如果您只想将项目移动到删除文件夹,请使用 testClient_MoveItem.cpp
(包含在本文附带的 tar 文件中)。当您编译并运行此示例时,您的电子邮件应该会从“草稿”文件夹中消失。
更多示例
现在您已经熟悉了连接 gSOAP 和 EWS 来创建一个客户端,您可以自行研究 tar 文件中的其他示例了!tar 文件中包含的文件如下:
testClient_CreateAttachment.cpp
testClient_DeleteAttachment.cpp
testClient_CreateFolder.cpp
testClient_DeleteFolder.cpp
testClient_FindFolder.cpp
testClient_MoveFolder.cpp
testClient_CreateItem.cpp
testClient_DeleteItem.cpp(本文介绍)
testClient_FindItem.cpp
testClient_SendItem.cpp(本文介绍)
testClient_MoveItem.cpp
testClient_PwdExpirationDate.cpp
如果您想了解其他服务操作,此页面将非常有用。如果您想了解 CreateItem,您可以在搜索栏中搜索 CreateItemtype Class,然后会显示 CreateItemtype Class (ExchangeWebServices) 的结果。如果单击该结果,将提供示例代码,其中显示了调用服务操作所需的属性(代码为 C#)。当您输入 Function_Nametype Class 格式的函数时,此搜索将起作用。
如果您有任何疑问,请随时发表评论!