7zip LZMA SDK 的使用
目标
使用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;
}