基于Zlib实现的从ZIP文件中提取文件数据

前言:呃,今天上来翻了翻之前写的文章,其中访问率最高的是那个『自绘RadioButton』,有好多人留言让我发源代码,这,也是自己懒惰,现在已经把源代码的连接补到文章最后,也是CSDN的下载,希望对某些人有帮助吧,呵呵。看着之前自己写的东西还挺有意思的,特别是那个用JAVA做的地图编辑器的学习版,其实是到后面写不下去了,觉得越想越复杂。其实自己最近闲暇时分用MFC写了个『Descent: Journey To The Dark!』的地图编辑器,下次整理整理发上来好了。这几年因为工作一直很忙,说实话也没啥多的技术积累,毕竟公司里面混饭吃,还是要做一些杂活,虽然感觉也做得久了点。好了,言归正传,这次的东西是关于从ZIP压缩包中读取文件的方法。其实也是那个地图编辑器要用的一个小功能。使用了Zlib库中contrib中的minizip的代码,有兴趣的童鞋可以去下Zlib源码看下,我是没这个闲情雅致,只是实用派的。之前也在网上搜了好久,大多都是介绍如何使用Zlib库压缩和解压数据,唯独就是没几多人介绍如何从ZIP Archive中抽取文件的。不过也可能是我太急功近利了,只是想找抽取的代码,而不想一步一步看人家的介绍。正好昨天找了点资料,自己捣鼓了下,把这抽取的代码写了下,可能有些人也正急着找也不一定(当然,我觉得像我这种从头写地图编辑器的奇葩估计也不多了,网上大把的类库可以用)。不过也正好,把抽取的方法写成了个动态库,还做了lua的封装导出,这样C++和Lua就都能用了,写得比较简单,只是介绍个方法,其他功能还有待扩展,这里只是个例子可以参考下实现的方案,因为代码中好多都是测试用的,并且错误处理基本没有做,以后再慢慢完善吧。代码随便用,本来就是个测试版的东西,出事儿了别找我就行。


平台

程序是在windows平台下(XP和WIN7都试过)用VC6.0编译的。VS的要用到的人自己转吧,反正内部实现就这样了,也没用到MFC的东西。
程序用到了Zlib的库,版本是1.2.3的。还用到了lua,版本是5.1.4的,但是可以不用lua,我是顺便搞的,不要的自己想办法移除。

源代码略解

  主要讲下zextract工程好了,其实只是实现了3个函数,并且只有两个还是靠谱的,其中只有一个是实用的。分别是1.『列出压缩包内的文件』,2.『查找压缩包中文件』,3.『获取压缩包内的文件』。第一个明显是玩玩的,因为直接是printf输出到console中,也是参考了minizip中的do_list函数的实现。第二个函数写是写了,但是没啥大用,以玩玩为主。最后一个是有用的东西,实现了从压缩包中读取文件的功能,其中还用到了CMemBuffer类。先看下工程的头文件好了。
// The following ifdef block is the standard way of creating macros which make exporting 
// from a DLL simpler. All files within this DLL are compiled with the ZEXTRACT_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see 
// ZEXTRACT_API functions as being imported from a DLL, wheras this DLL sees symbols
// defined with this macro as being exported.
#ifdef ZEXTRACT_EXPORTS
#define ZEXTRACT_API __declspec(dllexport)
#else
#define ZEXTRACT_API __declspec(dllimport)
#endif

#include "MemBuffer.h"

#include "luna.hpp"

ZEXTRACT_API void ListZip(const char* fname);
ZEXTRACT_API bool FindFileInZip(const char* zfn, const char* fname);
ZEXTRACT_API int GetFileInZip(CMemBuffer& buffer, const char* zfn, const char* fname, const char* password);

extern "C" ZEXTRACT_API int luaopen_zextract(lua_State* L);
   讲下 GetFileInZip函数。一共有4个入参:
  1. CMemBuffer对象,用来存储从压缩包中抽取的文件。
  2. 对应的ZIP Archive文件路径,相对路径和绝对路径皆可。
  3. 对应在ZIP Archive中要提取的文件路径。
  4. 密码,如果没有密码则填NULL或者0即可。
  函数返回大于等于0则表示提取的文件的大小,如果返回小于0,则是对应的错误代码。如果执行成功,则文件内容存放在buffer对象中。
  其余两个函数没什么介绍的必要,自己看下源代码即可。

zextract.cpp::GetFileInZip

ZEXTRACT_API int GetFileInZip(CMemBuffer& buffer, const char* zfn, const char* fname, const char* password)
{
	unzFile uf = unzOpen(zfn);
	
	if (NULL == uf)
	{
		printf("unzOpen failed...\n");
		return -1;
	}
	
	int err = unzLocateFile(uf, fname, 0);
	if (UNZ_OK != err)
	{
		printf("GetFileInZip unzLocateFile failed... error:%d\n");
		return err;
	}
	
	unz_file_info file_info;
	char filename_inzip[256];
	
	err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);

	if (UNZ_OK != err)
	{
		printf("unzGetCurrentFileInfo failed... error:%d\n", err);
		return err;
	}

	err = unzOpenCurrentFilePassword(uf, password);

	if (UNZ_OK != err)
	{
		printf("unzOpenCurrentFilePassword failed... error:%d\n", err);
		return err;
	}

	char* pBuff = new char[file_info.uncompressed_size];

	if (pBuff == NULL)
	{
		unzCloseCurrentFile(uf);
		unzClose(uf);
		return -2;
	}

	err = unzReadCurrentFile(uf, pBuff, file_info.uncompressed_size);

	if (err < 0)
	{
		printf("unzReadCurrentFile failed... error:%d\n", err);
		delete [] pBuff;
		unzCloseCurrentFile(uf);
		unzClose(uf);
		return err;
	}
	// Append data to the MemBuffer
	buffer.Append(pBuff, file_info.uncompressed_size);

	unzCloseCurrentFile(uf);

	unzClose(uf);
	return err;
}
其中关键的操作函数就只有7个:
unzOpen
打开Archive文件
unzClose 关闭Archive文件
unzGetCurrentFileInfo 获取当前选择的内部压缩文件的信息
unzLocateFile 定位文件
unzOpenCurrentFilePassword 选择打开当前文件
unzReadCurrentFile 读取当前文件
unzCloseCurrentFile 关闭当前文件
  其中后三个是需要组合使用的,也就是说需要读取一个内部文件,首先要打开,然后才是读取,最后要关闭。而选择当前文件的方法有很多,这里使用 unzLocateFile函数实现的,如果找到对应文件会把这个文件默认选中为当前文件。 ListZip函数的实现中还有遍历的代码可以参考。

CMemBuffer

  CMemBuffer类其实只是实现了一个存放内存数据的功能。考虑使用一个类专门来存储文件数据是有多方面的考虑,比如文件是二进制的——图片,还有就是获取数据是从堆上申请的内存,要考虑自己释放的问题。自己手动去管理明显会很麻烦,不如专门搞个缓存类来管理这些内存数据块。
// MemBuffer.h: interface for the CMemBuffer class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_MEMBUFFER_H__36D0136D_B57A_49FF_855B_E5545078B130__INCLUDED_)
#define AFX_MEMBUFFER_H__36D0136D_B57A_49FF_855B_E5545078B130__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CMemBuffer  
{
public:
	CMemBuffer(int nInitLen = 16);
	CMemBuffer(const char* pData, int nLen = 0);
	CMemBuffer(const CMemBuffer& buff);
	virtual ~CMemBuffer();

public:
	virtual int GetBufferLen();
	virtual bool Append(const char* pData, int nLen = 0);
	virtual char* GetBuffer();
	
protected:
	bool ReAllocBuffer(int nDeltaLen);

protected:
	char* m_pBuffer;	// buffer data
	int m_nCurLen;
	int m_nMaxLen;
};

#endif // !defined(AFX_MEMBUFFER_H__36D0136D_B57A_49FF_855B_E5545078B130__INCLUDED_)
  这个实现没什么好讲的,拿到数据只要Append即可。要获取数据直接使用 GetBuffer()即可。

Lua导出部分

//////////////////////////////////////////////////////////////////////////
// @Param
//   zipName   which archive
//   fileName  which file to extract from the archive named zipName's value
//   password  password to extract file from zip
// @Return
//   binary data
//   error number

static int luacf_getFileInZip(lua_State* L)
{
	CMemBuffer buff;
	const char* pZipName = luaL_checkstring(L, 1);
	const char* pFileName = luaL_checkstring(L, 2);
	const char* pPassword = lua_tostring(L, 3);
	int ret = GetFileInZip(buff, pZipName, pFileName, pPassword);

	if (ret >= 0)
	{
		lua_pushlstring(L, buff.GetBuffer(), buff.GetBufferLen());
		return 1;
	}

	lua_pushnil(L);
	lua_pushnumber(L, ret);
	return 2;
}

const struct luaL_reg libs[] =
{
	{"getFileInZip", luacf_getFileInZip},
	{NULL, NULL}
};

ZEXTRACT_API int luaopen_zextract(lua_State* L)
{
	luaL_register(L, "zextract", libs);
	return 1;
}
  这部分就没啥好说了,总之就是这么简单。

Lua测试代码

-- 是否是debug版本
local bDGB = false;

local zlib;
if bDGB then
	zlib = package.loadlib("zextract_d.dll", "luaopen_zextract");
	if type(zlib) == "function" then
		zlib = zlib();
	end
else
	zlib = require "zextract";
end

if type(zlib) ~= "table" then
	print("loadlib error");
	return
end

print(zlib.getFileInZip("readme.zip", "folder/readme.txt", "123456"))
  
  这里要讲下对应的载入,前面有个debug版本的判断,这个是根据当前编译的版本决定的,当然也可以debug和release版本都编译,名字是不同的,到时候自己手动修改下这个bDGB的标志即可。二进制的文件就不要用这个代码了,因为最后的数据是print出来的,文本的能看下。载入动态库的部分可以看下之前的文章 《Lua脚本调用C++动态库》

源代码下载。

目录结构

一共5个文件夹。
zextract文件夹内为动态库的工程目录。
zlibTest目录为C++调用zextract生成动态库的工程文件。
lib目录为对应的库文件的目录,其中有Zlib的库文件以及zextract动态库生成的库文件。
comm文件为一些公共文件存放区,比如minizip的实现源码,还有内存buffer的实现源码。
bin目录为程序的输出目录,内涵一个lua解析器,lua5.1.dll,zlib1.dll,还有个压缩包的例子readme.zip。还有一个lua文件,测试导出库用的。

你可能感兴趣的:(C++个人源程序,Lua)