You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS平台Objective-C实现.xz格式压缩的问题求助

问题:XZ格式压缩后无法解压,寻求替代库或NSTask实现方案

我正在开发一款支持zip、7z、xz等格式的归档APP,目前已完成zip与7z格式的压缩功能,但在.xz格式压缩上遇到困难。我采用Tukaani项目的XZ utils库完成压缩后,使用其他APP测试解压时出现“无法提取文件内容”的错误。

我使用该库中的03_compress_custom.c类进行压缩,相关代码如下:

static bool init_encoder(lzma_stream *strm, uint32 preset) {
    // Use the default preset (6) for LZMA2.
    //
    // The lzma_options_lzma structure and the lzma_lzma_preset() function
    // are declared in lzma/lzma12.h (src/liblzma/api/lzma/lzma12.h in the
    // source package or e.g. /usr/include/lzma/lzma12.h depending on
    // the install prefix).
    lzma_options_lzma opt_lzma2;
    if (lzma_lzma_preset(&opt_lzma2, preset)) {
        // It should never fail because the default preset
        // (and presets 0-9 optionally with LZMA_PRESET_EXTREME)
        // are supported by all stable liblzma versions.
        //
        // (The encoder initialization later in this function may
        // still fail due to unsupported preset *if* the features
        // required by the preset have been disabled at build time,
        // but no-one does such things except on embedded systems.)
        fprintf(stderr, "Unsupported preset, possibly a bug\n");
        return false;
    }

    // Now we could customize the LZMA2 options if we wanted. For example,
    // we could set the the dictionary size (opt_lzma2.dict_size) to
    // something else than the default (8 MiB) of the default preset.
    // See lzma/lzma12.h for details of all LZMA2 options.

    // The x86 BCJ filter will try to modify the x86 instruction stream so
    // that LZMA2 can compress it better. The x86 BCJ filter doesn't need
    // any options so it will be set to NULL below.

    // Construct the filter chain. The uncompressed data goes first to
    // the first filter in the array, in this case the x86 BCJ filter.
    // The array is always terminated by setting .id = LZMA_VLI_UNKNOWN.
    //
    // See lzma/filter.h for more information about the lzma_filter
    // structure.
    lzma_filter filters[] = {
        { .id = LZMA_FILTER_X86, .options = NULL },
        { .id = LZMA_FILTER_LZMA2, .options = &opt_lzma2 },
        { .id = LZMA_VLI_UNKNOWN, .options = NULL },
    };

    // Initialize the encoder using the custom filter chain.
    lzma_ret ret = lzma_stream_encoder(strm, filters, LZMA_CHECK_CRC64);
    if (ret == LZMA_OK)
        return true;

    const char *msg;
    switch (ret) {
        case LZMA_MEM_ERROR:
            msg = "Memory allocation failed";
            break;
        case LZMA_OPTIONS_ERROR:
            // We are no longer using a plain preset so this error
            // message has been edited accordingly compared to
            // 01_compress_easy.c.
            msg = "Specified filter chain is not supported";
            break;
        case LZMA_UNSUPPORTED_CHECK:
            msg = "Specified integrity check is not supported";
            break;
        default:
            msg = "Unknown error, possibly a bug";
            break;
    }

    fprintf(stderr, "Error initializing the encoder: %s (error code %u)\n",
            msg, ret);
    return false;
}

// This function is identical to the one in 01_compress_easy.c.
static bool compress(lzma_stream *strm, FILE *infile, FILE *outfile) {
    lzma_action action = LZMA_RUN;
    uint8_t inbuf[BUFSIZ];
    uint8_t outbuf[BUFSIZ];

    strm->next_in = NULL;
    strm->avail_in = 0;
    strm->next_out = outbuf;
    strm->avail_out = sizeof(outbuf);

    while (true) {
        if (strm->avail_in == 0 && !feof(infile)) {
            strm->next_in = inbuf;
            strm->avail_in = fread(inbuf, 1, sizeof(inbuf), infile);

            if (ferror(infile)) {
                fprintf(stderr, "Read error: %s\n", strerror(errno));
                return false;
            }

            if (feof(infile))
                action = LZMA_FINISH;
        }

        lzma_ret ret = lzma_code(strm, action);

        if (strm->avail_out == 0 || ret == LZMA_STREAM_END) {
            size_t write_size = sizeof(outbuf) - strm->avail_out;

            if (fwrite(outbuf, 1, write_size, outfile) != write_size) {
                fprintf(stderr, "Write error: %s\n", strerror(errno));
                return false;
            }

            strm->next_out = outbuf;
            strm->avail_out = sizeof(outbuf);
        }

        if (ret != LZMA_OK) {
            if (ret == LZMA_STREAM_END)
                return true;

            const char *msg;
            switch (ret) {
                case LZMA_MEM_ERROR:
                    msg = "Memory allocation failed";
                    break;
                case LZMA_DATA_ERROR:
                    msg = "File size limits exceeded";
                    break;
                default:
                    msg = "Unknown error, possibly a bug";
                    break;
            }

            fprintf(stderr, "Encoder error: %s (error code %u)\n",
                    msg, ret);
            return false;
        }
    }
}

最后我通过以下方法执行压缩:

FILE *input = fopen(sourcePath.fileSystemRepresentation, "r");
if(input == NULL) {
    NSLog(@"INPUT ERROR");
}
FILE *output = fopen(destinationPath.fileSystemRepresentation, "wb");
if(output == NULL) {
    NSLog(@"OUTPUT ERROR");
}
lzma_stream strm = LZMA_STREAM_INIT;
bool success = init_encoder(&strm, level);
if(success) {
    success = compress(&strm, input, output);
}
lzma_end(&strm);
if (fclose(stdout)) {
    fprintf(stderr, "Write error: %s\n", strerror(errno));
    success = false;
}
if(success) {
    NSLog(@"XZ COMPRESSED");
    [self archivingCompleted];
} else {
    NSLog(@"XZ FAILED");
    [self archivingFailedWithError:nil];
}

请问是否有其他可用的库,或者能否通过NSTask来实现.xz格式的压缩?恳请提供相关建议。


回答

先排查当前代码的问题

你遇到的解压失败大概率是因为当前代码生成的是原始LZMA2压缩流,而非标准的.xz归档文件。大多数解压APP(比如系统的Archive Utility)需要的是包含文件头、索引等结构的完整.xz容器,而你用lzma_stream_encoder结合自定义filter链生成的只是裸压缩数据,缺少这些必要的容器结构。

修复这个问题最简单的方式是改用lzma_easy_encoder,它会自动生成标准.xz容器:

// 替换你的init_encoder函数的核心逻辑
lzma_ret ret = lzma_easy_encoder(strm, preset, LZMA_CHECK_CRC64);
// 后续错误处理逻辑可以保留

这样生成的文件就是标准.xz格式,其他APP就能正常解压了。


替代方案1:用NSTask调用系统xz命令

macOS自带了xz命令行工具,用NSTask调用它是最省心的方案,兼容性拉满,不需要自己处理底层压缩细节。以下是示例代码:

- (void)compressFileToXZWithSourcePath:(NSString *)sourcePath destinationPath:(NSString *)destinationPath completion:(void(^)(BOOL success))completion {
    NSTask *xzTask = [[NSTask alloc] init];
    // 设置xz命令路径(系统默认在/usr/bin/xz)
    xzTask.launchPath = @"/usr/bin/xz";
    // 参数:-k保留原文件,-c输出到stdout,然后重定向到目标文件;-9是最高压缩级别,可按需调整
    xzTask.arguments = @[@"-k", @"-c", @"-9", sourcePath];
    
    // 设置输出文件
    NSFileHandle *outputFile = [NSFileHandle fileHandleForWritingAtPath:destinationPath];
    if (!outputFile) {
        completion(NO);
        return;
    }
    xzTask.standardOutput = outputFile;
    
    // 监控任务完成
    [[NSNotificationCenter defaultCenter] addObserverForName:NSTaskDidTerminateNotification object:xzTask queue:nil usingBlock:^(NSNotification *note) {
        [outputFile closeFile];
        [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTaskDidTerminateNotification object:xzTask];
        completion(xzTask.terminationStatus == 0);
    }];
    
    // 启动任务
    @try {
        [xzTask launch];
    } @catch (NSException *exception) {
        [outputFile closeFile];
        completion(NO);
    }
}

使用时直接传入源文件路径、目标.xz路径和回调即可。


替代方案2:使用libarchive库

libarchive是一个跨平台的归档处理库,支持包括xz在内的几乎所有主流格式,封装层次更高,使用起来比直接调用XZ Utils更简单。你可以通过Homebrew安装它,或者集成到你的Xcode项目中。

示例代码(压缩单个文件到.xz):

#include <archive.h>
#include <archive_entry.h>

- (BOOL)compressFileToXZWithSourcePath:(NSString *)sourcePath destinationPath:(NSString *)destinationPath {
    struct archive *a = archive_write_new();
    archive_write_set_format_raw(a); // 因为xz是单文件压缩,用raw格式
    archive_write_add_filter_xz(a);
    archive_write_open_filename(a, [destinationPath UTF8String]);
    
    struct archive_entry *entry = archive_entry_new();
    archive_entry_set_pathname(entry, [sourcePath lastPathComponent].UTF8String);
    archive_entry_set_size(entry, [[[NSFileManager defaultManager] attributesOfItemAtPath:sourcePath error:nil] fileSize]);
    archive_entry_set_filetype(entry, AE_IFREG);
    archive_entry_set_perm(entry, 0644);
    archive_write_header(a, entry);
    
    NSFileHandle *inputFile = [NSFileHandle fileHandleForReadingAtPath:sourcePath];
    if (!inputFile) {
        archive_entry_free(entry);
        archive_write_free(a);
        return NO;
    }
    
    char buffer[BUFSIZ];
    NSData *data;
    while ((data = [inputFile readDataOfLength:BUFSIZ]) && data.length > 0) {
        memcpy(buffer, data.bytes, data.length);
        archive_write_data(a, buffer, data.length);
    }
    
    [inputFile closeFile];
    archive_entry_free(entry);
    archive_write_close(a);
    archive_write_free(a);
    
    return YES;
}

libarchive的优势是统一的API,如果你已经处理zip、7z格式,用它可以统一你的代码逻辑,减少维护成本。

内容的提问来源于stack exchange,提问作者osxDev123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:04:44