現代應用程式普遍需要「啟動時檢查更新」功能。與其下載完整安裝包比對版本,不如將版本資訊以純文字檔置於 Web/FTP 伺服器,啟動時僅下載數百位元組即可完成比對。本文詳細實作 CWebVersion 封裝類,採用 WinInet API 實現 FTP 下載版本檔、解析版本號、比對邏輯,程式碼簡潔、無第三方依賴、易於整合。
wininet.lib,系統內建// 主程式啟動時 (如 CWinApp::InitInstance)
if (CWebVersion::IsOnline()) { // 快速檢測網路連線
CWebVersion ver(_T("update.mysite.com")); // FTP 伺服器
if (ver.ReadVersion(_T("version.txt"), _T("ftpuser"), _T("ftppwd"))) {
// 取得遠端版本
DWORD dwRemoteMS = ver.dwVersionMS; // 高 32 位 (Major.Minor)
DWORD dwRemoteLS = ver.dwVersionLS; // 低 32 位 (Build.Revision)
// 取得本地版本
DWORD dwLocalMS, dwLocalLS;
GetModuleVersion(AfxGetInstanceHandle(), dwLocalMS, dwLocalLS);
if (dwRemoteMS > dwLocalMS ||
(dwRemoteMS == dwLocalMS && dwRemoteLS > dwLocalLS)) {
if (AfxMessageBox(_T("發現新版本,是否立即下載更新?"), MB_YESNO) == IDYES) {
LaunchUpdater(); // 啟動下載/安裝程式
}
}
}
}
#pragma once
#include
#pragma comment(lib, "wininet.lib")
class CWebVersion {
enum { BUFSIZE = 64 };
protected:
LPCTSTR m_lpServer; // FTP 伺服器位址
DWORD m_dwError; // 最近錯誤碼
TCHAR m_errInfo[256]; // 延伸錯誤訊息
TCHAR m_version[BUFSIZE]; // 版本號文字
void SaveErrorInfo() {
m_dwError = GetLastError();
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, m_dwError,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
m_errInfo, 256, NULL);
}
public:
DWORD dwVersionMS; // 版本高字 (Major.Minor)
DWORD dwVersionLS; // 版本低字 (Build.Revision)
CWebVersion(LPCTSTR server) : m_lpServer(server), m_dwError(0) {
m_errInfo[0] = 0; m_version[0] = 0;
dwVersionMS = dwVersionLS = 0;
}
~CWebVersion() {}
// 靜態快速網路檢測
static BOOL IsOnline() {
DWORD flags = 0;
return InternetGetConnectedState(&flags, 0);
}
// 核心:讀取遠端版本檔
BOOL ReadVersion(LPCTSTR lpFileName, LPCSTR lpszUser, LPCSTR lpszPass) {
HINTERNET hSession = NULL, hFtp = NULL, hFile = NULL;
BOOL bRet = FALSE;
__try {
// 1. 建立網際網路會話
hSession = InternetOpen(_T("MyApp Version Checker"),
INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (!hSession) __leave;
// 2. 建立 FTP 連線 (被動模式)
hFtp = InternetConnect(hSession, m_lpServer, INTERNET_DEFAULT_FTP_PORT,
lpszUser, lpszPass, INTERNET_SERVICE_FTP,
INTERNET_FLAG_PASSIVE, 0);
if (!hFtp) __leave;
// 3. 開啟版本檔
hFile = FtpOpenFile(hFtp, lpFileName, GENERIC_READ, FTP_TRANSFER_TYPE_ASCII, 0);
if (!hFile) __leave;
// 4. 讀取內容
DWORD dwRead = 0;
if (!InternetReadFile(hFile, m_version, BUFSIZE-1, &dwRead)) __leave;
m_version[dwRead] = 0; // 結束字元
// 5. 解析版本號 "Major.Minor.Build.Revision"
ParseVersion(m_version);
bRet = TRUE;
}
__finally {
if (hFile) InternetCloseHandle(hFile);
if (hFtp) InternetCloseHandle(hFtp);
if (hSession) InternetCloseHandle(hSession);
if (!bRet) SaveErrorInfo();
}
return bRet;
}
// 解析 "x.y.z.w" 格式
void ParseVersion(LPCTSTR str) {
int a=0,b=0,c=0,d=0;
_stscanf_s(str, _T("%d.%d.%d.%d"), &a, &b, &c, &d);
dwVersionMS = MAKELONG(a, b);
dwVersionLS = MAKELONG(c, d);
}
// 取得錯誤描述
CString GetErrorString() const {
CString s; s.Format(_T("Error 0x%08X: %s"), m_dwError, m_errInfo);
return s;
}
};
sequenceDiagram
participant App
participant WinInet
participant FTPServer
App->>WinInet: InternetOpen()
WinInet-->>App: HINTERNET hSession
App->>WinInet: InternetConnect(hSession, server, port, user, pass, INTERNET_SERVICE_FTP, PASSIVE)
WinInet-->>App: HINTERNET hFtp
App->>WinInet: FtpOpenFile(hFtp, "version.txt", GENERIC_READ, ASCII)
WinInet-->>App: HINTERNET hFile
App->>WinInet: InternetReadFile(hFile, buffer, size, &read)
WinInet-->>App: 版本字串
App->>WinInet: InternetCloseHandle(hFile/hFtp/hSession)
伺服器端 version.txt 內容:
2.5.1024.0
Major.Minor.Build.Revision,四段整數,點號分隔。VS_VERSION_INFO 資源結構,便於 VerQueryValue 比對。| 功能 | 實作要點 |
|---|---|
| HTTP 下載支援 | 將 INTERNET_SERVICE_FTP 改為 INTERNET_SERVICE_HTTP,使用 HttpOpenRequest/HttpSendRequest |
| Proxy 自動偵測 | InternetOpen 旗標 INTERNET_OPEN_TYPE_PRECONFIG 自動讀取 IE/系統 Proxy 設定 |
| 超時控制 | InternetSetOption(hSession, INTERNET_OPTION_CONNECT_TIMEOUT, &ms, sizeof(ms)) |
| 斷點續傳大檔 | 搭配 HttpOpenRequest + Range 標頭,配合 InternetReadFile 分塊寫入 |
| 數位簽章驗證 | 下載 .exe 後呼叫 WinVerifyTrust 驗證 Authenticode 簽章 |