程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 基於Web的程序版本檢查

基於Web的程序版本檢查

編輯:關於VC++

現在的很多程序都可以通過 Internet 進行版本更新,Windows 操作系統本身的“Windows Update”就是一個典型的例子。要實現這種特性,首先必須對應用程序的版本進行檢查。那麼如何通過 Internet 對自己的程序進行版本檢查呢?本文將通過實際的例子程序來示范實現細節。

在進入正題之前,我想先羅嗦幾句,說說與此文內容有關的個人好惡:我很討厭程序顯示那些必須讓用戶干預的消息框,這種消息框很煩人,尤其是問你要不要更新的那種對話框。碰到這種情況我總是回答“No”,然後選擇“不要再來煩我”復選框(希望有一個這樣的選擇框)。 告訴用戶進行程序版本更新本身並沒有錯,但是必須用一種適當的友好的方式通知用戶,不要非得讓用戶來干預,除非是更新版本本身的行為。

但願我的個人好惡沒把你嚇跑。其實,實現基於Web的版本檢查有很多方法,2003年2月的 MSDN 雜志上有一篇標題為“使用.NET和後台智能傳輸服務API編寫自動更新應用”的文章,此文的作者是 Jason Clark。文章描述了一種全新的專用協議 BITS 來解決自動更新問題。有興趣的話可以仔細讀一讀。

但是,如果僅僅是為了檢查程序的版本,那麼可以將新的版本信息以文本形式保存在 Web 站點上,需要時通過 FTP 獲取文件信息。下載 文件的操作可以通過現成的 Windows Internet API 來實現,也就是大家都熟悉的 WinInet,如果你沒有用過它,沒關系,本文會詳細講述如何用它來編寫FTP程序。WinInet 的使用不難,他有固定的套路:第一步創建一個連接;第二步創建一個 FTP 會話;第三步打開文件;第四步讀取文件數據,就這麼簡單。用代碼表示就象下面這樣:HINTERNET h = InternetOpen(...);
HINTERNET hftp = InternetConnect(..,INTERNET_SERVICE_FTP,..);
HINTERNET hftpfile = FtpOpenFile(...);
InternetReadFile(...);

下面就讓我們深入細節,享受精彩。為了方便代碼的重用,我寫了類 CWebVersion,這個類對所有細節進行了封裝,實現的功能很簡單:就是通過 Web 來獲取程序版本信息,實現版本的檢查。這個類的使用也很方便:

if (CWebVersion::Online()) {
 CWebVersion ver("pub.chinafsdu.net");
 if (ver.ReadVersion("version.txt"),"pub","pub") {
  DWORD maj = ver.dwVersionMS;
  DWORD min = ver.dwVersionLS;
 }
}

下面是CWebVersion的聲明:////////////////////////////////////////////////////////////////
// WebVersion.h
//
#pragma once
class CWebVersion {
protected:
  enum { BUFSIZE = 64 };
  LPCTSTR m_lpServer;         // server name
  DWORD  m_dwError;          // most recent error code
  TCHAR  m_errInfo[256];       // extended error info
  TCHAR  m_version[BUFSIZ];      // version number as text
  void  SaveErrorInfo();       // helper to save error info
public:
  DWORD dwVersionMS;   // version number: most-sig 32 bits
  DWORD dwVersionLS;   // version number: least-sig 32 bits
  CWebVersion(LPCTSTR server) : m_lpServer(server) { }
  ~CWebVersion() { }
  static BOOL Online();
  BOOL   ReadVersion(LPCTSTR lpFileName,LPCSTR lpszUserName,LPCSTR lpszPassword);
  LPCTSTR GetVersionText()    { return m_version; }
  DWORD  GetError()       { return m_dwError; }
  LPCTSTR GetExtendedErrorInfo() { return m_errInfo; }
};
CWebVersion 的實現文件
////////////////////////////////////////////////////////////////
// WebVersion.cpp
//
#include "stdafx.h"
#include "WebVersion.h"
#include "InetHandle.h"
//////////////////
// Check if connected to Internet.
//
BOOL CWebVersion::Online()
{
  DWORD dwState = 0;
  DWORD dwSize = sizeof(DWORD);
  return InternetQueryOption(NULL,
    INTERNET_OPTION_CONNECTED_STATE, &dwState, &dwSize)
    && (dwState & INTERNET_STATE_CONNECTED);
}
//////////////////
// Read version number as string into buffer
//
BOOL CWebVersion::ReadVersion(LPCTSTR lpFileName,LPCSTR lpszUserName,LPCSTR lpszPassword)
{
  CInternetHandle hInternet;
  CInternetHandle hFtpSession;
  CInternetHandle hFtpFile;
  m_version[0] = 0;
  m_dwError=0;             // assume success
  m_errInfo[0]=0;           // ..
  DWORD nRead=0;
  hInternet = InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL,
              NULL, 0);
  if (hInternet!=NULL) {
   hFtpSession = InternetConnect(hInternet, m_lpServer,
     INTERNET_DEFAULT_FTP_PORT, lpszUserName, lpszPassword, INTERNET_SERVICE_FTP,
     0, NULL);
   if (hFtpSession!=NULL) {
     hFtpFile = FtpOpenFile(hFtpSession, lpFileName,
      GENERIC_READ, FTP_TRANSFER_TYPE_ASCII, NULL);
     if (hFtpFile!=NULL) {
      InternetReadFile(hFtpFile, m_version, BUFSIZE, &nRead);
      if (nRead>0) {
        m_version[nRead] = 0;
        int Mhi,Mlo,mhi,mlo;
        _stscanf(m_version, "%x,%x,%x,%x", &Mhi, &Mlo, &mhi, &mlo);
        dwVersionMS = MAKELONG(Mlo,Mhi);
        dwVersionLS = MAKELONG(mlo,mhi);
        return TRUE;
      }
     }
   }
  }
  // Failed: save error code and extended error info if any.
  m_dwError = GetLastError();
  if (m_dwError==ERROR_INTERNET_EXTENDED_ERROR) {
   DWORD dwErr;
   DWORD len = sizeof(m_errInfo)/sizeof(m_errInfo[0]);
   InternetGetLastResponseInfo(&dwErr, m_errInfo, &len);
  }
  return FALSE;
}

WebVersion::Online 檢查用戶是否連接到 Internet。如果用戶不在線,我會讓你決定做什麼——建議什麼也別做。如果用戶已經連接到 Internet,則顯示一個“版本檢查”按鈕,這樣用戶可以明確地發起與FTP服務器的連接(使用 WinInet 的InternetAttemptConnect 函數)。不論做什麼都要先詢問,再連接,不要太粗魯。現在很多人都是用貓上網,一定要有一個友好的用戶界面。 假設程序已經與 FTP 服務器連接上,為了讀取版本信息,只要建立一個 CWebVersion 對象實例,同時將 FTP 服務器地址傳入,然後用版本文件名作為參數調用 CWebVersion::ReadVersion 即可。ReadVersion 期望讀到下面這樣一行內容:4,3,0,0

這是用逗號分隔的四個數字,格式與應用程序 VS_VERSION_INFO 資源中的 FILEVERSION 和 PRODUCTVERSION 版本號相同。ReadVersion 將這個數字讀入兩個32位的 DWORDs,dwVersionMS 和 dwVersionLS。Windows 使用64位的版本號,所以你可以發布 18,446,744,070,000,000,000 版本。在美國,這是 18 乘以一百萬的三次方,而在歐洲這是 18 乘以一百萬的五次方。如果每秒鐘發布一個新版本,這些版本要八百萬個程序員發布 75,000 年。這樣的話,我們最好趕快練一下自己的打字速度。

圖一是本文的控制台例子程序(cslVerChkApp.exe)運行畫面,此程序使用 CWebVersion 通過 FTP 讀取版本號。

圖一 控制台例子程序運行畫面

下面我們來看看 CWebVersion 的工作原理。其大多數函數都是自解釋的;為了檢查系統是否連接,CWebVersion::Online調用了一個WinInet 函數 InternetQueryOption 來獲取 INTERNET_OPTION_CONNECTED_STATE。如果返回的狀態具備INTERNET_STATE_CONNECTED標志,說明有一個活動的連接。為了訪問 Internet 並讀取版本文件,CWebVersion 還用到了另外一個定制的C++類:

CInternetHandle:////////////////////////////////////////////////////////////////
// InternetHandle.h
//
#pragma once
class CInternetHandle {
protected:
  HINTERNET m_handle; // underlying handle
public:
  CInternetHandle() : m_handle(NULL) { }
  CInternetHandle(HINTERNET h) : m_handle(h) { }
  ~CInternetHandle() { Close(); }
  // Close handle and set to NULL so I don''t close again.
  void Close() {
   if (m_handle) {
     InternetCloseHandle(m_handle);
     m_handle = NULL;
   }
  }
  // Assignment from HINTERNET
  CInternetHandle& operator= (HINTERNET h)
  {
   ASSERT(m_handle==NULL); // only initial assignment, not copy
   m_handle = h;
   return *this;
  }
  // cast to HINTERNET
  operator HINTERNET() {
   return m_handle;
  }
};

這個類主要是封裝 HINTERNET。其作用是為了方便進行錯誤處理。如果某個WinInet 函數調用失敗,調用 GetLastError 會獲取失敗原因麼。但 CloseInternetHandle 會清除出錯代碼,所以,必須在 CloseInternetHandle 之前調用 GetLastError。看看下面的代碼就會明白:

////////////////////////////////////////////////////////////////
// 如果沒有 CInternetHandle 類。代碼可能會是下面這個樣子。
// 必須在幾個地方調用 GetLastError ,否則CloseInternetHandle 會
// 清除出錯代碼.
//
DWORD err;
HINTERNET h = InternetOpen(...);
if (h!=NULL) {
  HINTERNET hftp = InternetConnect(..,INTERNET_SERVICE_FTP,..);
  if (hftp!=NULL) {
    HINTERNET hftpfile = FtpOpenFile(...);
    if (hftpfile!=NULL) {
     InternetReadFile(...);
     if (/* success */) {
       // do something
     } else {
       err = GetLastError();
     }
     InternetCloseHandle(hftpfile);
    } else {
     err = GetLastError();
    }
    InternetCloseHandle(hftp);
  } else {
    err = GetLastError();
  }
  InternetCloseHandle(h);
} else {
  err = GetLastError();
}

有了 CInternetHandle 類,一切都便得簡單明了;ReadVersion 只需在最後檢查出錯情況。當控制流離開函數時,C++會自動關閉所有的 CInternetHandles。

MFC 程序員可能會問,MFC 都已經具備了 WinInet 封裝類,為什麼不用現成的東東,而要自己再做一個 CInternetHandle,原因很簡單:為了使用區區幾行代碼而加載整個 MFC 是非常浪費資源的。誰都知道,代碼越少越好。再者,不讓 CWebVersion 依賴於 MFC 是明智之舉。非 MFC 應用也能使用 CWebVersion 豈不是更好!不用擔心用戶是否加載了最新的DLLs。開發基於Web的應用尤其如此。

圖二 更新程序版本的對話框

為了進一步示范 CWebVersion 的使用,我寫了一個對話框程序,如圖二所示,每次啟動程序時,都會對版本進行檢查,讀取新版本數據是在 InitInstance()函數中進行的:

// 從 Web 讀取當前版本號
if (CWebVersion::Online()) {
 CWebVersion ver(_T("pub.chinafsdu.net"));
 if (ver.ReadVersion(_T("version.txt"),_T("pub"),_T("pub")))
  m_dwNewVersion = ver.dwVersionMS;
}

將讀取的版本號存儲在 m_dwNewVersion 數據成員中,這裡例子程序使用在32位的版本號。接著在對話框的 OnInitDialog() 例程中對版本號進行比較,當前的版本號存儲在程序的 VS_VERSION_INFO 資源中,這個信息在編譯程序時就已經產生了,獲取它很容易,參見另外一個C++類:CMyVersionInfo。通過比較:// 比較當前的程序版本與最新的程序版本

if (App.m_dwNewVersion && vi.dwProductVersionMS < App.m_dwNewVersion) {
  // current version is newer: display link
  s.Format(_T("更新版本 %d.%02d"),
  HIWORD(App.m_dwNewVersion),LOWORD(App.m_dwNewVersion));
  m_wndVerChkLink.SetWindowText(s);
} else {
  // current version not available: hide link
   m_wndVerChkLink.ShowWindow(SW_HIDE);
}

如果Web版本較新,則對話框顯示一個“更新版本 XXX”鏈接(如圖二所示),否則隱藏鏈接,什麼也不顯示。在程序啟動時用FTP讀取10個字節的文件應該是很快的,但實際情況往往與想象的並不一樣,對話框的顯示總是會有一個延時,延時的長短要看FTP服務器的位置以及繁忙狀態。因此,要想用戶界面的響應速度夠快,可以考慮在單獨的線程進行文件的下載。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
欄目導航
Copyright © 程式師世界 All Rights Reserved