编译支持Log功能的Unicode NSIS
对于开发多语言版本的安装包来说,使用Unicode NSIS就成了一个比较自然的选择。然而Unicode Nsis属于官方NSIS的衍生版,开发进度势必落后于官方的NSIS,主要由Jim一个人进行维护。现在官方最新版本是2.45,而Unicode版还停留在2.42。
用过NSIS的都知道,NSIS的默认方式是手写卸载脚本的。如果使用了支持LOG功能的特殊版NSIS(也是官方的)。使用这个版本的NSIS生成的安装包可以在安装过程中生成日志文件,再加上网友提供的现成脚本,就可以比较方便地实现用日志文件进行卸载的功能。实现方式可以参考NSIS的基本结构一文。
然而,Unicode NSIS的作者没有提供支持LOG功能的NSIS的下载。而只提供了源代码下载。这样我们就不得不自己重新编译NSIS。编译过程很简单,加上一个NSIS_CONFIG_LOG编译参数就可以。但是如果问题这么简单就没有必要写个文章专门介绍一下了。
问题就在于,用自己编译出来的NSIS生成的安装包生成的LOG文件有错误!什么错误呢?在网上没有搜到任何线索,只能自己分析一下LOG文件,自己找错误了。(昨天给Jim发了邮件,不过没有时间等他的回复了)
查看了Unicode NSIS的相关源代码和《Windows核心编程》,发现最有可能出问题的就是WriteFile函数的问题。相关源代码如下。
if (g_log_file[0] && fp == INVALID_HANDLE_VALUE)
{
fp = myOpenFile(g_log_file, GENERIC_WRITE, OPEN_ALWAYS);
if (fp != INVALID_HANDLE_VALUE)
SetFilePointer(fp, 0, NULL, FILE_END);
}
if (fp != INVALID_HANDLE_VALUE)
{
DWORD d;
mystrcat(log_text,_T("\r\n"));
WriteFile(fp,log_text,mystrlen(log_text),&d,NULL);
}
Unicode NSIS生成的所有字符串都是Unicode编码的,写到这个文件里的字符串也是。那么上面的代码有两个问题。
1. 作者在创建这个文件的时候,没有声明这个文件是Unicode文件,使得记事本在打开文件时,以ANSI编码打开,从而产生错误。这个声明就是Byte Order Mark,只是针对Windows平台的部分Unicode文件有这个约束。(比如VS就可以选择是否在源代码文件中加入BOM。)
2. WriteFile时,第三个参数不正确。第三个参数的单位是字节,但是mystrlen函数返回的是log_text的字符数。而Unicode的一个字符有sizeof(TCHAR)个字节。所以实际写入文件中的字符串,只是log_text的前sizeof(TCHAR)分之一。
好了,问题找到了,着手修改源代码吧。先加上BOM,再把第三个参数乘以sizeof(TCHAR)。编译很顺利地完成。生成的LOG文件也是完整的了并能用记事本打开了。
但是,卸载功能无效。把生成的Unicode编码的Log文件另存为ANSI编码的。能卸载了~~~~看来这个Unicode NSIS的卸载包不是很完美啊。想必要让Uninstaller支持Unicode又要改不少代码。一想还有另一种可能是Uninstaller不认识BOM,毕竟NSIS是跨平台的,但是如果记事本不可读也是不能接受的。后来试了一下,果然不能有BOM。把BOM去掉,Uninstaller就正常工作了。
最终代码修改如下:
if (fp != INVALID_HANDLE_VALUE)
{
DWORD d;
mystrcat(log_text, _T("\r\n"));
#ifdef UNICODE
WriteFile(fp, log_text, mystrlen(log_text) * sizeof(TCHAR), &d, NULL);
#else
WriteFile(fp, log_text, mystrlen(log_text), &d, NULL);
#endif
}
然后用下面的指令Build一下,
scons UNICODE=yes NSIS_CONFIG_LOG=yes SKIPUTILS=”NSIS Menu” PREFIX=”Your Install Folder” install
支持Log的Unicode版NSIS就生成好了。
另外附上自己编译好的补丁,覆盖现有安装即可。
更新:
这时生成的Log文件是Unicode编码,而FileRead指令是不能正确读取Unicode文件的。这样就导致Uninstall By Log功能无法正常运行。
解决方法也很简单,把读取Unicode文件的FileRead指令替换成Unicode NSIS所特有的FileReadUTF16LE就可以了。