还是在“PIA-MyPhotoGallery”中,为了能让使用者及时知道软件的更新版本发布,我增加了自动更新检查功能。鉴于这种功能具有很好的实用价值,所以写本文说明此功能的实现。
要实现更新检查,需要解决两个方面的问题:
1、通过Internet获取最新发布的版本号;
2、取得当前程序的版本,并与取得的最新版本相比较。
如果检查到有新版本发布,则打开下载页面(至于直接下载更新本文暂不讨论)。
对于第一个问题,最简单的解决办法就是在网站上发布新版本软件的同时,发布一个记录着版本号的文件。在软件进行版本检查时(比如程序启动时),通过Internet下载此文件,并读出最新的版本号。
注意:此方法仅适用于简单更新的情况,对于像杀毒软件的病毒库这样增量更新的情况,这种简单方法是不合适的,通常还需要有相应的服务端程序配合才行。
要通过Internet下载文件有很多方法,比如用WinINet API或现成的控件都可以。本文以Indy的TIdHTTP控件为例。
TIdHTTP控件的用法非常简单,但是直接使用它下载会有一个问题:程序会被阻塞着,直到文件被下载或连接超时(比如网络未连接)。所以必须将它放到线程中处理。
在PIA-MyPhotoGallery中,我用的代码如下:
//---------------------------------------------------------------------------
// Get new version thread
class 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 }
// init
constructor TMFileVersion.Create;
Begin
Inherited;
FMajor := 0;
FMinor := 0;
FRelease := 0;
FBuild := 0;
End;
destructor TMFileVersion.Destroy;
Begin
Inherited;
End;
// Get version info from a file
Procedure 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 size
Begin
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 info
Function 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 IF ( n1
解决了这两个问题,自动更新检查的功能也就解决了。