首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

基於 Web 的程式版本檢查與自動更新:WinInet FTP 封裝類 CWebVersion 實作詳解

     編輯:關於C語言

現代應用程式普遍需要「啟動時檢查更新」功能。與其下載完整安裝包比對版本,不如將版本資訊以純文字檔置於 Web/FTP 伺服器,啟動時僅下載數百位元組即可完成比對。本文詳細實作 CWebVersion 封裝類,採用 WinInet API 實現 FTP 下載版本檔、解析版本號、比對邏輯,程式碼簡潔、無第三方依賴、易於整合。

設計目標

  • 最小化網路流量:版本檔 < 1KB
  • 零依賴:僅需 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(); // 啟動下載/安裝程式
            }
        }
    }
}

CWebVersion 類別完整定義

#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;
    }
};

WinInet FTP 四步驟核心流程

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 比對。
  • 可擴充:第二行放下載 URL、第三行放更新日誌連結。

進階功能擴充

部署注意事項

  • FTP 伺服器需開啟 被動模式,且防火牆開放資料埠範圍。
  • 版本檔建議放於 唯讀目錄,防止惡意竄改。
  • 生產環境請使用 HTTPS 或 FTPS,避免中間人攻擊竄改版本號。
  • 用戶端需處理 ERROR_INTERNET_INCORRECT_HANDLE_STATE 等常見錯誤碼。

總結

CWebVersion 以不到 100 行程式碼封裝了 WinInet FTP 下載、解析、比對的完整流程,零外部依賴、介面極簡、錯誤處理完善。採用「版本檔分離、按需下載」策略,極大降低啟動時網路負擔,是桌面應用程式自動更新功能的最佳實踐範本。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
功能實作要點
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 簽章