爲 7-Zip 寫一個存檔格式插件 (4):實現 CHandler

在上一篇文中,我們講了 SplitHandler 中的整體結構,剩下的就是實現存檔包的操作邏輯,也就是實現 CHandler 類。

再次明確一下,我們的目標是實現一個只讀的存檔包格式插件,所以這裏只討論 IInArchive 接口的實現。

IInArchive 接口中還剩下這些方法需要討論:

  • Open
  • Close
  • GetNumberOfItems
  • Extract
  • GetArchiveProperty
  • GetProperty

CloseGetNumberOfItems 顧名思義,關閉文件和獲取存檔包內的文件項目數量,沒什麼好說的,直接跳過,講講剩下這幾個。

Open

STDMETHODIMP CHandler::Open(
    IInStream* stream,
    const UInt64 *maxCheckStartPosition,
    IArchiveOpenCallback* callback
)

參數1 stream 就是要打開的存檔包的流對象,流對象應該不用多說,7z 中的流接口比 STL 中的流簡單多了,一目瞭然。

參數2 maxCheckStartPositionIArchive.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 方法。有了這個寫出流,我們才能寫出數據。

該方法的實現流程大概是這樣:

  1. 判斷 indicesnumItems 是否合理
  2. 調用 extractCallback->SetTotal() 設置總進度,這個總數爲要解壓的總字節數
  3. 創建需要的 Decoder 對象,從而獲取解碼後的數據
  4. 進入循環,對每個文件項目調用 extractCallback->GetStream() 獲取寫出流
  5. 調用 extractCallback->PrepareOperation() 讓 7z 做好準備
  6. 通過 Decoder 對象的方法,或自己手動,把解碼後的數據寫入第 4 步返回的流中
  7. 更新解壓進度
  8. 調用 extractCallback->SetOperationResult() 設置當前項目的解壓結果
  9. 回到第 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章