MFC实现软件版本更新(一)

一、程序主要结构

主要分为两部分:待更新软件处的检查更新模块、自动更新程序模块。

由于更新功能是面向客户的,因此应尽可能地减少不必要的操作。

本篇文章主要是实现检查更新模块,需要在软件的Menu添加检测更新选项,Dialog处添加一个dialog:

点击Menu选项触发Dialog部分就略过了。

在检测更新这个模块需要实现以下功能:

  1. 解析本地配置文件'VersionInfo.xml'
  2. 获取当前版本号、服务端配置文件'ServerVersionInfo.xml'的URL
  3. 根据获得的URL进行下载
  4. 解析下载到本地的服务端配置文件'ServerVersionInfo.xml'
  5. 进行版本号比对,若当前版本号低于云端,则启动自动更新程序;反之,则提示无需更新

二、代码实现

创建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字符集环境下开发的。

通用软件自动更新模块,本程序不能主动运行,需要传递命令行参数,格式如下: 命令行参数 两种工作模式: 1.详细信息升级,格式如下(路径尽量使用""引起来,避免因为路径包含空格而调用失败): 自动升级模块可执行路径 需要升级的主程序路径 旧版本号 新版本程序下载地址 detail(固定写法) AutoUpdate.exe "E:\Visual Studio 2005\Projects\AutoUpdate\debug\测试.exe" 旧版本号 新版本号 http://.../WlwDir/10000/TaobaoDB.exe detail 2.主动分析模式,格式如下: 自动升级模块可执行路径 需要升级的主程序路径 旧版本号 新版本号(占位符,没实际用处,为了和模式1具有相同数量的参数,方便处理) 包含程序升级信息的网址(返回Json数据) linkurl(固定写法) AutoUpdate.exe "E:\Visual Studio 2005\Projects\AutoUpdate\debug\E语言示例.exe" 3.0 1.0 http://localhost:9572/UpdateWeb/Default.aspx linkurl http://localhost:9572/UpdateWeb/Default.aspx 返回数据为: {"SoftName":"测试软件","NewVersion":"2.0","DownUrl":"下载软件的url链接"} 如果返回的NewVersion比当前软件版本高,则执行升级,否则升级程序自动退出。 注意: 上述格式最后还有一个可选的参数,为待升级程序的主窗口句柄,如果不知道怎么回事,此参数可以忽略。 VC写法如下,sprintf(参数,"0x%x",GetSafeHwnd()) //使用Shell启动升级程序即可 ShellExecute(this->m_hWnd,"open","AutoUpdate.exe",命令行参数,NULL,SW_SHOW); 有不明白的朋友,请与我联系
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Completits_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值