之前只知道 7z 的作者是 Windows 用戶,7z 是在 Windows 上開發出來的。現在看了源碼才知道,作者深受 Windows 影響,代碼中大量依賴 COM 接口,但並沒有使用 ATL 庫,其實是作者自己修改簡化後的 COM 接口實現。如果你沒有 COM 組件開發經驗,那麼這裏會比較難受了,推薦趕緊先去補一補 COM 技術細節。
7z 中的這套 COM 接口簡化了大部分細節,甚至引用計數都沒加鎖,這個操作其實有點迷。。估計作者自己在代碼中能保證線程同步吧。由於有大量輔助宏,實際寫接口實現類時還是比較容易的。
還是有針對性的讀源碼,我們要增加新存檔格式,重點就要看 CPP/7zip/Archive/IArchive.h
文件,這個文件中包含了十幾個接口定義,和一些用於註冊存檔格式的宏,下面我把重要的幾個接口和宏摘出來,現在可以不用懂它們的具體含義,之後遇到時回來查就行。
重要接口
以下所有接口方法的返回值均爲 HRESULT
,且不能拋出異常。
以下所有註釋均以存檔包實現者的角度來寫,而不是庫使用者。
IProgress
進度回調接口,是多個接口的父類。
// 如果 SetTotal 被調用,則:
// 對於 xz、gz、bz2、lzma、z、ppmd 格式,SetCompleted 爲打包大小
// 對於其他格式,SetCompleted 爲解包大小
// 如果 SetTotal 沒有被調用,則 SetCompleted 爲打包大小
SetTotal(UInt64 total);
SetCompleted(const UInt64 *completeValue);
IArchiveOpenCallback
打開存檔的進度回調,在存檔包處理對象的 Open()
方法中作爲參數傳入,其本身還用作其他用途,如可以通過該接口 QueryInterface
到 IArchiveOpenVolumeCallback
接口,從而獲知當前打開的存檔文件的相關信息。
SetTotal(const UInt64 *files, const UInt64 *bytes);
SetCompleted(const UInt64 *files, const UInt64 *bytes);
IArchiveExtractCallback
繼承自 IProgress
。解壓回調,在存檔包處理對象的 Extract()
方法中作爲參數傳入,除了用作報告解壓進度外,其 GetStream()
方法還是重要的用來獲取輸入流的方法。
GetStream(
UInt32 index, // 要解壓的項目索引
ISequentialOutStream **outStream,
Int32 askExtractMode // 可用值爲 NExtract::NAskMode 枚舉,如果不爲 kExtract,則不能實際解壓
);
PrepareOperation(Int32 askExtractMode);
// opRes 的可用值爲 NExtract::NOperationResult 枚舉
SetOperationResult(Int32 opRes);
IArchiveOpenVolumeCallback
用來獲取當前打開卷的相關信息。對於分卷打包格式,還可用來打開後續的其他卷。
GetProperty(PROPID propID, PROPVARIANT *value);
GetStream(const wchar_t *name, IInStream **inStream);
IInArchiveGetStream
用來對外提供一個指定項目的順序輸入流,可能用於流式操作,無縫打開包內的其他存檔包。
GetStream(UInt32 index, ISequentialInStream **stream);
IInArchive
支持讀取的存檔包的核心接口,存檔包對象必須實現的接口之一。
// 如果 kUseGlobalOffset,stream 當前位置可以爲非零
// 如果 !kUseGlobalOffset,stream 當前位置爲 0
// 如果 maxCheckStartPosition == NULL,則 handler 能夠在 stream 中尋找存檔起點
// 如果 *maxCheckStartPosition == 0,則 handler 必須只把當前位置作爲存檔起點
Open(
IInStream *stream,
const UInt64 *maxCheckStartPosition,
IArchiveOpenCallback *openCallback
);
Close();
GetNumberOfItems(UInt32 *numItems);
// 寫出步驟:
// 1. 調用 extractCallback->GetStream(index, &outStream, askMode) 來獲取寫出流
// 2. 調用 extractCallback->PrepareOperation(askMode) 來讓 7z 做好準備
// 3. 創建需要的 Coder 來寫入 outStream
// 4. 寫入完成後,釋放 outStream,調用 extractCallback->SetOperationResult() 設置解壓結果
Extract(
const UInt32* indices, // 要解壓的所有索引數組,必須是已排序的
UInt32 numItems, // -1 爲解壓所有文件,注意這裏類型爲 Uint32
Int32 testMode, // 非零時,僅測試不寫出文件
IArchiveExtractCallback *extractCallback
);
GetNumberOfProperties(UInt32 *numProps);
GetPropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType);
GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value);
GetNumberOfArchiveProperties(UInt32 *numProps);
GetArchivePropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType);
// kpidOffset:存檔數據的起點,VT_EMPTY 表示起點爲 0,可爲 VT_UI4、VT_UI8、VT_I8 類型
// kpidPhySize:存檔數據的實際大小,VT_EMPTY 表示未知,能夠大於文件大小
// kpidIsDeleted、kpidIsAltStream、kpidIsAux、kpidINode:如果 GetProperty 支持這些屬性,則必須返回 VARIANT_TRUE (VT_BOOL)
GetArchiveProperty(PROPID propID, PROPVARIANT *value);
IArchiveOpenSeq
如果存檔包支持從順序輸入流中打開,可以實現該接口。
OpenSeq(ISequentialInStream *stream);
輔助宏
- IMP_IInArchive_Props:重要,用來實現獲取包內項目屬性的相關方法
- IMP_IInArchive_Props_WITH_NAME:同上,同時返回屬性名
- IMP_IInArchive_ArcProps:重要,用來實現獲取存檔包整體屬性的相關方法
- IMP_IInArchive_ArcProps_WITH_NAME:同上,同時返回屬性名
- IMP_IInArchive_ArcProps_NO_Table:存檔包整體不含任何額外屬性,但可獲取如文件大小這樣的基本信息
- IMP_IInArchive_ArcProps_NO:存檔包整體不含任何屬性,包括文件大小也不能獲取