爲 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 篇文中也有說。code

參數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
相關文章
相關標籤/搜索