爲 7-Zip 寫一個存檔格式插件 (3):理解 SplitHandler

作爲入門,我的選擇是從 SplitHandler 下手,因爲這裏不牽扯任何壓縮和哈希算法邏輯,也沒有任何文件結構,僅僅是把 .001、.002、... 這樣的序列文件合併爲一個文件,從實現邏輯上來講是最簡單的。

整個文件代碼太長,這裏不貼出來了,可以自己到 https://github.com/wzv5/7z-formatzzz/blob/master/CPP/7zip/Archive/SplitHandler.cpp 查看。

整體結構

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[] 數組,這些數組的含義之後再說。

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 繼承自 CMyUnknownImp

如果支持讀取此存檔格式,則 public 繼承 IInArchive

如果此存檔格式的文件項目能夠提供順序流讀取,則 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(...)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章