avatar nyne
Open App

7zip LZMA SDK 的使用

#cpp

目标

使用c++, 写一个wrapper, 完成一个简单易用的7zip解压和压缩库

开始

首先下载 LZMA SDK, 然后看看里面有什么东西, 压缩包内容如下

- Asm
- bin
- C
- CPP
- CS
- DOC
- Java

既然有cpp的话, 那么就尝试用它, cpp的接口应该会比c更好用. 打开cpp目录的瞬间我就后悔了, 这是什么乱七八糟的东西. 还是用c吧, c目录下面就是简单易懂的.c.h文件

看看文档里面有什么, 只给了一个c程序示例, 代码里面几乎没有注释. 没办法, 就这样开始吧.

实现解压功能

初始化

7zip需要我们提供内存分配和回收的函数

void *_AllocImp(ISzAllocPtr p, size_t size) {
  return malloc(size);
}

void _FreeImp(ISzAllocPtr p, void *ptr) {
  if (ptr != nullptr) {
    free(ptr);
  }
}

static ISzAlloc AllocImp = { _AllocImp, _FreeImp };

打开压缩包

class Archive {
private:
  CFileInStream archiveStream;
  CLookToRead2 lookStream;
  CSzArEx db;
  ISzAlloc allocImp = AllocImp;
  ISzAlloc allocTempImp = AllocImp;

public:
  ArchiveStatus status = kArchiveOK;

  explicit Archive(const char* path) {
    if (InFile_Open(&archiveStream.file, path)) {
      status = kArchiveOpenError;
      return;
    }
    FileInStream_CreateVTable(&archiveStream);
    lookStream.realStream = &archiveStream.vt;
    lookStream.buf = new Byte[16 * 1024];
    lookStream.bufSize = 16 * 1024;
    LookToRead2_CreateVTable(&lookStream, False);
    SzArEx_Init(&db);
    if (const auto res = SzArEx_Open(&db, &lookStream.vt, &allocImp, &allocTempImp); res != SZ_OK) {
      status = kArchiveOpenError;
      File_Close(&archiveStream.file);
    }
  }

  ~Archive() {
    SzArEx_Free(&db, &allocImp);
    File_Close(&archiveStream.file);
    delete [] lookStream.buf;
  }
};

第一步就出问题了, 验证头部的crc32时, 7zip的crc32函数算不对. 这个函数里面大量的使用定义的宏, 而且点开一个宏定义又会发现它用了其它的宏定义, 完全没办法看. 而且它还经常用宏定义变量, 就像这样:

static SRes SecToLook_Read(ISeqInStreamPtr pp, void *buf, size_t *size)
{
  Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSecToLook)
  return LookInStream_LookRead(p->realStream, buf, size);
}

猜猜变量p是从哪来的, 是第一行的宏定义! 实在无法理解这种写法, 老老实实写个函数不好吗.

所以该怎么办呢, 突然想起来miniz里面的那个crc32函数不错, 直接把它粘过来用. 好, 问题解决. 更新: 发现有个CrcGenerateTable()函数, 调用它就解决了问题

获取文件数量

很简单

uint32_t numFiles() const {
    return db.NumFiles;
}

获取文件信息

从db里拿信息即可

唯一的问题是, 它的filename是utf-16, 但它用char*来储存, length是两Byte为step. 一开始看到它是char*类型以为是utf-8, 在这里卡了好久.

ArchiveFile getFileByIndex(const uint32_t index) const {
    ArchiveFile archiveFile;

    archiveFile.is_dir = SzArEx_IsDir(&db, index) ? 1 : 0;

    const size_t offs = db.FileNameOffsets[index];
    const size_t len = db.FileNameOffsets[index + 1] - offs;
    auto fileName = new wchar_t[len+1];
    for (auto i = 0; i < len; i++) {
      fileName[i] = db.FileNames[offs + i*2] + (db.FileNames[offs + i*2 + 1] << 16);
    }
    fileName[len] = 0;
    archiveFile.name = fileName;

    archiveFile.size = SzArEx_GetFileSize(&db, index);

    if (db.CRCs.Defs[index]) {
      archiveFile.crc32 = db.CRCs.Vals[index];
    } else {
      archiveFile.crc32 = 0;
    }

    const CSzBitUi64s *timeTable = nullptr;
    if (db.CTime.Defs != nullptr) {
      timeTable = &db.CTime;
    } else if (db.MTime.Defs != nullptr) {
      timeTable = &db.MTime;
    }
    if (timeTable != nullptr) {
      if (timeTable->Defs[index]) {
        auto [Low, High] = timeTable->Vals[index];
        archiveFile.time = Low + (static_cast<uint64_t>(High) << 32);
      } else {
        archiveFile.time = 0;
      }
    } else {
      archiveFile.time = 0;
    }

    return archiveFile;
  }

解压文件

使用SzArEx_Extract函数, 这个函数要传一大堆参数, 而且一行注释都没有.

ArchiveStatus extractFileToPath(const uint32_t index, const char* path) const {
    const ArchiveFile archiveFile = getFileByIndex(index);
    if (archiveFile.is_dir) {
      return kArchiveReadError;
    }
    size_t read = 0;
    uint32_t blockIndex;
    Byte* outBuffer = nullptr;
    size_t outBufferSize;
    size_t offset;
    size_t outSizeProcessed;
    std::ofstream outFile;
    outFile.open(path, std::ios::binary | std::ios::out);
    if (!outFile.is_open()) {
      return kArchiveOpenError;
    }
    if (!outFile.good()) {
      return kArchiveOpenError;
    }
    while (read < archiveFile.size) {
      if (const auto res = SzArEx_Extract(&db, &lookStream.vt, index, &blockIndex, &outBuffer, &outBufferSize, &offset, &outSizeProcessed, &allocImp, &allocTempImp); res != SZ_OK) {
        outFile.close();
        return ArchiveStatus::kArchiveReadError;
      }
      outFile.write(reinterpret_cast<const char *>(outBuffer+offset), outSizeProcessed);
      read += outSizeProcessed;
    }
    outFile.close();
    return kArchiveOK;
  }