Bootstrap

InnoSetup制作补丁包替换文件操作

在程序即将给用户的时候,通常都会制作一个exe的安装包,因为我们面对的客户不知道计算机操作能力如何,最好的方式就是“下一步”、“下一步”....“安装完成”!

在这里讲述一下如何用InnoSetup制作一个简单的补丁包。

当前补丁包的功能是:替换安装程序的资源文件,也就是文件夹的替换。

这个操作要是让用户操作起来,可真是难喽,用户只是一顿点击操作,完全不知道运行程序中有什么,所以,在制作补丁包的时候也要做成简单易操作。

接下来讲解下是如何实现的吧!

开发环境:win32控制台应用程序

脚本工具:InnoSetup

那么,该如何实现这个补丁包操作呢

使用过InnoSetup脚本的都知道,可以使用下一步,下一步的方式一建生成简单的打包程序,这里就不讲解是如何配置基本属性了。

想要用脚本实现文件替换的功能,说实话,对于我这个不常用脚本的人来说,还是有一点困难的。索性就使用控制台程序,在Innosetup中运行win32控制台程序,使用C++指令完成替换操作。

[Run]
Filename: "{app}\server_packWin32.exe";Description: "运行应用程序"

以上是InnoSetup中的核心调用部分,调用名字叫做:server_packWin32的程序就可以实现文件替换啦!

win32程序实现文件替换功能

涉及到的主要功能

1:注册表读取功能

2:读取文件夹内容并覆盖

操作流程

第一步:获取注册表中安装的应用程序的路径

第二步:筛选出旧文件夹路径

第三步:文件替换

根据操作流程就可以实现简单的文件替换功能了。

获取注册表中应用程序路径

1:打开注册表

对于我们C++来说,获取注册表路径下key值信息应该是小菜一碟了吧!

使用RegOpenKeyEx打开指定的键值。该函数一共有五个参数。

参数一:指向以空结尾的字符串指针,一般就是填入我们在注册表中的键值

需要注意的是:如果参数设置成nullptr或者是一个指向空字符串的指针时,此时函数将打开一个新的句柄,在这种情况下,函数不会关闭之前打开的句柄的,这里需要大家进行注意哦~

参数二:传入注册表中需要查找的路径。

参数三:默认设置成0。

参数四:一般在读注册表的时候,设置成 KEY_READ 。这个参数是一个访问掩码,指定对密钥的期望访问权限。

参数五:指向变量的指针,该指针接收打开的键的句柄

注意:只有当键值打开成功之后,才可以做后续操作

HKEY hOpen;
//访问注册表,hKEY则保存此函数所打开的键的句柄
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_READ, &hOpen))
{
   //注册表打开成功,做有效处理
}

2:读取注册表数据

在读取注册表数据时,一般采用`RegQueryValueEx`函数进行操作。根据成打开的句柄,查找对应key值下的value数据

 

也就是图上红线标注的位置数据。

参数一:是打开注册表键值的句柄,默认传入hOpen。

参数二:需要查询的key值

参数三:默认传入nullptr

参数四:默认传入nuppltr

参数五:返回的查询到的数据

参数六:目前没有实际应用,可以不做考虑

TCHAR tcPath[MAX_PATH] = {0};
DWORD chData ;
printf("Path information is being read...\n");
if(RegQueryValueEx(hOpen, key, nullptr, nullptr, (BYTE*)tcPath, &chData) == ERROR_SUCCESS)
{
    //获取到了有效的数据值,记录并返回
    std::string spath = Tchar2String(tcPath);
    RegCloseKey(hOpen);
    return spath;
}

注意:无论是否查询到有效内容,最后都必须要关闭指定的注册表项的句柄。

在这使用了一个转换函数,有TCHAR类型转成了string类型。在这里我也贴出来了,如果有需要的,可以拿去了。

std::string Tchar2String(TCHAR* tchar)
{
	int iLen = WideCharToMultiByte(CP_ACP, 0, tchar, -1, NULL, 0, NULL, NULL);
	char* chRtn = new char[iLen*sizeof(char)];
	WideCharToMultiByte(CP_ACP, 0, tchar, -1, chRtn, iLen, NULL, NULL);
	std::string str(chRtn);
	delete chRtn;
	return str;
}

筛选出旧文件夹路径

应用程序的整体路径已经拿出来了,对于那些资源文件路径的截取也就简单了。

在这里,就需要根据你需要替换的那些文件对exe应用程序来说是哪些层级就可以筛选到了。

文件替换

在文件替换的功能中,首先需要查询新文件夹中存在的所有子文件,并替换成旧文件。

这也是整个替换文件中的重点。

因为程序不知道传入的文件是属于子文件还是嵌套文件,所以,在查询过程中,采用了递归方式,逐层级查询。

查询到匹配文件后,复制替换

第一步:判断传入的需要替换的新文件路径是否存在?

if (_access(spath.c_str(), 0) == -1)
{
	//当前文件夹路径不存在
	return;
}

文件不存在时直接不进行后续处理。在使用_access函数时,记得要添加头文件#include <io.h>

第二步:获取路径下对应的文件句柄是否有效?

在这里需要注意:并不是文件夹存在就代表了文件句柄是有效的。

intptr_t hFile = 0;
std::string p, starget;
hFile = _findfirst(p.assign(spath).append("\\*").c_str(), &fileinfo);
if(!hFile)
{
    return;
}

第三步:循环遍历查找有效数据

使用do{}while();的方式根据文件句柄,查找数据是否成功,成功返回0,失败返回-1

以下格式判断:

do{
    //有效数据处理
}while(_findnext(hFile, &fileinfo) == 0);

第四步:文件判断

如果该文件有子文件夹存在,递归该路径,并查找该子文件夹下的子文件内容,直到没有子文件夹为止。

如果该文件下没有子文件夹直接进行复制替换

if ((fileinfo.attrib & _A_SUBDIR))
{
    if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
    {		
        CopyFolder(p.assign(spath).append("\\").append(fileinfo.name), starget.assign(sTargetpath).append("\\").append(fileinfo.name), enumFlag);
    }
}
else
{
    std::string  sinfo = p.assign(spath).append("\\").append(fileinfo.name);
    std::string sNewTargetPath = starget.assign(sTargetpath).append("\\").append(fileinfo.name);
    //文件复制
    std::string sLog = "path = ";
    sLog += fileinfo.name;
    BOOL bSuccess = ::CopyFileA(sinfo.c_str(), sNewTargetPath.c_str(), false); 
    if (bSuccess == TRUE)
    {
	sLog += "cpoy successful!\n";	
    }
    else
    {
	//复制失败时,判断目标路径是否存在?不存在说明没有安装,存在说明被占用
	LoadFilesFailedTips(sNewTargetPath, enumFlag);
	sLog += "cpoy failed!\n";
    }
    printf(sLog.c_str());
}

这是我们查询的核心部分,也是最最容易出错的地方。

大部分新手都不是很会用递归函数,但是只要你用了就会一直爱上它,真的是太太方便了。

在上述代码中还涉及到了一个重点,就是文件复制。采用了:CopyFileA函数。

这些函数都是可以在win32程序中直接运行的。

总结

根据上述介绍,就可以应用win32控制台程序实现文件替换的功能了,将程序编译成exe后,直接使用InnoSetup脚本程序运行。

用户使用起来非常方便,仅仅需要下一步等几项点击操作就可以实现更新资源的功能啦~

我是糯诺诺米团,一名C++开发程序媛~

;