作爲入門,我的選擇是從 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 個接口,IInArchive
和 IInArchiveGetStream
,就調用 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
接口中的 GetNumberOfProperties
、GetPropertyInfo
、GetNumberOfArchiveProperties
、GetArchivePropertyInfo
這些方法了。
可以看到,與屬性相關的方法僅剩下 GetProperty
、GetArchiveProperty
,前者用來返回文件項目的屬性,後者用來返回存檔包整體的屬性。
注意,重點來了,這 2 個宏有什麼魔力,能夠自動實現這些方法呢?答案是開頭所說的 kProps
和 kArcProps
這 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)
以上宏中參數的含義:
-
n
:char*
,格式名字 -
e
:char*
,後綴名,不帶點- 如果該格式有多種後綴名,以空格分割
- 如果該格式爲多卷存儲,只寫第一卷的後綴名
-
ae
:char*
,以空格分割的額外的後綴名- 該參數的成員數必須和
e
的成員數一致,分別用以描述每個e
的額外後綴名 - 如
.tgz
這樣的後綴名,它實際爲.tar.gz
的簡寫形式,通過e
判斷該文件爲 gzip 格式,但其內部又有 tar,故ae
應該寫爲.tar
- 這裏的後綴名需要帶點,如
.tar
- 如果某個
e
沒有額外後綴名,則要填入*
- 如果所有
e
都沒有額外後綴名,可以傳入NULL
- 示例:
* * .tar .tar
- 該參數的成員數必須和
-
id
:Byte
,格式獨立 ID -
sigSize
:Byte
,簽名的長度 -
sig
:Byte[]
,簽名,即文件頭,用來識別文件格式 -
offs
:UInt16
,簽名偏移,如果簽名不在文件頭部需要手動指定 -
flags
:UInt16
,該格式的一些參數,可用值在IArchive.h
文件的NArcInfoFlags
命名空間中 -
crIn
:指定一個函數,返回IInArchive
接口的對象 -
crOut
:指定一個函數,返回IOutArchive
接口的對象 -
isArc
,指定一個函數,用來檢測傳入文件是否是該格式- 函數簽名爲
API_FUNC_static_IsArc IsArc_XXX(const Byte *p, size_t size)
- 參數爲傳入的緩衝區,返回值爲
k_IsArc_Res_YES
或k_IsArc_Res_NO
- 可能傳入緩衝區
size
過小,不足以準確判斷,可以返回k_IsArc_Res_NEED_MORE
- 如果只通過
sig
就能準確判斷,該參數可以傳NULL
- 函數簽名爲
-
cls
:指定處理類的類名,通常爲CHandler
- 實現細節:宏內部會通過
new cls;
的形式創建對象,如果處理類的構造函數需要傳入額外的參數,可以直接在類名後添加,如CMyHandler(...)
- 實現細節:宏內部會通過