最近做的一个项目,使用小工具代替人工提交到网站并获取结果。
在开发过程中碰到了一个问题。
网站返回的格式为tar.gz文件。第一个想到的方式是直接用第三方工具,使用的是7z。但用户那里使用的系统为win10,不以管理员身份运行会没有调用第三方工具的权限。遂决定用库解决该问题。
这里记录下windows下解压tar.gz的一些问题。
其实tar.gz是两次封装。
第一次是tar打包(这里不会压缩,文件反而会更大),第二次是gz文件的算法压缩。
解压GZ文件使用的是zlib库。
这里说一下zlib的使用过程。
一开始我是图懒直接从官网下载的windows develop版本,包含头文件,静态库和dll。但运行过程中出现了缺少zlib1.dll的问题。经过反复的折腾,没有解决。他这个库生成时间是2005年,可能在新版windows下运行会有问题把。
本人对系统了解还是不多,没有找到深层次的原因。如果有大神知道原理请告知。
在此之后下载了新版并进行编译。具体编译过程忘记了,挺顺利的。在vs命令行里执行一条命令后,会直接生成vs工程项目。选择静态库或动态库编译即可。
不知道这里能不能发csdn的连接,我是根据这篇文章来编译的:http://blog.csdn.net/shellching/article/details/8116622。
当然我用的版本是vs2015。用这种方法也能顺利通过。
接下来就是解压了。一开始我从网上找到的资料全是compress和uncompress,后来发现:
错啦!
压根就不是这么解压的。
具体原因没有详细了解到,大体上我的理解就是,其实gz文件需要一个头来标识压缩级别等信息。而compress和uncompress只是压缩字节流,单纯地调用uncompress而不知道数据的开始位置和数据的压缩信息的话,是无法正常压缩的。
下面是解压代码:
//初始化结构体
z_stream strm = { 0 };
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
//下面分别初始化源数据,源数据长度,目标数据长度和目标数据地址
strm.next_in = srcBuffer;
strm.avail_in = srcLength;
strm.avail_out = dstLength;
strm.next_out = dstBuffer;
//下面函数的第二个参数在头文件的函数说明里有,这里的这个参数意思是自动判断信息头
int res = inflateInit2(&strm, MAX_WBITS + 32);
//执行解压缩
res = inflate(&strm, Z_NO_FLUSH);
//回收资源
inflateEnd(&strm);
上面的代码我去掉了结果判断的部分,具体的结果判断大家参考头文件的信息把。我其实在代码里没有进行详细判断,只是简单判断了成功失败,并没有返回原因。这里就不贴上来坑大家了。
有几点注意事项:
1,目标内存长度需要足够。当然不够也没关系,该函数会在执行时发现并返回这个错误,在判断后逐步增加缓冲区即可。
2,转换成功后目标数据的长度为:dstLen - strm.avail_out。
3,如果你编译后使用这些函数,这个程序会莫名其妙地崩掉。
4,如果你是新手(现在没啥新手在VS上用这个了把。。。),可能会报无法连接的错误,当然你引入了lib还是会报。
先说下为何会报连接错误,需要加一个宏ZLIB_WINAPI,如果有新手在看的话,跟大家分享下。
一个库,无论是静态还是动态。在有头文件的时候,有两个地方需要注意,一是你编译库的时候,导出函数前面有没有加export,第二个是你引入库的时候,导出函数前面是不是import。
有些库因为要处理跨平台的问题,库的源码的宏定义会非常复杂,这时候要仔细观察,设置响应的宏确定库函数的正确引入和导出,如果你用VS的话,VS会根据宏定义的状态来高亮具体代码用到哪个宏。
在编译和引入的时候都要注意。
接下来,在崩溃之后,各种google原因,但始终没有发现是为什么,因为当时没有解压成功,我也一度怀疑自己的代码有问题,或者库有问题。
在这里,感谢一位大神:http://blog.csdn.net/u013283835/article/details/70311499
真是跪谢。这是怎么发现的。祝您能修到孙艺珍。
根据这位大神的说法,把预处理器中的ASMINF这个宏去掉。执行程序将转换出来的字节流写入文件。搞定了。
这里鄙视下CSDN:本人的csdn因为前几年给别人发QSS代码被封了。我也是醉了,发个QSS代码都能封。就这还搞技术论坛。呵呵。
接下来就进行tar文件的解析了。最开始想到的使用libarchive库。
嗯。编译,连接,导入,使用。在经历过种种挫折后,我突然发现:libarchive不支持全路径,只能打开当前工作目录下的文件,当然也可能是我某个配置没有配好,但libarchive的资料实在是太少了。
官方文档也只有很粗略的解释。而且在win10下,如果程序安装在C盘program files下,运行程序是没有创建文件权限的,在其他盘则需要不停设置当前路径。放弃。这个库真不是一般的难用。
查了下tar文件的格式:
http://blog.chinaunix.net/uid-20357359-id-1963469.html
https://www.gnu.org/software/tar/manual/html_node/Standard.html
嗯,貌似做简单的拆包不复杂吗。外加我做的小项目中tar文件本就是自动生成的,只有一级目录。自己解决把。
下面是代码:
typedef struct posix_header
{
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char typeflag;
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
}TAR_HEADER;
//参数为目标路径、tar缓冲区,缓冲区长度
void ExtractTar(const char *dstDir, BYTE *buffer, uLongf len) { uLongf index = 0; while (true) { if (index == len - 1) { return; } TAR_HEADER newHeader = { 0 }; memcpy(&newHeader, buffer + index, sizeof(TAR_HEADER)); index += sizeof(TAR_HEADER); if (strcmp(newHeader.name, "") == 0) { return; } char fileFullPath[MAX_PATH] = { 0 }; memcpy(fileFullPath, dstDir, strlen(dstDir)); memcpy(fileFullPath + strlen(dstDir), newHeader.name, strlen(newHeader.name)); FILE *newFile = NULL; fopen_s(&newFile, fileFullPath, "wb+");long size = strtol(newHeader.size, (char **)(&(newHeader.size) + 12), 8); size_t res = fwrite(buffer + index, 1, size, newFile); fclose(newFile); index += size;
//填充字节块 int nblock = index / 512 + 1; index = nblock * 512; } }
注意:
1,我还是去掉了返回判断的代码,如果想完成健壮代码请判断各种状态返回值。
2,这段代码不能解压包含文件夹或者多级目录的tar文件,也不能解压特殊文件,也没有判断字节偏移等数据。
简单来说,这段代码只能保证我这个小项目的需求。而且很大程度上满足不了其他需求。
经过上面两步,基本上满足了我的需求。在这里分享给大家。