7z源码的编译与使用_markdown 格式

本文详细介绍了7z源码的结构、编译过程、内部与外部接口,以及解码器的注册和接口调用方法。在编译过程中,遇到的错误及解决办法也一并分享。内容涵盖从源码结构到实际解压操作的完整流程。

7z作为开源的解压缩项目,支持多种格式的解压缩,由 Igor Pavlov 开发,最新的版本为 19.00 版。

源码下载位置:https://www.7-zip.org/a/7z1900-src.7z

1、源码结构

源码解压之后,是这样的结构:

路径备注
Asm包含主要算法的汇编实现,直接使用汇编可以提高执行效率。但是却对跨平台移植造成了一些困难
C主要是算法的代码,由C语言实现
CPP相关COM接口的实现,界面,工程文件等
DOC相关文档

对于我们编译项目来说,最主要的就是 CPP 文件夹,编译的项目文件在 CPP\7zip\Bundles\ 中可以找到。

路径备注
Alone独立的可执行程序,支持的解压格式仅包括7z, cab, tar, zip这几种。
Alone7z独立的可执行程序,仅支持7z格式。FM文件管理器(File Manager),通过加载7z.dll的导出函数进行解压。
Format7z7za.dll 7z Standalone Plugin,7z 独立插件(仅7z格式)
Format7zF7z.dll 7z Plugin, 7z插件,包含各种格式。
SFXCon自解压(控制台程序)。
SFXSetup自解压程序(安装包)。
SFXWin自解压程序(Windows界面)。

2、代码的编译

打开CPP\7zip\Bundles\Format7zF\Format7z.dsw,即可打开7z.dll工程。

我使用的编译器是vs2008,打开dsw文件提示升级,转换后可生成sln文件和对应的vcproj文件。

直接编译。第一次编译,报错:1>LINK : 无法创建 .ILK 文件的映射;正在非增量链接

1>LINK : fatal error LNK1104: 无法打开文件“C:\Program Files\7-Zip\7z.dll”

解决方法:文件占用,编译器权限不够,不能对此文件进行修改,修改生成目标地址,生成到其他地方就可以了。

第二次编译,报错:

1>正在链接...
1>.\Debug\7zCrcOpt.obj : fatal error LNK1107: 文件无效或损坏: 无法在 0x276 处读取

解决方法:删除工程中的asm文件,改由对应的c文件实现,记得将这些c文件的预编译头选项改为“不使用预编译头”。

第三次编译,成功

3、内部接口

7z.dll 中的每一种支持的格式被称作 Archive,代码位于CPP\7zip\Archive中。

每一种Archive包含一个HandlerHandler里包含处理每一种Archive的接口。

每一种Archive包含一个Register,用于向全局对象注册,只有注册后的Handler才会被调用。

Handler必须继承IInArchive接口,表示可读,用于解压。(必选)

Handler可以继承IOutArchive接口,表示可写,用于压缩文档。(可选)

根据官方说明:
Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM

仅这几种Archive是可以被压缩的,因此这些ArchiveHandler要继承IOutArchive接口。

如果代码只用于解压,而不用于压缩,可定义EXTRACT_ONLY宏,可不生成IOutArchive,可以减少文件体积。

文件接口说明
ICoder.hICompressProgressInfo设置进度,用于向外部展示进度条
ICompressCoder解码
ICompressCoder2同上,传出多个Stream对象
ICompressSetCoderPropertiesOpt设置属性
ICompressSetCoderProperties设置属性
ICompressSetDecoderProperties2设置属性
ICompressWriteCoderProperties将属性写入到Stream
ICompressGetInStreamProcessedSize获取已经处理的大小
ICompressSetCoderMt设置进程数
ICompressSetFinishMode设置结束标志
ICompressGetInStreamProcessedSize2获取已经处理的大小
ICompressSetMemLimit设置内存限制
ICompressGetSubStreamSize获取内部流文件大小
ICompressSetInStream设置压缩传入的InStream流对象
ICompressSetInStreamSize设置压缩传入的InStream流对象大小
ICompressSetOutStreamSize设置压缩传出的InStream流对象
ICompressSetBufSize设置缓冲区大小
ICompressInitEncoder初始化编码器
ICompressSetInStream2设置输入流
ICompressSetOutStream2设置输出流
ICompressSetInStreamSize2设置输入流大小
ICompressFilter设置过滤器,只处理小于等于size的文档
ICompressCodecsInfo获取压缩解码器信息
ISetCompressCodecsInfo设置压缩编码器信息
ICryptoProperties加密属性
ICryptoResetInitVector加密,重置InitVector
ICryptoSetPassword设置密码,用户处理加密文档。
ICryptoSetCRC设置CRC,用于处理加密文档的。
IHasher计算哈希接口
IHashers哈希管理器
IStream.hISequentialInStream顺序可读文件流
ISequentialOutStream顺序可写文件流
IInStream随机可读文件流(在ISequentialInStream基础上增加Seek函数)
IOutStream随机可写文件流(在ISequentialOutStream基础上增加Seek/SetSize函数)
IStreamGetSize获取文件流大小
IOutStreamFinish为可写文件流设置结束状态
IStreamGetProps获取文件流的属性
IStreamGetProps2获取文件流的属性
IArchive.hIInArchive可读文档(用于输入)
IArchiveGetRawProps文档属性
IArchiveGetRootProps根文档属性
IArchiveOpenSeq将顺序流打开为文档
IArchiveUpdateCallback设置文档更新回调函数
IArchiveUpdateCallback2设置文档更新回调函数
IArchiveUpdateCallbackFile设置文档更新回调函数(到输出流)
IOutArchive可写文档(用于输出)
ISetProperties设置属性
IArchiveKeepModeForNextOpen下次打开时保持相同模式
IArchiveAllowTail允许尾部数据

4、外部接口

调用通过IDA打开7z.dll可发现其导出函数。

函数说明
CreateDecoder创建解码器
CreateEncoder创建编码器
CreateObject创建对象
GetHandlerProperty2获取Handler属性
GetHandlerProperty获取Handler属性
GetHashers获取IHasher对象
GetIsArc获取IsArc函数地址
GetMethodProperty获取解码器属性。传入codecIndexPROPID,传出PROPVARIANT*
GetNumberOfFormats获取文件格式的数量。(指:7z,zip,rar等文件格式)
GetNumberOfMethods获取解码器的数量。(指:BCJ2,LZMA,Deflate等格式编码)
SetCaseSensitive设置当前文件系统是否大小写敏感,WINDOWS默认不敏感,其他系统默认敏感。
SetCodecs传入ICompressCodecsInfo对象,设置外部解码器。
SetLargePageMode设置大内存页模式,这种模式可申请更多的内存。

5、解码器

解码器通过【注册】的方式,注册到全局变量g_Arcs中。

@ CPP\7zip\UI\Common\LoadCodecs.cpp

static const unsigned kNumArcsMax = 64;
static unsigned g_NumArcs = 0;
static const CArcInfo *g_Arcs[kNumArcsMax];

根据定义g_Arcs最多可以容纳64种不同的解码器。

CArcInfo的定义如下:

struct CArcInfo
{
   UInt16 Flags;
   Byte Id;
   Byte SignatureSize;
   UInt16 SignatureOffset;

   const Byte *Signature;
   const char *Name;
   const char *Ext;
   const char *AddExt;
   Func_CreateInArchive CreateInArchive;
   Func_CreateOutArchive CreateOutArchive;
   Func_IsArc IsArc;

   bool IsMultiSignature() const
   	{ return (Flags & NArcInfoFlags::kMultiSignature) != 0; }
};

CArcInfo各主要成员的含义:

成员说明
CArcInfo::Flags定义在@CPP\7zip\Archive\IArchive.h中,NArcInfoFlags有详细说明。
CArcInfo::IdArchive的ID标识符,例如:7z=7, Rar=3。
CArcInfo::SignatureSize解码器标识的长度。
CArcInfo::Signature解码器标识符,例如:zip={0x50, 0x4B, 0x03, 0x04},7z={'7' + 1, 'z', 0xBC, 0xAF, 0x27, 0x1C}。等。
CArcInfo::Name解码器名称。
CArcInfo::Ext解码器扩展名。
CArcInfo::CreateInArchive函数指针,创建解码器InArchive对象,用于打开文件用于解压。
CArcInfo::CreateOutArchive函数指针,创建解码器OutArchive对象,用于创建文件用于压缩。
CArcInfo::IsArc函数指针,判断文件格式是否合法。
CArcInfo::IsMultiSignature判断是否有多个Signature

各解码器通过RegisterArc.h中封装的宏进行注册。
如:ZIP 的注册代码位于CPP\7zip\Archive\Zip\ZipRegister.cpp中,我将代码贴出来。

#include "StdAfx.h"

#include "../../Common/RegisterArc.h"

#include "ZipHandler.h"

namespace NArchive {
namespace NZip {

static const Byte k_Signature[] = {
    4, 0x50, 0x4B, 0x03, 0x04,               // Local
    4, 0x50, 0x4B, 0x05, 0x06,               // Ecd
    4, 0x50, 0x4B, 0x06, 0x06,               // Ecd64
    6, 0x50, 0x4B, 0x07, 0x08, 0x50, 0x4B,   // Span / Descriptor
    6, 0x50, 0x4B, 0x30, 0x30, 0x50, 0x4B }; // NoSpan

REGISTER_ARC_IO(
  "zip", "zip z01 zipx jar xpi odt ods docx xlsx epub ipa apk appx", 0, 1,
  k_Signature,
  0,
  NArcInfoFlags::kFindSignature |
  NArcInfoFlags::kMultiSignature |
  NArcInfoFlags::kUseGlobalOffset,
  IsArc_Zip)

}}

6、相关宏开关

作用
_7ZIP_STSingle-Thread单线程,默认未定义,开启后将不编译多线程相关逻辑
_7ZIP_LARGE_PAGES开启大内存页,默认未开启
_SZ_ALLOC_DEBUG默认不开启,开启后可输出内存申请与释放的Log
USE_MIXER_MTMultiple_Thread 多线程解码器,不可与USE_MIXER_ST同时开启。
USE_MIXER_STSingle_Thread 单线程解码器,不可与USE_MIXER_MT同时开启。
EXTRACT_ONLY开启后只包含解压逻辑,不包含压缩逻辑。
NSIS_SCRIPT是否将NSIS脚本解压,默认关闭,需要手动修改CPP\7zip\Archive\Nsis\Nsis.h开启
EXTERNAL_CODECS是否使用外部解码器,全局变量g_ExternalCodecs负责加载外部解码器。CPP\7zip\UI\Agent\Agent.cpp@LoadGlobalCodecs()中包含g_ExternalCodecs相关初始化逻辑。
NEW_FOLDER_INTERFACE使用新文件夹操作接口:IFolderOperationsIFolderSetFlatMode
NO_READ_FROM_CODER禁止从解码器读取数据,默认未定义
USE_WIN_FILE默认开启,开启后使用Windows API处理文件(CreateFile/CloseHandle等),否则使用C函数处理文件(如open,close等)。

7、接口的调用

1. 模块加载

使用LoadLibrary/GetProcAddress(Windows)或dlopen/dlsym(Linux)获取函数地址。

2. 获取文件格式数量

DWORD dwFormat;
HRESULT hr = GetNumberOfFormats(&dwFormat);

3. 获取每种格式的GUID

for(DWORD i=0;i<dwFormat;++i)
{
    PROPVARIANT propvar;
    propvar.vt = VT_EMPTY;
    HRESULT hr = GetHandlerProperty2(i, NArchive::NHandlerPropID::kClassID, &propvar)
    GUID clsid = *(const GUID *)propvar.bstrVal;
    SysFreeString(propvar.bstrVal);
    // todo
    ......
}

GetHandlerProperty2内部,是通过SetPropGUID()函数将classid的值传递给PROPVARIANT的。
内部调用了SysAllocStringByteLen(),为避免内存泄漏,获取成功后应当调用SysFreeString()释放

static inline HRESULT SetPropStrFromBin(const char *s, unsigned size, PROPVARIANT *value)
{
  if ((value->bstrVal = ::SysAllocStringByteLen(s, size)) != 0)
    value->vt = VT_BSTR;
  return S_OK;
}

static inline HRESULT SetPropGUID(const GUID &guid, PROPVARIANT *value)
{
  return SetPropStrFromBin((const char *)&guid, sizeof(guid), value);
}

注:文档CPP\7zip\Archive\Guid.txt中有一段描述,是关于格式classid的。
返回的classid都应当符合这种格式{23170F69-40C1-278A-1000-000110xx0000}
中间两位xx在下表中可以找到对应关系,如果只是希望打开指定格式的文档,直接指定classid即可,不需要通过GetHandlerProperty2来获取。

Handler GUIDs:

{23170F69-40C1-278A-1000-000110xx0000}

  01 Zip
  02 BZip2
  03 Rar
  04 Arj
  05 Z
  06 Lzh
  07 7z
  08 Cab
  09 Nsis
  0A lzma
  0B lzma86
  0C xz
  0D ppmd
  ......

4. 创建IInArchive对象

CMyComPtr<IInArchive> parc;
HRESULt hr = CreateObject(&clsid, &IID_IInArchive, (void**)&parc));

注:我在Linux版本调试时,遇到了崩溃的问题,调试之后发现,7z中的IUnknown接口在Linux中使用了虚析构函数。
这与我代码中已有的IUnknown定义不一致,创建的对象会有虚表地址,因此调用的函数地址错位导致崩溃。

DEFINE_GUID(IID_IUnknown,
0x00000000, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
struct IUnknown
{
  STDMETHOD(QueryInterface) (REFIID iid, void **outObject) PURE;
  STDMETHOD_(ULONG, AddRef)() PURE;
  STDMETHOD_(ULONG, Release)() PURE;
  #ifndef _WIN32
  virtual ~IUnknown() {}
  #endif
};

5. 获取包中文件数量

IInArchive->Open返回成功之后。可通过调用IInArchive->GetNumberOfItems()获取文档中包含的文件数量。

UInt32 dwItems;
HRESULT hr = parc->GetNumberOfItems(&dwItems);

6. 解压

IInArchive->Extract负责文档的解压。

DWORD dw; // dw 为要解压的文档ID,从0开始
C7zArchiveOpenCB opencb(); // 自定义一个CallBack类,继承自IArchiveExtractCallBack接口
CMyComPtr<IArchiveExtractCallback> pcb = &opencb();
HRESULT hr = parc->Extract(&dw, 1, 0/*TestMode*/, pcb));
CMyComPtr<ISequentialOutStream> pSeqOutStm;
hr = pcb->GetStream(0, &pSeqOutStm, 0); // pSeqOutStm 为解压文件流,只支持顺序写入
// 需要调用者对pSeqOutStm进行包装,使其支持Read/Seek/Tell等操作

7. 关闭IInArchive

解压完毕后,调用IInArchive->Close关闭IInArchive对象,以便回收内存防止泄漏。

parc->Close();

------先写这么多,后续更------

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值