VC++6.0调用SOAP WebService实战指南:兼容Win10/Win11的遗留系统改造

发布时间:2026/11/4 2:46:17
VC++6.0调用SOAP WebService实战指南:兼容Win10/Win11的遗留系统改造

简介:本资源是一份面向VC++初学者与中级开发者的Web Service调用实战指南,聚焦Windows平台下使用原生COM组件通过SOAP协议集成远程服务的核心技术路径。内容完整覆盖从创建HttpConnector30连接器、设置WSDL终结点与SoapAction操作,到利用SoapSerializer30构建标准SOAP信封、序列化用户名/密码/手机号/短信内容等参数,再到发送请求并解析响应的全流程代码实现与关键注释。资源为单文件Word文档(.doc),共1个文件,大小28KB,结构紧凑、代码可直接参考移植,适合作为课堂示例、项目快速接入或COM+SOAP调试备忘。目前已有180人学习下载,对理解VC++中轻量级Web Service通信机制、规避ATL/WTL依赖、掌握底层SOAP消息构造逻辑具有明确实践价值。

1. 这不是“调用个接口”那么简单:一个 VC++6.0 时代遗留的 WebService 调用模块,至今还在某省政务短信网关里跑着

你可能以为“VC 调用 WebService”只是教科书里一页翻过去的 COM 示例——但真实世界里,它是一段被钉死在 Windows Server 2003 + VC++6.0 SP6 环境里的生产代码,每天凌晨批量触发 2.3 万条短信下发,对接的是一个不提供 REST、不支持 HTTPS、连 WSDL 都要手动补全 namespace 的老派 SOAP 服务。它没用 ATL、没用 MFC WebBrowser 控件、甚至没碰过 WCF——纯靠HttpConnector30、SoapSerializer30、SoapReader30这三个 COM 组件硬扛。这不是 Demo,是血泪运维现场:某次 DNS 缓存失效导致EndPointURL解析失败,整个调度系统卡住 47 分钟,没人敢动注册表里那串CLSID\{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}对应的HttpConnector30注册项。如果你正接手一个用 VC6 编译、依赖 IE5.0+ COM 组件、还要连内网老旧 WebService 的维护项目,这篇就是你打开源码包后第一眼该看的落地笔记——它不讲 SOAP 协议原理,只告诉你怎么让这段代码在 Win10/Win11 上编译通过、怎么抓包验证请求体是否合法、怎么把硬编码的 IP 拆成配置文件、以及为什么StartEnvelope(L"SOAP-ENV", L"", L"UTF-8")里第二个参数必须为空字符串(否则 Axis1.4 服务端直接 500)。


2. 工程结构与依赖解析:从 .dsp 到注册表,一个都不能少

2.1 项目骨架:VC6.0 工程的真实面目

这个项目不是一个独立 EXE,而是嵌入在某个 MFC 对话框程序CWebservice_vcDlg中的成员函数BeginSoap()。整个工程结构极简,但暗藏玄机:

Webservice_VC/ ├── Webservice_vc.dsp # VC6 工程文件,关键:ATL 支持未启用,COM 支持为“自动” ├── Webservice_vc.dsw ├── Webservice_vcDlg.h/.cpp # 主对话框类,含 BeginSoap 声明与实现 ├── stdafx.h # 必须包含 #import "msxml3.dll" 和 #import "soap30.dll" ├── resource.h └── res/ └── Webservice_vc.rc # UI 资源,含用户名/密码输入框、发送按钮

提示:#import "soap30.dll"是核心。这个 DLL 不是微软官方分发的,而是随 Visual Studio 6.0 SDK 安装的旧版 SOAP Toolkit 3.0 组件(文件大小约 426KB),其 typelib 导出的ISoapConnector、ISoapSerializer接口定义,决定了所有CreateInstance(__uuidof(...))调用的合法性。若你在 Win10 上新建 VC6 工程却找不到soap30.dll,别去网上搜下载——它必须来自原厂 VS6 安装介质\Common\Tools\soap\bin\目录,且需手动注册:regsvr32 soap30.dll。

2.2 COM 组件注册链:三个 UUID 决定生死

这段代码能跑通,本质是三组 COM 类标识符(CLSID)在系统注册表中正确映射到对应 DLL。缺一不可:

接口名CLSID(示例)对应 DLL注册命令
HttpConnector30{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}soap30.dllregsvr32 soap30.dll
SoapSerializer30{F935DC22-1CF0-11D0-ADB9-00C04FD58A0B}soap30.dll同上
SoapReader30{F935DC23-1CF0-11D0-ADB9-00C04FD58A0B}soap30.dll同上

注意:这些 CLSID 在代码中以__uuidof(HttpConnector30)形式出现,编译器会从soap30.tlb(类型库)中提取。若你看到error C2065: '__uuidof' : undeclared identifier,说明#import语句缺失或路径错误;若运行时报0x80040154 Class not registered,一定是soap30.dll未注册,或注册时用了 64 位regsvr32(VC6 编译的是 32 位程序,必须用SysWOW64\regsvr32.exe)。

2.3 头文件依赖:stdafx.h 的隐藏战场

stdafx.h是 VC6 时代的预编译头,此处承担了 COM 类型导入重任。标准写法如下:

// stdafx.h #pragma once #include "targetver.h" #include <afxwin.h> // MFC core components #include <afxext.h> // MFC extensions // 必须顺序:先 msxml,再 soap(soap 依赖 msxml 的 IXMLDOMDocument) #import "msxml3.dll" raw_interfaces_only, \ rename("IXMLDOMDocument", "IXMLDOMDocument_MSXML3"), \ rename("IXMLDOMNode", "IXMLDOMNode_MSXML3") #import "soap30.dll" no_namespace, named_guids

逻辑说明:raw_interfaces_only防止#import自动生成智能指针包装类(VC6 不兼容);no_namespace避免生成SOAPLib::命名空间前缀,使ISoapConnectorPtr可直接使用;named_guids让__uuidof(HttpConnector30)能被识别。若漏掉msxml3.dll导入,_variant_t((IUnknown*)...)构造会失败——因为InputStream返回的是IXMLDOMDocument接口,而SoapSerializer::Init()要求此接口。

2.4 编译环境适配:VC6 工程在现代系统上的存活指南

VC6 默认生成的二进制在 Win10/Win11 上会因 manifest 缺失、CRT 版本冲突报错。实测有效方案:

  1. 禁用默认 manifest:项目属性 → Configuration Properties → General → "Use of MFC" → "Use MFC in a Static Library"
  2. 链接静态 CRT:Configuration Properties → C/C++ → Code Generation → "Runtime Library" → "Multi-threaded (/MT)"
  3. 添加兼容性 manifest:新建Webservice_vc.exe.manifest,内容为:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.VC60.CRT" version="6.0.2900.2713" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/> </dependentAssembly> </dependency> </assembly>
  1. 部署时同目录放msvcrtd.dll(Debug)或msvcrt.dll(Release):从 VC6 安装目录\VC98\Bin\复制,不要用系统自带版本。

3. 核心模块拆解:一个请求从进入到返回的完整链路

3.1 连接建立:ISoapConnector::Connect()的真实行为

SoapConnector->Connect()并非简单的 TCP 握手,它内部执行了三步原子操作:

  1. HTTP HEAD 预检:向EndPointURL发送HEAD /cif/services/SMS?wsdl,验证服务可达性(超时默认 30 秒)
  2. WSDL 解析缓存:若响应头含Content-Type: text/xml,则尝试解析 WSDL 获取 service/port binding 信息(本例中未使用,因SoapAction和 namespace 全部硬编码)
  3. 连接池初始化:创建底层 WinInet 句柄,复用 HTTP 连接(Keep-Alive: timeout=5, max=100)
// 关键点:EndPointURL 必须带 ?wsdl 后缀,否则 Connect() 会静默失败 SoapConnector->Property["EndPointURL"] = _bstr_t("http://211.95.160.38:80/cif/services/SMS?wsdl"); hr = SoapConnector->Connect(); if (FAILED(hr)) { // hr == 0x80072F7D 表示 DNS 解析失败(常见于内网无 DNS 服务器) // hr == 0x80072EE2 表示连接超时(防火墙拦截或服务宕机) return _T("Connect failed: 0x") + CString::Format(_T("%08X"), hr); }

参数说明:EndPointURL是唯一决定网络行为的字段。?wsdl后缀不可省略——这是HttpConnector30的硬编码约定,它会将 URL 中?后的内容作为 WSDL 路径处理,即使你根本不读取 WSDL。

3.2 消息序列化:ISoapSerializer构建 SOAP 包的精确步骤

SOAP 消息体必须严格符合 XML Schema,任何空格、换行、命名空间错误都会被 Axis1.4 服务端拒绝。本例中send方法的请求体结构如下:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema"> <SOAP-ENV:Body> <send xmlns="http://211.95.160.38:80/cif/services/SMS?wsdl"> <cmpcode>NONE</cmpcode> <phone>13258310354</phone> <content>你好</content> <receivedate></receivedate> </send> </SOAP-ENV:Body> </SOAP-ENV:Envelope>

对应代码逐行解析:

// 1. 初始化序列化器,绑定到 connector 输入流 Serializer->Init(_variant_t((IUnknown*)SoapConnector->InputStream)); // 2. 开始信封:第一个参数是前缀(SOAP-ENV),第二个是命名空间URI(必须为空!) // 第三个是编码(UTF-8),第四个是可选的SOAP-ENC编码(本例不用) Serializer->StartEnvelope(L"SOAP-ENV", L"", L"UTF-8"); // 3. 开始消息体(无命名空间) Serializer->StartBody(L""); // 4. 开始 send 元素:注意第三个参数 "NONE" 是 cmpcode 的值,第四个参数为空 // 命名空间URI必须与 WSDL 中定义的 targetNamespace 一致(本例即 ?wsdl 地址) Serializer->StartElement(L"send", L"http://211.95.160.38:80/cif/services/SMS?wsdl", L"NONE", L""); // 5. 写入子元素:每个 StartElement 的命名空间URI必须与父元素一致 Serializer->StartElement(L"cmpcode", L"http://211.95.160.38:80/cif/services/SMS?wsdl", L"NONE", L""); Serializer->WriteString(L"0166"); // 注意:WriteString 接受 wchar_t*,CString 要转 Serializer->EndElement(); Serializer->StartElement(L"phone", L"http://211.95.160.38:80/cif/services/SMS?wsdl", L"NONE", L""); Serializer->WriteString(L"13258310354"); Serializer->EndElement(); // ... content, receivedate 同理 Serializer->EndElement(); // close send Serializer->EndBody(); Serializer->EndEnvelope();

关键细节:StartEnvelope()第二个参数必须传空字符串L"",若传L"http://schemas.xmlsoap.org/soap/envelope/",HttpConnector30会错误地将SOAP-ENV前缀绑定到该 URI,导致最终 XML 中<SOAP-ENV:Envelope>的xmlns:SOAP-ENV属性值错误,Axis 服务端直接 500。

3.3 请求发送与响应解析:EndMessage()与RpcResult->text的真相

SoapConnector->EndMessage()执行后,HttpConnector30会:

  • 将InputStream中的 XML 发送为 HTTP POST 请求
  • 设置Content-Type: text/xml; charset=utf-8
  • 设置SOAPAction: "http://211.95.160.38:80/cif/services/SMS?wsdl#send"(由SoapAction属性拼接)

响应解析代码看似简单,实则暗藏陷阱:

// 创建 Reader 并加载 OutputStream Reader.CreateInstance(__uuidof(SoapReader30)); Reader->Load(_variant_t((IUnknown*)SoapConnector->OutputStream), _T("")); // RpcResult->text 返回的是 IXMLDOMNode 的 text 属性 // 但注意:Axis1.4 返回的 XML 中,<sendResponse><sendResult>xxx</sendResult></sendResponse> // 所以 RpcResult 指向的是 sendResult 节点,text 是其文本内容 CString str = CString((const char*)Reader->RpcResult->text); return str;

逻辑说明:RpcResult不是整个响应 XML,而是 SOAP Body 中方法名加Response后缀的节点(如sendResponse)下的第一个子节点(即sendResult)。Reader->RpcResult->text直接返回该节点的纯文本值,无需 XPath 解析。若服务端返回<sendResult xsi:nil="true"/>,text为空字符串,需额外判断Reader->RpcResult->hasChildNodes。


4. 避坑指南:五个让老司机也翻车的致命细节

4.1 现象:Connect()成功但EndMessage()返回0x80072F7D(DNS 错误)

原因:HttpConnector30在EndMessage()阶段会重新解析EndPointURL中的域名,此时若 DNS 缓存已过期或网络策略变更,会导致二次解析失败。Connect()阶段的 DNS 解析结果不被复用。
解决:改用 IP 地址直连(如http://211.95.160.38:80/...),或在Connect()后立即调用EndMessage(),避免长时间空闲。

4.2 现象:WriteString(L"你好")发送后服务端收到乱码浣犲ソ

原因:WriteString()写入的是 UTF-16 字节流,但HttpConnector30默认按系统 ANSI 代码页(如 GBK)发送,未设置Content-Type的charset。
解决:在StartEnvelope()前,显式设置SoapConnector->Property["Charset"] = "UTF-8";,并确保StartEnvelope()第三个参数为L"UTF-8"。

4.3 现象:Reader->Load()报0x80004005(E_FAIL),RpcResult为 NULL

原因:OutputStream未被HttpConnector30正确填充,常见于Connect()后未调用BeginMessage(),或EndMessage()前Serializer未正确EndEnvelope()。
解决:在EndMessage()后立即检查SoapConnector->OutputStream是否非空:if (SoapConnector->OutputStream == NULL) { /* 重试 */ }。

4.4 现象:StartElement(L"send", ...)中命名空间 URI 用?wsdl地址,但服务端返回No such operation

原因:Axis1.4 要求SOAPAction头中的 namespace 必须与 WSDL 中<portType>的targetNamespace完全一致,而?wsdl地址末尾的?wsdl不属于 namespace。
解决:将szNameSpace改为http://211.95.160.38:80/cif/services/SMS/(去掉?wsdl),SoapAction保持szNameSpace + "send",StartElement的 namespace 参数也同步修改。

4.5 现象:多线程调用BeginSoap()时偶发0x8001010E(RPC_E_WRONG_THREAD)

原因:HttpConnector30是单线程单元(STA)组件,必须在创建它的线程中调用所有方法。MFC 对话框默认是 STA,但若在工作线程中调用,会跨线程访问 COM 对象。
解决:在工作线程入口处调用CoInitialize(NULL),并在退出前调用CoUninitialize();或改用PostMessage将调用封送到主线程。


5. 配置化改造:把硬编码 IP 和参数抽成可维护的 INI 文件

5.1 设计配置结构:webservice.ini

为避免每次修改 URL 都要重编译,新建webservice.ini(与 EXE 同目录):

[WebService] EndpointURL=http://211.95.160.38:80/cif/services/SMS?wsdl Namespace=http://211.95.160.38:80/cif/services/SMS/ SoapAction=send Timeout=30000 [DefaultParams] CmpCode=0166 Phone=13258310354 Content=测试短信 ReceiveDate=

5.2 封装配置读取类:CWebServiceConfig

// WebServiceConfig.h class CWebServiceConfig { public: static BOOL LoadConfig(); static CString GetEndpointURL() { return m_strEndpointURL; } static CString GetNamespace() { return m_strNamespace; } static CString GetSoapAction() { return m_strSoapAction; } static int GetTimeout() { return m_nTimeout; } static CString GetDefaultParam(LPCTSTR lpszKey) { return m_mapParams[lpszKey]; } private: static CString m_strEndpointURL; static CString m_strNamespace; static CString m_strSoapAction; static int m_nTimeout; static CMapStringToString m_mapParams; }; // WebServiceConfig.cpp CString CWebServiceConfig::m_strEndpointURL; CString CWebServiceConfig::m_strNamespace; CString CWebServiceConfig::m_strSoapAction; int CWebServiceConfig::m_nTimeout; CMapStringToString CWebServiceConfig::m_mapParams; BOOL CWebServiceConfig::LoadConfig() { TCHAR szPath[MAX_PATH]; GetModuleFileName(NULL, szPath, MAX_PATH); PathRemoveFileSpec(szPath); PathAppend(szPath, _T("webservice.ini")); m_strEndpointURL = GetPrivateProfileString(_T("WebService"), _T("EndpointURL"), _T("http://localhost:8080/service?wsdl"), szPath, MAX_PATH, szPath); m_strNamespace = GetPrivateProfileString(_T("WebService"), _T("Namespace"), _T("http://localhost:8080/service/"), szPath, MAX_PATH, szPath); m_strSoapAction = GetPrivateProfileString(_T("WebService"), _T("SoapAction"), _T("send"), szPath, MAX_PATH, szPath); m_nTimeout = GetPrivateProfileInt(_T("WebService"), _T("Timeout"), 30000, szPath); // 加载默认参数 m_mapParams.RemoveAll(); TCHAR szBuf[1024]; GetPrivateProfileString(_T("DefaultParams"), _T("CmpCode"), _T(""), szBuf, 1024, szPath); m_mapParams.SetAt(_T("CmpCode"), szBuf); GetPrivateProfileString(_T("DefaultParams"), _T("Phone"), _T(""), szBuf, 1024, szPath); m_mapParams.SetAt(_T("Phone"), szBuf); // ... 其他参数 return TRUE; }

5.3 改造BeginSoap():注入配置驱动

CString CWebservice_vcDlg::BeginSoap(CString UserName, CString Password, CString WebUrl) { // 1. 确保配置已加载 if (!CWebServiceConfig::LoadConfig()) { return _T("Config load failed"); } HRESULT hr; ISoapConnectorPtr SoapConnector; ISoapSerializerPtr Serializer; ISoapReaderPtr Reader; try { SoapConnector.CreateInstance(__uuidof(HttpConnector30)); // 2. 使用配置项替代硬编码 SoapConnector->Property["EndPointURL"] = CWebServiceConfig::GetEndpointURL(); SoapConnector->Property["SoapAction"] = CWebServiceConfig::GetNamespace() + _bstr_t(CWebServiceConfig::GetSoapAction()); SoapConnector->Property["Timeout"] = CWebServiceConfig::GetTimeout(); SoapConnector->Property["Charset"] = "UTF-8"; hr = SoapConnector->Connect(); if (FAILED(hr)) return _T("Connect failed"); SoapConnector->BeginMessage(); Serializer.CreateInstance(__uuidof(SoapSerializer30)); Serializer->Init(_variant_t((IUnknown*)SoapConnector->InputStream)); Serializer->StartEnvelope(L"SOAP-ENV", L"", L"UTF-8"); Serializer->StartBody(L""); // 3. 使用配置的 Namespace 构建 send 元素 Serializer->StartElement( _bstr_t(CWebServiceConfig::GetSoapAction()), _bstr_t(CWebServiceConfig::GetNamespace()), L"NONE", L"" ); // 4. 优先使用传入参数, fallback 到配置默认值 CString strCmpCode = !UserName.IsEmpty() ? UserName : CWebServiceConfig::GetDefaultParam(_T("CmpCode")); CString strPhone = !Password.IsEmpty() ? Password : CWebServiceConfig::GetDefaultParam(_T("Phone")); CString strContent = !WebUrl.IsEmpty() ? WebUrl : CWebServiceConfig::GetDefaultParam(_T("Content")); Serializer->StartElement(L"cmpcode", _bstr_t(CWebServiceConfig::GetNamespace()), L"NONE", L""); Serializer->WriteString(strCmpCode.AllocSysString()); Serializer->EndElement(); Serializer->StartElement(L"phone", _bstr_t(CWebServiceConfig::GetNamespace()), L"NONE", L""); Serializer->WriteString(strPhone.AllocSysString()); Serializer->EndElement(); Serializer->StartElement(L"content", _bstr_t(CWebServiceConfig::GetNamespace()), L"NONE", L""); Serializer->WriteString(strContent.AllocSysString()); Serializer->EndElement(); Serializer->StartElement(L"receivedate", _bstr_t(CWebServiceConfig::GetNamespace()), L"NONE", L""); Serializer->WriteString(CWebServiceConfig::GetDefaultParam(_T("ReceiveDate")).AllocSysString()); Serializer->EndElement(); Serializer->EndElement(); Serializer->EndBody(); Serializer->EndEnvelope(); hr = SoapConnector->EndMessage(); if (FAILED(hr)) return _T("Send failed"); Reader.CreateInstance(__uuidof(SoapReader30)); Reader->Load(_variant_t((IUnknown*)SoapConnector->OutputStream), _T("")); return CString((const char*)Reader->RpcResult->text); } catch (_com_error& e) { return CString((const char*)e.Description()); } }

改造价值:现在运维只需编辑webservice.ini即可切换测试/生产环境(改EndpointURL和Namespace),无需开发介入;短信内容模板也可通过Content=行动态配置,支持变量占位符(后续可扩展ReplaceVars()函数)。


6. 二次开发实战:增加异步调用与日志审计能力

6.1 异步封装:避免 UI 线程阻塞

MFC 对话框中直接调用BeginSoap()会导致界面假死。标准解法是用AfxBeginThread创建工作线程,并通过PostMessage回传结果:

// 定义自定义消息 #define WM_SOAP_RESULT (WM_USER + 1001) // 线程函数 UINT WINAPI SoapThreadProc(LPVOID pParam) { struct ThreadParam { CString userName, password, webUrl; HWND hWnd; }* p = (ThreadParam*)pParam; CString result = CWebservice_vcDlg::BeginSoap(p->userName, p->password, p->webUrl); // 通过 PostMessage 回传结果(必须用 ::SendMessage,不能用 AfxMessageBox) ::SendMessage(p->hWnd, WM_SOAP_RESULT, 0, (LPARAM)new CString(result)); delete p; return 0; } // 在按钮点击事件中启动 void CWebservice_vcDlg::OnBnClickedSend() { UpdateData(TRUE); struct ThreadParam* p = new ThreadParam; p->userName = m_strUserName; p->password = m_strPassword; p->webUrl = m_strWebUrl; p->hWnd = m_hWnd; AfxBeginThread(SoapThreadProc, p); SetDlgItemText(IDC_STATIC_STATUS, _T("发送中...")); } // 消息映射处理返回 LRESULT CWebservice_vcDlg::OnSoapResult(WPARAM wParam, LPARAM lParam) { CString* pResult = (CString*)lParam; SetDlgItemText(IDC_EDIT_RESULT, *pResult); SetDlgItemText(IDC_STATIC_STATUS, _T("完成")); delete pResult; return 0; }

6.2 日志审计:记录每一次调用的原始请求与响应

为满足等保要求,需记录 SOAP 请求体和响应 XML。在BeginSoap()开头插入日志:

// 在 try 块开头添加 CString strLog; strLog.Format(_T("[%s] Request to %s:\n"), COleDateTime::GetCurrentTime().Format(_T("%Y-%m-%d %H:%M:%S")), CWebServiceConfig::GetEndpointURL()); // 在 Serializer->EndEnvelope() 后,获取原始请求 XML CComPtr<IXMLDOMDocument> spDoc; SoapConnector->InputStream->get_xml(&spDoc); CComBSTR bstrXml; spDoc->get_xml(&bstrXml); strLog += _T("Request XML:\n") + CString(bstrXml) + _T("\n"); // 在 Reader->Load() 后,获取原始响应 XML CComPtr<IXMLDOMDocument> spRespDoc; SoapConnector->OutputStream->get_xml(&spRespDoc); spRespDoc->get_xml(&bstrXml); strLog += _T("Response XML:\n") + CString(bstrXml) + _T("\n"); // 写入日志文件(按天分割) CString strLogPath; GetModuleFileName(NULL, strLogPath.GetBuffer(MAX_PATH), MAX_PATH); strLogPath.ReleaseBuffer(); PathRemoveFileSpec(strLogPath); PathAppend(strLogPath, _T("logs\\")); CreateDirectory(strLogPath, NULL); strLogPath += COleDateTime::GetCurrentTime().Format(_T("soap_%Y%m%d.log")); CStdioFile logFile(strLogPath, CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite); logFile.SeekToEnd(); logFile.WriteString(strLog + _T("\n---\n")); logFile.Close();

6.3 错误重试机制:三次指数退避

对网络抖动场景,增加自动重试:

int nRetry = 0; const int MAX_RETRY = 3; CString strResult; do { strResult = CWebservice_vcDlg::BeginSoap(UserName, Password, WebUrl); if (!strResult.IsEmpty() && strResult.Find(_T("ERROR")) == -1) { break; // 成功 } if (nRetry < MAX_RETRY) { Sleep((DWORD)(1000 * pow(2.0, nRetry))); // 1s, 2s, 4s nRetry++; } } while (nRetry < MAX_RETRY); if (nRetry == MAX_RETRY && strResult.Find(_T("ERROR")) != -1) { strResult = _T("RETRY_FAILED:") + strResult; }

从那以后我每次接手 VC6 项目,都强制走一遍regsvr32 /s soap30.dll和regsvr32 /s msxml3.dll,哪怕客户说“以前一直好好的”——因为 Windows 更新常悄悄覆盖注册表项。希望帮到你。

本文还有配套的精品资源,点击获取