軟體的自動更新檢查
[Mental Studio]猛禽[Blog]
還是在“PIA-MyPhotoGallery”中,為了能讓使用者及時知道軟體的更新版本發布,我增加了自動更新檢查功能。鑒於這種功能具有很好的實用價值,所以寫本文說明此功能的實現。
要實現更新檢查,需要解決兩個方面的問題:
1、通過Internet擷取最新發行的版本號碼;
2、取得當前程式的版本,並與取得的最新版本相比較。
如果檢查到有新版本發布,則開啟下載頁面(至於直接下載更新本文暫不討論)。
對於第一個問題,最簡單的解決辦法就是在網站上發布新版本軟體的同時,發布一個記錄著版本號碼的檔案。在軟體進行版本檢查時(比如程式啟動時),通過Internet下載此檔案,並讀出最新的版本號碼。
注意:此方法僅適用於簡單更新的情況,對於像殺毒軟體的病毒庫這樣累加式更新的情況,這種簡單方法是不合適的,通常還需要有相應的服務端程式配合才行。
要通過Internet下載檔案有很多方法,比如用WinINet API或現成的控制項都可以。本文以Indy的TIdHTTP控制項為例。
TIdHTTP控制項的用法非常簡單,但是直接使用它下載會有一個問題:程式會被阻塞著,直到檔案被下載或連線逾時(比如網路未串連)。所以必須將它放到線程中處理。
在PIA-MyPhotoGallery中,我用的代碼如下:
//---------------------------------------------------------------------------// Get new version threadclass TGetNewVersionThread : public TThread{private: AnsiString FURL; TMFileVersion * FVer;protected: void __fastcall Execute( );public: __fastcall TGetNewVersionThread( AnsiString aURL ) : TThread( true ), FURL( aURL ), FVer( new TMFileVersion( ) ) { FreeOnTerminate = true; } __fastcall ~TGetNewVersionThread( ) { delete FVer; } __property TMFileVersion * Version = { read=FVer };};//---------------------------------------------------------------------------// TGetNewVersionThread//---------------------------------------------------------------------------void __fastcall TGetNewVersionThread::Execute( ){ boost::scoped_ptr webConn( new TIdHTTP( NULL ) ); boost::scoped_ptr ss( new TStringList( ) ); try { ss->Text = webConn->Get( FURL ); } catch ( ... ) { ss->Text = ""; } AnsiString s = ss->Values["piapg"]; if ( s != "" ) FVer->VerStr = s;}//---------------------------------------------------------------------------
這段代碼很簡單:建立一個線程,線上程裡建立一個TIdHTTP執行個體,然後下載URL對應的檔案,最後從中讀出“piapg”的版本號碼。為了偷懶,我用了boost庫裡的smart pointer--scoped_ptr。
這個線程類的使用方法如下:
//---------------------------------------------------------------------------// 在程式啟動時執行: if ( PIAPGCfg->AutoUpd ) // 如果選擇了“檢查更新”選項則執行檢查 { if ( SplashDlg ) // 如果有splash,則在其中顯示提示文本 { SplashDlg->labProgress->Caption = "正在檢查新版本..."; SplashDlg->labProgress->Refresh( ); } // 建立檢查新版本的線程 TGetNewVersionThread * pThread = new TGetNewVersionThread( "http://mental.mentsu.com/update.txt" ); pThread->OnTerminate = GetNewVersionDone; pThread->Resume( ); }//---------------------------------------------------------------------------// 版本檔案下載完成或逾時void __fastcall TMainForm::GetNewVersionDone(TObject * Sender){ TGetNewVersionThread * pThread = dynamic_cast( Sender ); boost::scoped_ptr fv( new TMFileVersion( ) ); fv->GetVersionFromFile( Application->ExeName ); // 讀取當前程式的版本 if ( ( pThread->Version->Compare( fv.get( ) ) > 0 ) // 如果有新版本,則提示 && ( Application->MessageBox( "發現更新版本的程式,是否現在更新?", "新版本檢查", MB_YESNO | MB_ICONINFORMATION ) == IDYES ) ) { ShellExecute( NULL, "open", "http://mental.mentsu.com", NULL, NULL, SW_SHOW ); PostQuitMessage( 0 ); }}//---------------------------------------------------------------------------
此代碼的功能詳見其中的注釋。
再來看第二個問題:程式版本的問題。
在上面的代碼中,用到了一個類:TMFileVersion。這是我以前用DELPHI寫的一個用於處理可執行檔版本號碼的類。實現代碼如下:
TMFileVersion = class private FMajor : Integer; FMinor : Integer; FRelease : Integer; FBuild : Integer; Function GetVerStr : String; Procedure SetVerStr( aVerStr : String ); public constructor Create; destructor Destroy; override; Procedure GetVersionFromFile( aFileName : String ); Function Compare( aVer : TMFileVersion ) : Integer; Property VerStr : String read GetVerStr write SetVerStr; End;{ TMFileVersion }// initconstructor TMFileVersion.Create;Begin Inherited; FMajor := 0; FMinor := 0; FRelease := 0; FBuild := 0;End;destructor TMFileVersion.Destroy;Begin Inherited;End;// Get version info from a fileProcedure TMFileVersion.GetVersionFromFile( aFileName : String );Type PVS_FIXEDFILEINFO = ^VS_FIXEDFILEINFO;Var h : Cardinal; // a handle, ignore nSize : Cardinal; // version info size pData : Pointer; // version info data pffiData : PVS_FIXEDFILEINFO; // fixed file info data nffiSize : Cardinal; // fixed file info sizeBegin FMajor := 0; FMinor := 0; FRelease := 0; FBuild := 0; If ( FileExists( aFileName ) ) Then FBuild := 1; nSize := GetFileVersionInfoSize( PChar( aFileName ), h ); If ( nSize = 0 ) Then Exit; GetMem( pData, nSize ); Try GetFileVersionInfo( PChar( aFileName ), h, nSize, pData ); If ( VerQueryValue( pData, '/', Pointer( pffiData ), nffiSize ) ) Then Begin FMajor := ( pffiData^.dwFileVersionMS ) SHR 16; FMinor := ( pffiData^.dwFileVersionMS ) AND $FFFF; FRelease := ( pffiData^.dwFileVersionLS ) SHR 16; FBuild := ( pffiData^.dwFileVersionLS ) AND $FFFF; End; Finally FreeMem( pData ); End;End;// Compare two version infoFunction TMFileVersion.Compare( aVer : TMFileVersion ) : Integer;Var n1, n2 : Cardinal;Begin n1 := ( FMajor SHL 16 ) OR FMinor; With aVer Do n2 := ( FMajor SHL 16 ) OR FMinor; If ( n1 > n2 ) Then Result := 1 Else If ( n1 < n2 ) Then Result := -1 Else Begin n1 := ( FRelease SHL 16 ) OR FBuild; With aVer Do n2 := ( FRelease SHL 16 ) OR FBuild; If ( n1 > n2 ) Then Result := 1 Else IF ( n1 < n2 ) Then Result := -1 Else Result := 0; End;End;// Get/Set property - VerStrFunction TMFileVersion.GetVerStr : String;Begin Result := Format( '%d,%.02d,%d,%.02d', [FMajor, FMinor, FRelease, FBuild] );End;Procedure TMFileVersion.SetVerStr( aVerStr : String );Var sTemp : TStrings;Begin FMajor := 0; FMinor := 0; FRelease := 0; FBuild := 0; sTemp := TStringList.Create; Try sTemp.CommaText := aVerStr; Try FMajor := StrToInt( sTemp.Strings[0] ); FMinor := StrToInt( sTemp.Strings[1] ); FRelease := StrToInt( sTemp.Strings[2] ); FBuild := StrToInt( sTemp.Strings[3] ); Except // Do nothing End; Finally sTemp.Free; End;End;
解決了這兩個問題,自動更新檢查的功能也就解決了。
BTW:為方便使用,已經改用DELPHI重寫並封裝為一個VCL控制項。
[Mental Studio]猛禽 Oct.30-04