在上一篇文中,我們講了 SplitHandler 中的整體結構,剩下的就是實現存檔包的操作邏輯,也就是實現 CHandler
類。
再次明確一下,我們的目標是實現一個只讀的存檔包格式插件,所以這裏只討論 IInArchive
接口的實現。
IInArchive
接口中還剩下這些方法需要討論:
- Open
- Close
- GetNumberOfItems
- Extract
- GetArchiveProperty
- GetProperty
Close
、GetNumberOfItems
顧名思義,關閉文件和獲取存檔包內的文件項目數量,沒什麼好說的,直接跳過,講講剩下這幾個。
Open
STDMETHODIMP CHandler::Open(
IInStream* stream,
const UInt64 *maxCheckStartPosition,
IArchiveOpenCallback* callback
)
參數1 stream
就是要打開的存檔包的流對象,流對象應該不用多說,7z 中的流接口比 STL 中的流簡單多了,一目瞭然。
參數2 maxCheckStartPosition
,IArchive.h
的註釋中有講,本系列第 2 篇文中也有說。
參數3 callback
表面上看是打開進度回調,但別忘了它實現了 IUnknown
接口,是可以查詢關聯的其他接口的。
CMyComPtr<IArchiveOpenVolumeCallback> volumeCallback;
callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&volumeCallback);
比如 Split 就通過 callback
查詢到了 IArchiveOpenVolumeCallback
接口,從而獲取了當前打開的存檔包的文件名。
對於 Split 這樣的多卷存檔格式,打開剩餘的其他卷也是通過 IArchiveOpenVolumeCallback
接口,調用其 GetStream
方法,傳入其他卷的文件名,就能獲得其他卷的輸入流。
說回 callback
的本質,用它報告打開進度就是調用 SetCompleted
方法。如果存檔格式支持直接獲取所有文件項目數,還可以先調用 SetTotal
設置總數,這是可選的,不設置總數也行,就像 Split 這樣。
Extract
STDMETHODIMP CHandler::Extract(
const UInt32 *indices,
UInt32 numItems,
Int32 testMode,
IArchiveExtractCallback *extractCallback
)
參數1 indices
爲要解壓的所有索引的數組,7z 強制要求,該數組傳入時必須是已排序的。排序後的索引應該能夠提高解壓性能,因爲順序讀入一次就行了。
參數2 numItems
爲參數 indices
數組的成員數。如果要解壓所有項目,該參數爲 -1。注意,該參數的類型爲 UInt32,所以 -1 這個值實際爲 0xffffffff,官方源碼中是這樣寫的:(UInt32)(Int32)-1
。
參數3 testMode
可以認爲是個 bool 類型,表示本次解壓是否爲測試模式,測試模式下不會實際寫出文件。
參數4 extractCallback
爲解壓進度回調。但是像 IArchiveOpenCallback
打開回調一樣,該參數也不僅僅只有解壓進度回調這一個功能。它還用於獲取寫出流,即調用 GetStream
方法。有了這個寫出流,我們才能寫出數據。
該方法的實現流程大概是這樣:
- 判斷
indices
和numItems
是否合理 - 調用
extractCallback->SetTotal()
設置總進度,這個總數爲要解壓的總字節數 - 創建需要的 Decoder 對象,從而獲取解碼後的數據
- 進入循環,對每個文件項目調用
extractCallback->GetStream()
獲取寫出流 - 調用
extractCallback->PrepareOperation()
讓 7z 做好準備 - 通過 Decoder 對象的方法,或自己手動,把解碼後的數據寫入第 4 步返回的流中
- 更新解壓進度
- 調用
extractCallback->SetOperationResult()
設置當前項目的解壓結果 - 回到第 4 步,繼續處理下一個要解壓的文件項目,直到所有文件解壓完畢
GetArchiveProperty
上一篇文中講到,對於存檔包整體的所有屬性,應該在全局 kArcProps
數組中寫出來。通過 IMP_IInArchive_ArcProps
宏,我們就只需要自己實現一下 GetArchiveProperty
方法。
照葫蘆畫瓢就行了。
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
{
NCOM::CPropVariant prop;
switch (propID)
{
case kpidMainSubfile: prop = (UInt32)0; break;
case kpidPhySize: if (!_sizes.IsEmpty()) prop = _sizes[0]; break;
case kpidTotalPhySize: prop = _totalSize; break;
case kpidNumVolumes: prop = (UInt32)_streams.Size(); break;
}
prop.Detach(value);
return S_OK;
}
每個屬性的含義,看它的名字就知道,看不出來怎麼辦呢?猜唄,不然還能如何。。這裏官方源碼中完全沒有註釋!什麼都沒有講!我們知道的就只有屬性名字是什麼,它的數據類型是什麼。
據我的調試觀察,即使之前調用 IMP_IInArchive_ArcProps_NO_Table
,即定義存檔包不含任何屬性,7z 仍然會嘗試獲取以下這些屬性:
- kpidMainSubfile
- kpidOffset
- kpidPhySize
- kpidError
- kpidIsAltStream
- kpidIsAux
- kpidIsDeleted
- kpidIsTree
- kpidErrorFlags
- kpidWarningFlags
- kpidWarning
- kpidINode
- kpidReadOnly
GetProperty
與 GetArchiveProperty
類似,這裏是獲取每個文件項目的屬性。
STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)
{
NCOM::CPropVariant prop;
switch (propID)
{
case kpidPath: prop = _subName; break;
case kpidSize:
case kpidPackSize:
prop = _totalSize;
break;
}
prop.Detach(value);
return S_OK;
}
同樣,屬性的含義只能靠猜。。
以下屬性是一定會嘗試獲取的:
- kpidIsDir
- kpidMTime
- kpidAttrib
- kpidCRC
- kpidNumSubDirs
- kpidNumSubFiles
- kpidZerosTailIsAllowed