为 7-Zip 写一个存档格式插件 (3):理解 SplitHandler

做为入门,个人选择是从 SplitHandler 下手,由于这里不牵扯任何压缩和哈希算法逻辑,也没有任何文件结构,仅仅是把 .00一、.00二、... 这样的序列文件合并为一个文件,从实现逻辑上来说是最简单的。git

整个文件代码太长,这里不贴出来了,能够本身到 https://github.com/wzv5/7z-formatzzz/blob/master/CPP/7zip/Archive/SplitHandler.cpp 查看。github

总体结构

namespace NArchive {
namespace NSplit {

static const Byte kProps[] = { ... }
static const Byte kArcProps[] = { ... }

class CHandler { ... };

REGISTER_ARC_I_NO_SIG(...)
}}

全部代码位于 NArchive::NSplit 命名空间中,分为如下 4 部份内容。算法

首先最重要的是 CHandler 类,这个类是存档包处理的核心。数组

跳过中间的类实现代码,看文件最后,这是第 2 部分,使用 REGISTER_ARC_I_NO_SIG 宏来注册该存档包格式。函数

回过头来看文件开头,第 3 部分是全局的 static const Byte kProps[] 数组,第 4 部分是全局的 static const Byte kArcProps[] 数组,这些数组的含义以后再说。spa

CHandler 类定义

class CHandler:
  public IInArchive,
  public IInArchiveGetStream,
  public CMyUnknownImp
{
public:
  MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream)
  INTERFACE_IInArchive(;)
  STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
};

须要继承的父类和接口

按照习惯,类名必须为 CHandler,必须 public 继承自 CMyUnknownImpcode

若是支持读取此存档格式,则 public 继承 IInArchiveorm

若是此存档格式的文件项目可以提供顺序流读取,则 public 继承 IInArchiveGetStream对象

实现 IUnknown 接口

因为 CMyUnknownImp 类仅仅定义了一个引用计数成员,并无实现任何方法,因此须要本身在类内显式实现,这有现成的宏来实现。继承

这里的 CHandler 类实现了 2 个接口,IInArchiveIInArchiveGetStream,就调用 MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream)

官方已定义 MY_UNKNOWN_IMP1 ~ MY_UNKNOWN_IMP7 宏,实现了几个接口就调用相应的宏,并把全部接口传入宏。

若是实现了 IInArchive 接口,调用 INTERFACE_IInArchive(;),宏括号内必须写分号。这个宏括号中参数的含义实际上是用以区分当前究竟是为了定义接口,仍是子类为了实现接口。在 IArchive.h 文件中能够看到,若是是要定义接口,那么宏括号内传入的是 PURE,即 = 0,纯虚函数。

对于其余接口方法,都经过 STDMETHOD 宏定义出来,能够直接从 IArchive.h 文件中复制定义。

类成员变量和额外的方法

对于类成员变量,没有任何要求,这里定义的几个成员变量都是 Split 处理中须要的,而不是接口规范要求的。

能够看到,这里额外定义了一个 Open2() 方法,这一样不是接口规范要求的,能够按本身须要来写。

快速实现获取属性的相关方法

IMP_IInArchive_Props
IMP_IInArchive_ArcProps

类定义下方,紧接着就是这 2 个宏,它们的含义在上一篇文中有说到,IMP_IInArchive_Props 是用来实现获取包内项目属性的相关方法,IMP_IInArchive_ArcProps 用来实现获取存档包总体属性的相关方法。

有了这 2 个宏,咱们就不须要手动实现 IInArchive 接口中的 GetNumberOfPropertiesGetPropertyInfoGetNumberOfArchivePropertiesGetArchivePropertyInfo 这些方法了。

能够看到,与属性相关的方法仅剩下 GetPropertyGetArchiveProperty,前者用来返回文件项目的属性,后者用来返回存档包总体的属性。

注意,重点来了,这 2 个宏有什么魔力,可以自动实现这些方法呢?答案是开头所说的 kPropskArcProps 这 2 个全局数组。数组中定义了该存档格式支持的全部属性,数组成员可用值在 PropID.h 文件中。数组的名字必须为这样,这与宏定义相匹配。

注册格式

REGISTER_ARC_I_NO_SIG(
  "Split", "001", 0, 0xEA,
  0,
  0,
  NULL)

经过 RegisterArc.h 文件中定义的宏来注册文件格式。

// 只建立存档信息结构
REGISTER_ARC_V(n, e, ae, id, sigSize, sig, offs, flags, crIn, crOut, isArc)

// 建立存档信息结构,并注册
REGISTER_ARC_R(n, e, ae, id, sigSize, sig, offs, flags, crIn, crOut, isArc)

// 只读,指定实现了读取接口的类名
REGISTER_ARC_I_CLS(cls, n, e, ae, id, sig, offs, flags, isArc)

// 只读,指定实现了读取接口的类名,没有签名
REGISTER_ARC_I_CLS_NO_SIG(cls, n, e, ae, id, offs, flags, isArc)

// 只读,读取接口为全局的 CHandler 类
REGISTER_ARC_I(n, e, ae, id, sig, offs, flags, isArc)

// 只读,读取接口为全局的 CHandler 类,没有签名
REGISTER_ARC_I_NO_SIG(n, e, ae, id, offs, flags, isArc)

// 读写,读取和写入接口均为全局的 CHandler 类
REGISTER_ARC_IO(n, e, ae, id, sig, offs, flags, isArc)

// 读写,读取和写入接口均为全局的 CHandler 类,且对签名首字符减1,只有 7z 格式使用
REGISTER_ARC_IO_DECREMENT_SIG(n, e, ae, id, sig, offs, flags, isArc)

以上宏中参数的含义:

  • nchar*,格式名字
  • echar*,后缀名,不带点

    • 若是该格式有多种后缀名,以空格分割
    • 若是该格式为多卷存储,只写第一卷的后缀名
  • aechar*,以空格分割的额外的后缀名

    • 该参数的成员数必须和 e 的成员数一致,分别用以描述每一个 e 的额外后缀名
    • .tgz 这样的后缀名,它实际为 .tar.gz 的简写形式,经过 e 判断该文件为 gzip 格式,但其内部又有 tar,故 ae 应该写为 .tar
    • 这里的后缀名须要带点,如 .tar
    • 若是某个 e 没有额外后缀名,则要填入 *
    • 若是全部 e 都没有额外后缀名,能够传入 NULL
    • 示例:* * .tar .tar
  • idByte,格式独立 ID
  • sigSizeByte,签名的长度
  • sigByte[],签名,即文件头,用来识别文件格式
  • offsUInt16,签名偏移,若是签名不在文件头部须要手动指定
  • flagsUInt16,该格式的一些参数,可用值在 IArchive.h 文件的 NArcInfoFlags 命名空间中
  • crIn:指定一个函数,返回 IInArchive 接口的对象
  • crOut:指定一个函数,返回 IOutArchive 接口的对象
  • isArc,指定一个函数,用来检测传入文件是不是该格式

    • 函数签名为 API_FUNC_static_IsArc IsArc_XXX(const Byte *p, size_t size)
    • 参数为传入的缓冲区,返回值为 k_IsArc_Res_YESk_IsArc_Res_NO
    • 可能传入缓冲区 size 太小,不足以准确判断,能够返回 k_IsArc_Res_NEED_MORE
    • 若是只经过 sig 就能准确判断,该参数能够传 NULL
  • cls:指定处理类的类名,一般为 CHandler

    • 实现细节:宏内部会经过 new cls; 的形式建立对象,若是处理类的构造函数须要传入额外的参数,能够直接在类名后添加,如 CMyHandler(...)
相关文章
相关标签/搜索