2005年,我升级——自动升级编程实例
编程爱好者
自动升级对程序的维护更新确实极为重要,而对开发者来说,要实现一个良好的自动升级程序,这项工作本身也充满了诱惑力。在进入具体的代码之前,我们先来看看一个基本的自动升级程序的编程思路。
一、动手前,我要编程思路
简单地说,自动升级前首先要判断当前系统是否为最新的版本,若不是,则需要自动在线更新系统。从实现的角度来讲,首先我们需要把当前最新的版本以及升级所需要的相关信息放在网上,然后在升级程序中读取这些信息。为了方便读取信息,我们将升级信息组织成一个INI文件,其中的信息包括版本号、该版本特性的说明、升级文件的下载地址、升级前所需要执行的操作,以及升级后需要执行的操作等。
然后我们在程序中比较版本号的大小,来判断是否需要更新。如果存在新版本,那我们就需要执行一些升级前的处理工作,然后下载升级文件,完成之后再进行一些升级之后的恢复工作。
有了清晰的思路之后,我们就来实际操作一下。编程工具使用的是VC6.0。
二、要升级,信息预处理
1.获取网上升级信息
要完成自动升级的工作,首先我们就需要打开包含升级信息的网络资源。那如何做到呢?假设m_strIniPath就是升级信息的网络地址,下面的代码就可以完成相应的功能:
CHttpFile *pFile = m_pHttp->OpenRe
quest(CHttpConnection::HTTP_VERB_GET,
m_strIniPath, NULL, 1, NULL, NULL, m_dwHttpRequestFlags);
if(!pFile->SendRequest())
{
pFile->Close();
return;
}
2.存储网上升级信息
如果利用上述代码打开资源成功后,我们就需要将读取到的数据先保存到一个本地临时文件update中。请看下面代码:
if(pFile)
{
char szTempPath [MAX_PATH] = {0};
::GetTempPath(MAX_PATH, szTempPa
th);
CString m_strTempDir = szTempPath;
CStdioFile csf;
csf.Open(m_strTempDir+"\\update",CFile::modeCreate|CFile::modeWrite|CFile::typeBinary);
char buf[2048];
int n;
while((n=pFile->Read(buf,2048))>0)
csf.Write(buf,n);
csf.Close();
pFile->Close();
}
代码解释:上述代码是将网上获得的信息顺次读入一个临时文件内。升级信息保存到本地文件之后,我们在接下来的操作中都需要读取这个文件中的信息。
3.判断是否需要升级
在做自动升级的工作前,还有一个重要的工作,就是将要升级的程序的版本和网上最新升级程序的版本相比较,看是否需要升级。这样的工作如何完成呢?请看下面的代码。其中m_strTempDir是保存升级信息的临时目录,m_strSoft是当前要升级程序的名称。
char buf[128];
GetPrivateProfileString(m_strSoft,"VERSION","1.0",buf,sizeof(buf),m_strTemp
Dir+"\\update");
m_strNewVer=buf;
//我们这里用一个单精度小数来表示版本号,其中m_strVersion是当前系统的版本号。
if(atof(m_strVersion)>=atof(buf))//现有系统已经是最新的版本
{
m_strStatus="您现在用的版本已是最新的!";
UpdateData(FALSE);
//不需要升级
return;
}
三、升级,我的主体工作
通过前面的准备工作,我们已经从网上获得了相关最新的升级信息,并将网上提供的程序版本与现在待升级程序的版本进行了比较。如果需要升级的话,那接下来的问题就是如何升级呢?让我们接着往下看。
系统升级前通常还需要进行一些处理工作,虽然每套系统所需要做的工作各不相同,但是总的来讲,还是可以分为以下几类行为:关闭某些应用程序,关闭某些服务,卸载组件等。为了便于扩展,我们这里通过一种简易的脚本来实现升级前的预处理工作。
1.脚本实现预处理
从升级信息中读取处理需要执行的命令。多个命令之间用“;”分隔,参数和命令之间用“?”分隔。如注册abc.dll可以写为:“regsvr32 ?-s “abc.dll””。
char commandTxt[10240] = {0};
::GetPrivateProfileString(m_strSoft,"PreCommand","",commandTxt,10240,m_strTempDir+"\\update");
//调用我们自己写的ExecuteCommand函数来执行命令。
ExecuteCommand(commandTxt);
下面我们来看看ExecuteCommand函数所需要做的工作:
void ExecuteCommand(char* lpComma
ndTxt)
{
char commandTxtTmp[10240] = {0};
strncpy(commandTxtTmp,lpCommandTx
t,10200);
lpCommandTxt = commandTxtTmp;
//提取脚本中命令的路径
下面的代码把命令中的“%PATH%”替换为当前路径m_strAppPath。其中支持简易的变量“%PATH%”,“%PATH%”代表当前路径。
char commandTxt[10240] = {0};
while(TRUE)
{
char* pTmp = strstr(lpCommandTxt,"%PATH%");
if(pTmp == NULL)
break;
*pTmp = 0;
strcat(commandTxt,lpCommandTxt);
strcat(commandTxt,m_strAppPath);
lpCommandTxt = pTmp +strlen("%PATH%");
}
strcat(commandTxt,lpCommandTxt);
//处理用“;”分隔的多条命令
char* pCommand = commandTxt;
while(TRUE)
{
char* pCurCommand = pCommand;
char* pCom = strchr(pCommand,';');
if(pCom == NULL)
break;
*pCom = 0;
pCommand = pCom +1;
//分离出参数。
char Para[1024] = {0};
char* pPara = strchr(pCurCommand,'?');
if(pPara != NULL)
{
*pPara = 0;
strcpy(Para,pPara+1);
}
//执行命令的具体执行内容
//以下的代码是执行命令。
SHELLEXECUTEINFO sei = {0};
//初始化SHELLEXECUTEINFO结构成员
sei.cbSize = sizeof(sei);//设置类型大小。
sei.hwnd = 0;
sei.lpVerb = "Open";
sei.lpFile = pCurCommand; //执行程序文件全路径名称。
sei.lpParameters = Para; //执行参数。
sei.lpDirectory = 0;
sei.nShow = SW_HIDE;
sei.fMask = SEE_MASK_NOCLOSEPR
OCESS;
//设置为SellExecuteEx函数结束后进程退出。
if(ShellExecuteEx(&sei) == TRUE)
WaitForSingleObject(sei.hProcess,INFINITE);//阻塞等待进程结束
}
}
2.下载文件完成升级
接下来可以下载升级文件完成升级了。其中strURL是下载地址,strFileName是下载后的文件名。
(1)读取升级文件的下载地址
char strURL [MAX_PATH];
GetPrivateProfileString(m_strSoft,” FILE”,"", strURL, MAX_PATH,m_strTemp
Dir+"\\update");
(2)下载文件完成升级
HRESULT hr = ::URLDownloadToCach
eFile(NULL,
strURL,
strFileName.
GetBuffer(MAX_PATH),
URLOSTRM_GETNEWESTVERSION,
0,
NULL);
以上只是举了下载一个文件的办法,对于多个文件就以此类推,另外也可以把多个文件压缩成一个文件,下载之后再解压。
四、小结
系统升级后我们通常还需要再做一些后续工作,主要有注册组件、注册服务,启动应用程序等。这里只是通过一个最简单的例子,向读者展现了按怎样的一个流程去开发一个自动升级的程序。程序根据不同的应用环境在功能上还可以作进一步的扩展,有兴趣的读者可以进行更多的尝试。
注:本程序在Windows2000+VC6.0下调试通过。