一、程序主要结构
主要分为两部分:待更新软件处的检查更新模块、自动更新程序模块。
由于更新功能是面向客户的,因此应尽可能地减少不必要的操作。
本篇文章主要是实现检查更新模块,需要在软件的Menu添加检测更新选项,Dialog处添加一个dialog:
点击Menu选项触发Dialog部分就略过了。
在检测更新这个模块需要实现以下功能:
- 解析本地配置文件'VersionInfo.xml'
- 获取当前版本号、服务端配置文件'ServerVersionInfo.xml'的URL
- 根据获得的URL进行下载
- 解析下载到本地的服务端配置文件'ServerVersionInfo.xml'
- 进行版本号比对,若当前版本号低于云端,则启动自动更新程序;反之,则提示无需更新
二、代码实现
创建IDD_CHECK_VERSION dialog
为该dialog添加类CCheckVersion
CheckVersion.h
#pragma once
// CCheckVersion 对话框
class CCheckVersion : public CDialogEx
{
DECLARE_DYNAMIC(CCheckVersion)
public:
CCheckVersion(CWnd* pParent = nullptr); // 标准构造函数
virtual ~CCheckVersion();
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_CHECK_VERSION };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedBtnCheckupdate();
};
CheckVersion.cpp
// CheckVersion.cpp: 实现文件
//
#include "stdafx.h"
#include "Inspect.h"//此处替换为你自己的头文件
#include "CheckVersion.h"
#include "VersionUpdate.h"
#include "afxdialogex.h"
// CCheckVersion 对话框
IMPLEMENT_DYNAMIC(CCheckVersion, CDialogEx)
CCheckVersion::CCheckVersion(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_CHECK_VERSION, pParent)
{
}
CCheckVersion::~CCheckVersion()
{
}
void CCheckVersion::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CCheckVersion, CDialogEx)
ON_BN_CLICKED(IDC_BTN_CHECKUPDATE, &CCheckVersion::OnBnClickedBtnCheckupdate)
END_MESSAGE_MAP()
// CCheckVersion 消息处理程序
void CCheckVersion::OnBnClickedBtnCheckupdate()
{
// TODO: 在此添加控件通知处理程序代码
//自动更新线程
AfxBeginThread(CVersionUpdate::ThreadCheckVer, this);
}
CCheckVersion主要是当点击检测更新按钮时,触发自动更新线程。
随后在CVersionUpdate中实现xml下载、解析、比对。
VersionUpdate.h
#pragma once
class CVersionUpdate
{
public:
CVersionUpdate(void);
~CVersionUpdate(void);
//自动更新线程
static UINT ThreadCheckVer(LPVOID pParam);
//下载包含更新版本号的配置文件
static INT DownloadIniFile(CString strUrl);
//下载指定地址的文件
static INT DownloadFile(const CString strUrl, const CString strSavePath);
};
VersionUpdate.cpp
#include "stdafx.h"
#include "VersionUpdate.h"
#include <WinBase.h>
#include <afxinet.h>
#include <afxdisp.h>
#include <atlstr.h>
//第三方xml解析 tinystr,tinyxml,tinyxmlerror,tinyxmlparser
#include "tinyxml.h"
#define BUF_SIZE 2048
#define WM_UPDATE WM_USER + 100
#define SERVERINFONAME "ServerVersionInfo.xml"
#define CLIENTINFONAME "VersionInfo.xml"
#define UPDATEEXENAME "Update.exe"
CVersionUpdate::CVersionUpdate() {}
CVersionUpdate::~CVersionUpdate() {}
UINT CVersionUpdate::ThreadCheckVer(LPVOID lpParam)
{
CString strUrl, programFolder;
double flCurVer, flNewVer;
wchar_t cBuf[MAX_PATH]; // 使用宽字符数组存储路径名
GetCurrentDirectoryW(MAX_PATH, cBuf); // 获取当前目录
programFolder = cBuf;
wcscat_s(cBuf, _T("\\" CLIENTINFONAME)); // 拼接出本地配置文件完整路径
TRACE(cBuf);
CString cFileName_xml = cBuf;
if (_waccess(cFileName_xml, 0) == -1) // 检查配置文件是否存在
{
AfxMessageBox(_T(CLIENTINFONAME "文件不存在!"));
return -1;
}
TRACE(cFileName_xml);
std::shared_ptr<TiXmlDocument> cdoc(new TiXmlDocument);
if (!cdoc->LoadFile(CStringA(cFileName_xml))) {
AfxMessageBox(_T("加载" CLIENTINFONAME "文件失败!"));
return -2;
}
TiXmlElement* cRoot = cdoc->RootElement();
if (!cRoot) {
AfxMessageBox(_T(CLIENTINFONAME "文件格式错误!"));
return -3;
}
TiXmlElement* clientElement = cRoot->FirstChildElement("Client");
if (!clientElement) {
AfxMessageBox(_T(CLIENTINFONAME "文件缺少Client节点!"));
return -4;
}
auto getText = [](TiXmlElement* element, const char* name) -> const char* {
auto childElement = element->FirstChildElement(name);
if (childElement) {
const char* text = childElement->GetText();
TRACE("Text content of <%s>: %s\n", name, text ? text : "NULL");
return text ? text : "";
}
else {
TRACE("Element <%s> not found!\n", name);
return "";
}
};
auto cXmlUrl = getText(clientElement, "XMLURL");//url获取不到
auto cMainver = getText(clientElement, "MAINVER");
auto cVerdate = getText(clientElement, "VERDATE");
auto cAuthor = getText(clientElement, "AUTHOR");
TRACE("<%s>\n", cMainver);
TRACE("<%s>\n", cXmlUrl);
//本地版本
flCurVer = _wtof(CString(cMainver));
strUrl = CString(cXmlUrl);
if (strUrl.IsEmpty()) {
AfxMessageBox(_T(CLIENTINFONAME "文件有误,请手动下载新版本!"));
return 0;
}
// 下载包含更新版本号的配置文件
INT downloadResult = DownloadIniFile(strUrl);
if (downloadResult != 0) {
AfxMessageBox(_T("检测新版本失败!如果软件不能使用请手动下载更新!"));
return 0;
}
// 从下载来的配置文件读入最新版本号
CString strServerXmlName;
wchar_t sBuf[MAX_PATH]; // 存储路径名
GetCurrentDirectoryW(MAX_PATH, sBuf); // 获取当前目录
wcscat_s(sBuf, _T("\\" SERVERINFONAME)); // 拼接出本地配置文件完整路径
strServerXmlName = sBuf;
TRACE(strServerXmlName);
if (_waccess(strServerXmlName, 0) == -1) //检查配置文件是否存在
{
AfxMessageBox(_T(SERVERINFONAME "文件不存在!"));
return 0;
}
std::shared_ptr<TiXmlDocument> sdoc(new TiXmlDocument);
if (!sdoc->LoadFile(CStringA(strServerXmlName))) {
AfxMessageBox(_T("加载XML文件失败!"));
return 0;
}
auto sRootElement = sdoc->RootElement();
auto ServerElement = sRootElement->FirstChildElement("Server");
//auto xmlUrl = getText(pServerElement, "XMLURL");
auto sMainver = getText(ServerElement, "MAINVER");
TRACE("<%s>\n",sMainver);
//服务端版本号
flNewVer = _wtof(CString(sMainver));
CString strPath;
// 对比版本号,如果大于本地版本就启动更新程序进行更新,更新成功就写入新版本到.xml里
if (flNewVer > flCurVer) {
int result = AfxMessageBox(_T("当前版本为[v%f],检测到新版本[v%f]\n是否更新?", flCurVer, flNewVer), MB_YESNO);
if (result == IDYES) {
// 用户点击了“是”按钮
//AfxMessageBox(_T("检测到新版本,"));
strPath.Format(_T("%s\\" UPDATEEXENAME), programFolder);
HINSTANCE hID = ShellExecute(NULL, _T("open"), strPath, NULL, NULL, SW_SHOWNORMAL);
if ((int)hID <= 32) {
AfxMessageBox(_T("程序目录下缺少升级程序" UPDATEEXENAME "!,请重新下载完整版本!"));
return 0;
}
ExitProcess(0); // 关闭本程序
}
else {
// 用户点击了“否”按钮
ExitProcess(0);
//return;
}
}
else {
AfxMessageBox(_T("当前为最新版本 [v%f]", flCurVer));
::DeleteFile(strServerXmlName); // 删除下载来的配置文件
}
return 0;
}
INT CVersionUpdate::DownloadIniFile(CString strUrl)
{
CString strInfoFileName, programFolder;
GetModuleFileName(NULL, strInfoFileName.GetBuffer(MAX_PATH), MAX_PATH);
strInfoFileName.ReleaseBuffer();
programFolder = strInfoFileName.Mid(0, strInfoFileName.ReverseFind(_T('\\'))) + _T("\\");
strInfoFileName = programFolder + _T(SERVERINFONAME);
INT flage = DownloadFile(strUrl, strInfoFileName);
//Debug
//AfxMessageBox(flage);
return flage;
}
INT CVersionUpdate::DownloadFile(const CString strUrl, const CString strSavePath)
{
if (strUrl.IsEmpty() || strSavePath.IsEmpty())
return -5;
unsigned short nPort;
CStringW strServer, strObject;
DWORD dwServiceType, dwRet;
if (!AfxParseURL(strUrl, dwServiceType, strServer, strObject, nPort))
return -1;
CInternetSession intsess;
CHttpFile* pHtFile = nullptr;
CHttpConnection* pHtCon = nullptr;
intsess.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 1000 * 20);
intsess.SetOption(INTERNET_OPTION_CONNECT_BACKOFF, 1000);
intsess.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1);
intsess.SetOption(INTERNET_OPTION_SEND_TIMEOUT, 6000);
intsess.SetOption(INTERNET_OPTION_RECEIVE_TIMEOUT, 6000);
try {
pHtCon = intsess.GetHttpConnection(strServer, nPort);
if (pHtCon == nullptr) {
intsess.Close();
return -2;
}
pHtFile = pHtCon->OpenRequest(CHttpConnection::HTTP_VERB_GET, strObject);
if (pHtFile == nullptr) {
intsess.Close();
delete pHtCon;
return -3;
}
pHtFile->SendRequest();
pHtFile->QueryInfoStatusCode(dwRet);
if (dwRet != HTTP_STATUS_OK) {
intsess.Close();
delete pHtCon;
delete pHtFile;
return -4;
}
}
catch (CInternetException* e) {
e->Delete();
intsess.Close();
delete pHtCon;
delete pHtFile;
return -2;
}
UINT nFileLen = (UINT)pHtFile->GetLength();
DWORD dwRead = 1;
CHAR* szBuffer = new CHAR[BUF_SIZE];
try {
CFile PicFile(strSavePath, CFile::modeCreate | CFile::modeWrite | CFile::shareExclusive);
while (dwRead > 0) {
memset(szBuffer, 0, BUF_SIZE);
dwRead = pHtFile->Read(szBuffer, BUF_SIZE);
PicFile.Write(szBuffer, dwRead);
}
PicFile.Close();
delete[] szBuffer;
delete pHtFile;
delete pHtCon;
intsess.Close();
}
catch (CFileException* e) {
delete[] szBuffer;
delete pHtFile;
delete pHtCon;
intsess.Close();
e->Delete();
return -7;
}
return 0;
}
顺便说下,解析XML是使用了第三方解析库TinyXML,下载和使用可以参考C++库(TinyXml)的安装和使用
并且该MFC程序是基于VS2017,x64,Unicode字符集环境下开发的。