将一个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 -O3
lzma.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<string.h>解决。
类似的警告:

lzma.c:51:13: warning: implicit declaration of function ‘unlink’ [-Wimplicit-function-declaration]
             unlink(argv[3]);

通过添加#include<unistd.h>解决。
还有函数返回值未使用问题,

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_PATH
cp /usr/local/lib/liblzma.so.5.4.7 ./liblzma.so
ls -l ./liblzma.so
-rwxr-xr-x 1 kylin kylin 951648 724 14:19 ./liblzma.so
gcc 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_bound
000000000000bf40 T lzma_block_buffer_decode
0000000000007d58 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.lzma
Input file size=4714

./lzmat -d lzma.lzma lzma.decom

ls -l lzma.c lzma.lzma lzma.decom
-rw-rw-r-- 1 kylin kylin 4714 724 14:15 lzma.c
-rw-rw-r-- 1 kylin kylin 4714 724 14:46 lzma.decom
-rw-rw-r-- 1 kylin kylin 1278 724 14:45 lzma.lzma

源文件4714字节,压缩后1278字节,解压后4714字节,打开看内容也是正确的。
再做压缩率和用时测试,因为设定了最大压缩级别=9,很慢,和xz工具几乎完全一致。

time xz -T0 -k -9 clickhouse
xz:已将所使用的线程数从 8 减小为 1,以不超出 1,927 MiB 的内存用量限制

real	3m58.052s
user	3m57.712s
sys	0m0.628s
time ./lzmat -c clickhouse clickhouse.lzma
Input file size=549101800

real	2m38.908s
user	3m56.256s
sys	0m5.744s

ls -l clickhouse.*
-rw-rw-r-- 1 kylin kylin 175161389 723 15:21 clickhouse.lz4
-rw-rw-r-- 1 kylin kylin  76556763 724 16:17 clickhouse.lzma
-rwxr-xr-x 1 kylin kylin  77118536 214 09:27 clickhouse.xz
-rw-rw-r-- 1 kylin kylin 106616762 723 15:00 clickhouse.zst
-rwxr-xr-x 1 kylin kylin 117150311 214 09:27 clickhouse.zstd

为便于查看,将修复的c源代码抄录如下,感谢原作者的贡献:

#include<stdio.h>
#include<malloc.h>
#include<string.h>
#include<unistd.h>

#include"LzmaLib.h"
void Usage()
{
    fputs(
        "Usage:\n"
        "LZMAComp <command> <input> <output>\n"
        "Command:\n"
        "  -c: Compress a single file <input> into <output>.\n"
        "  -d: Decompress a single file <input> into <output>.\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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值