分享
 
 
 

基于 Web 的程序版本检查

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

基于 Web 的程序版本检查

编译/NorthTibet

下载源代码

现在的很多程序都可以通过 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 (nRead0) {

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 如果Web版本较新,则对话框显示一个“更新版本 XXX”链接(如图二所示),否则隐藏链接,什么也不显示。在程序启动时用FTP读取10个字节的文件应该是很快的,但实际情况往往与想象的并不一样,对话框的显示总是会有一个延时,延时的长短要看FTP服务器的位置以及繁忙状态。因此,要想用户界面的响应速度够快,可以考虑在单独的线程进行文件的下载。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有