爲 7-Zip 寫一個存檔格式插件 (2):IArchive.h

之前只知道 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() 方法中作爲參數傳入,其本身還用作其他用途,如可以通過該接口 QueryInterfaceIArchiveOpenVolumeCallback 接口,從而獲知當前打開的存檔文件的相關信息。

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:存檔包整體不含任何屬性,包括文件大小也不能獲取
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章