65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (9投票s)

2016年8月19日

CPOL

8分钟阅读

viewsIcon

29176

downloadIcon

528

如何使用 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.wsdlmessages.xsdtypes.xsd。要下载这些文件,您需要知道您的 Exchange 服务器。

您的 Exchange 服务器的格式应类似于以下示例:exchange.your_company.com、exchange.your_shcool.edu。

要下载Services.wsdlmessages.xsdtypes.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.xsdtypes.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.hstdsoap2.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);
现在我们将设置发送到 Web 服务的请求所需的所有信息。记住将 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.cppduration.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.cppduration.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 格式的函数时,此搜索将起作用。

如果您有任何疑问,请随时发表评论!

 

© . All rights reserved.