将一个Windows上的lzma压缩解压程序移植到Linux的方法
在0xaa55论坛的【C】7z源代码的使用——LZMA压缩解压算法帖子中看到一个简单的lzma压缩解压程序,好奇它能否在Linux下编译,理论上Linux的xz压缩工具底层就是调用liblzma库,应该没问题。
但实际操作还是有一些需要注意的地方,记录如下。
0.源文件压缩包问题。
帖子作者发的LZMA SDK下载地址:
http://ncu.dl.sourceforge.net/project/sevenzip/LZMA%20SDK/lzma920.tar.bz2已经不可用,我用7-zip官网2025最新版代替。
1.头文件大小写问题
我把lzma2500.7z解压缩,发现C函数的头文件和实现源代码均在C子目录下,所以设定编译参数-I C。
原程序写的是#include\"lzmalib.h\"
,而实际文件名是LzmaLib.h,看来俄国程序员(Igor Pavlov)对大小写敏感字符串真是偏爱(同是俄国人开发的Clickhouse中很多函数也区分大小写)。这在Windows下是能找到的,但在Linux下就报错:
gcc lzma.c -I C -llzma -o lzmat -O3lzma.c:3:20: fatal error: lzmalib.h: 没有那个文件或目录compilation terminated.
把它改成#include\"LzmaLib.h\"
就好了。
2.C系统库函数问题
/tmp/ccgJ7hEs.o:在函数‘main’中:lzma.c:(.text.startup+0x2c):对‘stricmp’未定义的引用
查了一下,stricmp是windows特有的。而linux中对应的是strcasecmp,包含在头文件string.h下。
改名之后,编译错误变成了警告:
lzma.c: In function ‘main’:lzma.c:25:9: warning: implicit declaration of function ‘strcasecmp’ [-Wimplicit-function-declaration] if(!strcasecmp(argv[1],\"-c\"))//压缩一个文件
此问题通过添加#include
解决。
类似的警告:
lzma.c:51:13: warning: implicit declaration of function ‘unlink’ [-Wimplicit-function-declaration] unlink(argv[3]);
通过添加#include
解决。
还有函数返回值未使用问题,
lzma.c: In function ‘CompressFile’:lzma.c:100:5: warning: ignoring return value of ‘fread’, declared with attribute warn_unused_result [-Wunused-result] fread(pInBuffer,1,InSize,fpIn);//读取文件
通过赋值给一个变量a解决。
3.调用lzma相关函数问题
这里折腾时间最久,又是指定-L编译参数,又是设置LIBRARY_PATH环境变量,还把动态库改名复制到当前目录,始终报告同样的对‘LzmaCompress’未定义的引用
错误。
export LIBRARY_PATH=/usr/local/lib:$LIBRARY_PATHcp /usr/local/lib/liblzma.so.5.4.7 ./liblzma.sols -l ./liblzma.so-rwxr-xr-x 1 kylin kylin 951648 7月 24 14:19 ./liblzma.sogcc lzma.c -I C -llzma -o lzmat -O3 -L .//tmp/ccGui3Ty.o:在函数‘CompressFile’中:lzma.c:(.text+0xd8):对‘LzmaCompress’未定义的引用/tmp/ccGui3Ty.o:在函数‘DecompressFile’中:lzma.c:(.text+0x2d4):对‘LzmaUncompress’未定义的引用collect2: error: ld returned 1 exit status
这里我犯了经验主义的错误,以为liblzma.so中就有对应的函数,结果拿nm工具检查nm -D liblzma.so | grep \" T \" >lzmalib.txt
里面都是全小写带下划线分隔符的函数,典型的linux风格。
0000000000007d18 T lzma_block_buffer_bound000000000000bf40 T lzma_block_buffer_decode0000000000007d58 T lzma_block_buffer_encode
好在lzma2500.7z包含源代码,检查后发现LzmaCompress在LzmaLib.c中,改写编译命令包含它,结果又有新的错误。
gcc lzma.c C/LzmaLib.c -I C -o lzmat -O3/tmp/ccojhmmo.o:在函数‘LzmaCompress’中:LzmaLib.c:(.text+0x58):对‘LzmaEncProps_Init’未定义的引用LzmaLib.c:(.text+0x94):对‘g_Alloc’未定义的引用LzmaLib.c:(.text+0x98):对‘g_Alloc’未定义的引用LzmaLib.c:(.text+0xb8):对‘LzmaEncode’未定义的引用
函数调用函数,没完没了,这样一个个添加源文件太费劲了。干脆用通配符*代替。
gcc lzma.c C/*.c -I C -o lzmat -O3
所有lzma相关函数未定义的引用全都解决了。
4.调用外部依赖库问题
以上编译命令行还遗留最后的问题
/tmp/ccGANPyb.o:在函数‘Thread_Create_With_CpuSet’中:Threads.c:(.text+0x94):对‘pthread_attr_setaffinity_np’未定义的引用Threads.c:(.text+0xa8):对‘pthread_create’未定义的引用
这个我遇到过,添加pthread动态库调用就编译成功了。测试如下:
gcc lzma.c C/*.c -I C -o lzmat -O3 -lpthread./lzmat -c lzma.c lzma.lzmaInput file size=4714./lzmat -d lzma.lzma lzma.decomls -l lzma.c lzma.lzma lzma.decom-rw-rw-r-- 1 kylin kylin 4714 7月 24 14:15 lzma.c-rw-rw-r-- 1 kylin kylin 4714 7月 24 14:46 lzma.decom-rw-rw-r-- 1 kylin kylin 1278 7月 24 14:45 lzma.lzma
源文件4714字节,压缩后1278字节,解压后4714字节,打开看内容也是正确的。
再做压缩率和用时测试,因为设定了最大压缩级别=9,很慢,和xz工具几乎完全一致。
time xz -T0 -k -9 clickhousexz:已将所使用的线程数从 8 减小为 1,以不超出 1,927 MiB 的内存用量限制real3m58.052suser3m57.712ssys0m0.628stime ./lzmat -c clickhouse clickhouse.lzmaInput file size=549101800real2m38.908suser3m56.256ssys0m5.744sls -l clickhouse.*-rw-rw-r-- 1 kylin kylin 175161389 7月 23 15:21 clickhouse.lz4-rw-rw-r-- 1 kylin kylin 76556763 7月 24 16:17 clickhouse.lzma-rwxr-xr-x 1 kylin kylin 77118536 2月 14 09:27 clickhouse.xz-rw-rw-r-- 1 kylin kylin 106616762 7月 23 15:00 clickhouse.zst-rwxr-xr-x 1 kylin kylin 117150311 2月 14 09:27 clickhouse.zstd
为便于查看,将修复的c源代码抄录如下,感谢原作者的贡献:
#include#include#include#include#include\"LzmaLib.h\"void Usage(){ fputs( \"Usage:\\n\" \"LZMAComp \\n\" \"Command:\\n\" \" -c: Compress a single file into .\\n\" \" -d: Decompress a single file into .\\n\",stderr);}int CompressFile(FILE*fpOut,FILE*fpIn,unsigned long InSize);int DecompressFile(FILE*fpOut,FILE*fpIn);int main(int argc,char**argv){ if(argc<4) { Usage(); return 1; } if(!strcasecmp(argv[1],\"-c\"))//压缩一个文件 { FILE*fp=fopen(argv[2],\"rb\"); FILE*fpout=fopen(argv[3],\"wb\"); int iRet; unsigned long fLen; if(!fp) { fprintf(stderr,\"Unable to open %s\\n\",argv[2]); return 2; } if(!fpout) { fprintf(stderr,\"Unable to write %s\\n\",argv[3]); return 2; } fseek(fp,0,SEEK_END); fLen=ftell(fp); fseek(fp,0,SEEK_SET); printf(\"Input file size=%lu\\n\",fLen); iRet=CompressFile(fpout,fp,fLen); if(iRet) fprintf(stderr,\"Error:%d\\n\",iRet); fclose(fpout); fclose(fp); if(iRet) unlink(argv[3]); return iRet; } if(!strcasecmp(argv[1],\"-d\"))//解压一个文件 { FILE*fp=fopen(argv[2],\"rb\"); FILE*fpout=fopen(argv[3],\"wb\"); int iRet; if(!fp) { fprintf(stderr,\"Unable to open %s\\n\",argv[2]); return 2; } if(!fpout) { fprintf(stderr,\"Unable to write %s\\n\",argv[3]); return 2; } iRet=DecompressFile(fpout,fp); if(iRet) fprintf(stderr,\"Error:%d\\n\",iRet); fclose(fpout); fclose(fp); if(iRet) unlink(argv[3]); return iRet; } Usage(); return 1;}int CompressFile(FILE*fpOut,FILE*fpIn,unsigned long InSize){ void*pInBuffer;//输入缓冲区 void*pOutBuffer;//输出缓冲区 unsigned long OutSize;//输出缓冲区大小 unsigned char Props[LZMA_PROPS_SIZE];//属性 size_t PropsSize=LZMA_PROPS_SIZE;//属性大小 pInBuffer=malloc(InSize);//缓冲区分配内存 pOutBuffer=malloc(OutSize=InSize);//输出缓冲区分配和输入缓冲区一样大的内存 if(!pInBuffer||!pOutBuffer) { free(pInBuffer); free(pOutBuffer); return 2; } int a=fread(pInBuffer,1,InSize,fpIn);//读取文件 switch(LzmaCompress(//开始压缩 pOutBuffer,&OutSize,//输出缓冲区,大小 pInBuffer,InSize,//输入缓冲区,大小 Props,&PropsSize,//属性,属性大小 9,0,-1,-1,-1,-1,-1))//压缩比最大。其余全部取默认 { case SZ_OK://成功完成 fwrite(&InSize,1,sizeof(InSize),fpOut);//写入原数据大小 fwrite(&OutSize,1,sizeof(OutSize),fpOut);//写入解压后的数据大小 fwrite(Props,1,PropsSize,fpOut);//写入属性 fwrite(pOutBuffer,1,OutSize,fpOut);//写入缓冲区 free(pInBuffer);//释放内存 free(pOutBuffer); return 0; case SZ_ERROR_PARAM://参数错误 free(pInBuffer); free(pOutBuffer); return 1; default: case SZ_ERROR_MEM://内存分配错误 case SZ_ERROR_THREAD://线程错误 free(pInBuffer); free(pOutBuffer); return 2; case SZ_ERROR_OUTPUT_EOF://缓冲区过小 free(pInBuffer); free(pOutBuffer); return 3; }}int DecompressFile(FILE*fpOut,FILE*fpIn){ void*pSrcBuffer; size_t InSize; void*pDestBuffer; size_t OutSize; unsigned char Props[LZMA_PROPS_SIZE]; int a=fread(&OutSize,1,sizeof(OutSize),fpIn);//读取原数据大小 a=fread(&InSize,1,sizeof(InSize),fpIn);//读取压缩后的数据大小 pDestBuffer=malloc(OutSize);//分配内存 pSrcBuffer=malloc(InSize);//分配内存 if(!pSrcBuffer||!pDestBuffer)//内存不足 { free(pSrcBuffer); free(pDestBuffer); return 2; } a=fread(Props,1,sizeof(Props),fpIn); a=fread(pSrcBuffer,1,InSize,fpIn); switch(LzmaUncompress(pDestBuffer,&OutSize,pSrcBuffer,&InSize,Props,sizeof(Props))) { case SZ_OK: fwrite(pDestBuffer,1,OutSize,fpOut); free(pDestBuffer); free(pSrcBuffer); return 0; case SZ_ERROR_DATA: case SZ_ERROR_UNSUPPORTED: case SZ_ERROR_INPUT_EOF: free(pDestBuffer); free(pSrcBuffer); return 1; default: case SZ_ERROR_MEM: free(pDestBuffer); free(pSrcBuffer); return 2; }}