在程序即将给用户的时候,通常都会制作一个exe的安装包,因为我们面对的客户不知道计算机操作能力如何,最好的方式就是“下一步”、“下一步”....“安装完成”!
在这里讲述一下如何用InnoSetup制作一个简单的补丁包。
当前补丁包的功能是:替换安装程序的资源文件,也就是文件夹的替换。
这个操作要是让用户操作起来,可真是难喽,用户只是一顿点击操作,完全不知道运行程序中有什么,所以,在制作补丁包的时候也要做成简单易操作。
接下来讲解下是如何实现的吧!
开发环境:win32控制台应用程序
脚本工具:InnoSetup
那么,该如何实现这个补丁包操作呢?
使用过InnoSetup脚本的都知道,可以使用下一步,下一步的方式一建生成简单的打包程序,这里就不讲解是如何配置基本属性了。
想要用脚本实现文件替换的功能,说实话,对于我这个不常用脚本的人来说,还是有一点困难的。索性就使用控制台程序,在Innosetup中运行win32控制台程序,使用C++指令完成替换操作。
[Run]
Filename: "{app}\server_packWin32.exe";Description: "运行应用程序"
以上是InnoSetup中的核心调用部分,调用名字叫做:server_packWin32的程序就可以实现文件替换啦!
涉及到的主要功能
1:注册表读取功能
2:读取文件夹内容并覆盖
操作流程
第一步:获取注册表中安装的应用程序的路径
第二步:筛选出旧文件夹路径
第三步:文件替换
根据操作流程就可以实现简单的文件替换功能了。
对于我们C++来说,获取注册表路径下key值信息应该是小菜一碟了吧!
使用RegOpenKeyEx打开指定的键值。该函数一共有五个参数。
参数一:指向以空结尾的字符串指针,一般就是填入我们在注册表中的键值
需要注意的是:如果参数设置成nullptr或者是一个指向空字符串的指针时,此时函数将打开一个新的句柄,在这种情况下,函数不会关闭之前打开的句柄的,这里需要大家进行注意哦~
参数二:传入注册表中需要查找的路径。
参数三:默认设置成0。
参数四:一般在读注册表的时候,设置成 KEY_READ 。这个参数是一个访问掩码,指定对密钥的期望访问权限。
参数五:指向变量的指针,该指针接收打开的键的句柄
注意:只有当键值打开成功之后,才可以做后续操作
HKEY hOpen;
//访问注册表,hKEY则保存此函数所打开的键的句柄
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_READ, &hOpen))
{
//注册表打开成功,做有效处理
}
在读取注册表数据时,一般采用`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
第二步:获取路径下对应的文件句柄是否有效?
在这里需要注意:并不是文件夹存在就代表了文件句柄是有效的。
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++开发程序媛~