win32程序在线自动升级(更新)的方法

来源:赵克立 分类: Win32 标签:Win32发布时间:2018-03-28 11:42:50浏览:1209
版权声明:
本文为博主原创文章,转载请声明原文链接...谢谢。o_0。
温馨提示:
技术类文章有它的时效性,请留意文章更新时间以及软件的版本
更新时间:
2018-03-31 12:20:47

程序使用过程肯定会跟进修改一些bug,添加一些新功能等这个时候就要有在线更新功能啦。具体要求就是最常见的更新下载然后自动重新启动,

自动更新方案

实现这种方法至少要两个程序一个主程序(main.exe)一个更新程序(Update.exe)

主程序启动后另起一个新的线程调用Update.exe来检查软件并更新,单单这样也不可以,因为Update.exe更新时候是占用的状态,是覆盖不了的。

Update.exe启动的时候先检查自己的文件名字是不是UpdateTem.exe,如果不是的话就把自己复制一份,复制到当前目录中的这个路径 Update/UpdateTem.exe 当前程序结束,  然后运行这个临时文件临时文件运行的时候也会检查当前的文件名字,这一次文件名字就等于UpdateTem.exe啦。就正常运行更新程序,把更新文件zip包直接解压到上级目录覆盖就可以啦


更新程序要接收的参数字符串如下 

update.exe 1.0.0  url   pid

参数:

1.0.0 当前程序的版本号

url 检查软件版本信息地址

pid 主程序启动后的进程pid,如果有更新的话要用pid结束掉原来的主进程,然后由更新程序重启启动

检查更新格式

返回一个json的格式字符串

<?php
header('Content-type: application/json; charset=utf-8'); //json
$descr = <<<eot
v0.2.0
修复反馈的bug
添加一些新功能
新版本的一些特性
eot;
$jsondata = [
	"lastversion" => "0.2.0", //版本号
	"downloadurl" => "http://www.xxx.com/update.zip", //新版本压缩包
	"descr"       => $descr, //新版本的一些描述
	"run"         => "HeMei_u_d.exe", //更新成功后执行的当前目录的程序,如果为空就什么也不启动
	"autoupdate"  => false,
];
echo json_encode($jsondata);


Update升级程序部分代码

一般情况下程序退出都是0,我们可以给更新程序加一些自定义的退出码,方便主程序实现更复杂的功能

//更新程序的退出码,

//0没有新版本,

//1有新版本并且已经下载替换重启启动,

//2有新版本但是取消下载啦

//3出现错误

把判断是不是运行的临时的更新文件

//取当前执行模块的全路径,如果此模块是被其它程序调用的,返回的路径还是这个程序的路径
::GetModuleFileName(NULL, MainWnd::s_curPath, MAX_PATH);
//从路径中移除文件名
PathRemoveFileSpec(MainWnd::s_curPath);
TCHAR curFileName[MAX_PATH];
//取当前可执行文件的全路径
::GetModuleFileName(NULL, curFileName, MAX_PATH);
TCHAR* fname = ::PathFindFileName(curFileName);//取文件名
//临时的更新文件名
TCHAR temFileName[] = _T("UpdateTem.exe");
if (_tcscmp(fname, temFileName) != 0) {
    TCHAR opath[MAX_PATH] = { 0 }, npath[MAX_PATH] = { 0 }, updatepath[MAX_PATH] = { 0 };
    _stprintf(updatepath, _T("%s\\Update\\"), MainWnd::s_curPath);
    if (!PathIsDirectory(updatepath))
    {
        if (!CreateDirectory(updatepath, 0))
            return false;
    }
    _stprintf(opath, _T("%s\\Update.exe"), MainWnd::s_curPath);
    _stprintf(npath, _T("%s\\Update\\%s"), MainWnd::s_curPath, temFileName);
    //把自己copy到二级目录运行二级目录中的文件
    CopyFile(opath, npath, false);
    *opath = { 0 }, *npath = { 0 };
    _stprintf(opath, _T("%s\\DuiLib_u_d.dll"), MainWnd::s_curPath);
    _stprintf(npath, _T("%s\\Update\\DuiLib_u_d.dll"), MainWnd::s_curPath);
    //把自己copy到二级目录运行二级目录中的文件
    CopyFile(opath, npath, false);
    TCHAR strFilePath[MAX_PATH] = { 0 };
    _stprintf_s(strFilePath, _T("\"%s%s\""), updatepath, temFileName);
    ShellExecute(NULL, _T("open"), strFilePath, lpCmdLine, _T(""), SW_HIDE);
    return 0;
}
//运行到这里的时候update.exe已经被放进二级目录啦,所以当前目录路径要返回一级目录
TCHAR* pp = _tcsrchr(MainWnd::s_curPath, _T('\\'));
if (pp) {
    *pp = 0;
}

解析传进来的参数

//命令行接收格式为 update.exe  1.0.0 url  pid
LPTSTR *szArgList;
int argCount;
szArgList = CommandLineToArgvW(GetCommandLine(), &argCount);
#ifdef _DEBUG
//argCount = 4;
//szArgList[0] = L"update.exe";
//szArgList[1] = L"0.1.0";
//szArgList[2] = L"http://crm369.hemeids.com:8888/CheckUpdate.php";
//szArgList[3] = L"0";
#endif // DEBUG
if (szArgList == NULL) {
    MessageBox(NULL, L"没有参数传进来直接退出", L"提示", 0);
    return 0;
}
if (argCount < 4) {
    MessageBox(NULL, L"参数格式不正确", L"提示", 0);
    return 0;
}

检查对比新版本号

启动时由上面代码解析出传过来的版本号,和从url中取过来的版本号

tstring serverVersion = L"0.2.0";
tstring curVersion = L"0.0.1";
//对比两个版本的新旧
const TCHAR * split = L".";
const int verlen = 3;
//两个版本的数字数组
int version1[verlen] = { 0 }, version2[verlen] = { 0 };
TCHAR * p;
p = _tcstok((TCHAR*)curVersion.c_str(), split);
int i = 0;
while (p != NULL) {
    int nums = _ttoi(p);
    version1[i++] = nums;
    p = _tcstok(NULL, split);
    if (i > verlen)break;
}
p = _tcstok((TCHAR*)curVersion.c_str(), split);
i = 0;
while (p != NULL) {
    int nums = _ttoi(p);
    version2[i++] = nums;
    p = _tcstok(NULL, split);
    if (i > verlen)break;
}
bool isUpdate = false;
for (int j = 0;j <= verlen;j++)
{
    if (version2[j] > version1[j]) {
        isUpdate = true;
        break;
    }
}
//没有更新就什么也不做直接返回
if (!isUpdate) {
    return 0;
}
//如果有更新就在下面下载并且替换文件....
//最后根据自己的处理结果返回对应的数字
return 1;

用Pid关闭主程序

//更新前要终止之前的程序
//================TerminateProcess================//根据进程ID强杀  
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, m_pVersionInfo->parentPid);
if (hProcess)
{
    TerminateProcess(hProcess, 4);
    CloseHandle(hProcess);
}

主程序检查更新代码

检查更新的代码要放到一个线程里运行,只不然会卡界面 

传参数运行更新程序,并取返回的退出码相应的处理

static DWORD WINAPI CheckUpdate(LPVOID lpParameter);
DWORD MainWnd::CheckUpdate(LPVOID lpParameter)
{
    //检查更新程序
    DWORD pid = GetCurrentProcessId();
    TCHAR cmd[MAX_PATH] = { 0 };
    _stprintf(cmd, L"%sUpdate.exe %s %s %d",L".\\", L"0.0.1", _T("http://www.xxx.com/CheckUpdate.php"), pid);
    STARTUPINFO StartInfo;
    PROCESS_INFORMATION procStruct;
    memset(&StartInfo, 0, sizeof(STARTUPINFO));
    StartInfo.cb = sizeof(STARTUPINFO);
    BOOL working = ::CreateProcess(NULL, cmd, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &StartInfo, &procStruct);
    if (working == 0)
    {
        DWORD error = GetLastError();
        MessageBox(NULL, L"启动检查更新出错", L"提示", 0);
        return 0;
    }
    WaitForSingleObject(procStruct.hProcess, INFINITE);
    unsigned long Result;
    GetExitCodeProcess(procStruct.hProcess, &Result);
    if (Result == 1) {
        //新版本已经更新覆盖需要重启,这个进程直接退出
        return 0;
    }
    return 0;
}



微信号:kelicom QQ群:215861553 紧急求助须知
留下一点心意, :)
点击更换验证码
留言