在 Windows 8 上解压缩存档

Posted

技术标签:

【中文标题】在 Windows 8 上解压缩存档【英文标题】:Unzipping archive on Windows 8 【发布时间】:2012-06-11 04:59:11 【问题描述】:

我以前使用过 MiniZip(zlib 包装器)来解压缩档案。 MiniZip 不能用于 Metro 应用程序,因为它使用“iowin32.c”中已弃用的 API -- CreateFile() 和 SetFilePointer()。

我认为这将是一个简单的解决方法,并创建了“iowinrt.c”,并将 CreateFile() 和 SetFilePointer() 替换为 CreateFile2() 和 SetFilePointerEx()。虽然通过这种方式我获得了一个仅使用经过批准的 Win8 API 的 MiniZip 版本,但它仍然没有用——我忘记了沙盒。如果我使用 FileOpenPicker() 选择一个文件并将其路径传递给我修改后的 MiniZip,我仍然无法打开它—— CreateFile2() 将失败并显示“访问被拒绝”。消息。

因此,如果现在用于文件访问的旧 C API 几乎无用,那么它似乎是无用的;我的理解是,为了解决这个问题,我需要使用新的异步文件访问在 C++/CX 中重新实现我的“iowinrt”。还有其他选择吗?我想我在某处看到 WinRT 确实具有压缩/解压缩功能,但它仅适用于单个文件,而不适用于存档。

我需要它在内存中工作的其他要求。

我一度以为我有一个通过 .NET Framework 4.5 的解决方案:

    我找到了有关如何创建可从 C++/CX 使用的 .NET 类的以下信息: http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/3ff383d0-0c9f-4a30-8987-ff2b23957f01

    .NET Framework 4.5 在 System.IO.Compression 中包含 ZipArchive 和 ZipArchiveEntry 类: http://msdn.microsoft.com/en-us/library/system.io.compression.ziparchive%28v=vs.110%29.aspx#Y0 http://msdn.microsoft.com/en-us/library/system.io.compression.ziparchiveentry%28v=vs.110%29.aspx#Y0

我认为我可以创建带有 WinMD 输出类型的 C# Metro 类库,以公开 ZipArchive 和 ZipArchiveEntry,然后在我的 C++/CX 项目中使用它。但是,即使它起作用,它也不会在内存中起作用;似乎 ZipArchive 和 ZipArchiveEntry 仅适用于文件。

【问题讨论】:

就 minizip 库而言,您的方法是正确且直接的。您传入 minizip 的路径,然后让 I/O 回调在内部重新创建 StorageFile 对象。您是否查看过 Process Monitor 并检查了 I/O 调用和相关错误? @Nathan 谢谢你的建议——没试过,试一试。但是,我暂时基本上放弃了 Win8 C++。在 WinRT C++ 文档赶上 C#/JS 文档之前,将任何进一步的精力投入到 WinRT C++ 编程中都是浪费时间。由于 MS 不认为 C++ 文档很重要(请参阅此处的 cmets:social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/…),我想我会再等一两年再尝试。 这人太坏了。看起来您已经完成了大部分工作。 【参考方案1】:

从存档工作中读取。下面的解释和代码,但在这一点上实际上只是一个 hack,看看它是否可能。我只是一直在修改东西,直到我得到一些工作为止;这只是一个有效的例子,绝不是生产质量代码(它不能重新进入开始)。毫无疑问,有很多东西是不好的/不必要的/wtf,所以请随意使用 cmets 来帮助清理。

如前所述,将路径传递到库已不再足够 - 除非文件位于 KnownFolders 之一(文档、家庭、媒体、音乐、图片、可移动或视频)中,否则您最终会出现“访问被拒绝“ 信息。相反,库必须能够接受从 FileOpenPicker 返回的 StorageFile^。至少我还没有找到其他方法,也许有人知道更好?

MiniZip 通过 iowin32.h/.c 为 zlib 提供 Windows 文件系统访问层。这仍然适用于旧式应用程序的桌面模式,但不适用于 Metro 应用程序,因为它使用已弃用的 API 并依赖于路径。要让 MiniZip 在 Windows 8 上运行,需要完全重写 iowin32。

要让事情再次正常运行,首先要找到一种方法将 StorageFile^ 一直传递到 iowinrt(Windows 8 替代 iowin32)。幸运的是,这不是问题,因为 MiniZip 提供了两种风格的打开文件函数——一种接受指向 char 的指针,另一种接受指向 void 的指针。由于 ^ 仍然只是一个指针,因此将 StorageFile^ 转换为 void* 而不是返回 StorageFile^ 工作正常。

现在我能够将 StorageFile^ 传递给我的新 iowinrt,下一个问题是如何使新的异步 C++ 文件访问 API 与 Zlib 一起工作。为了支持非常旧的 C 编译器,Zlib 是用旧的 K&R 风格的 C 编写的。VisualStudio 编译器将拒绝将其编译为 C++,它必须编译为 C,而新的 iowinrt 当然必须编译为 C++ - 保持这一点创建项目时请牢记。关于 VS 项目需要注意的其他事项是,我将它作为 Visual C++ Windows Metro 风格的静态库,虽然 DLL 也应该可以工作,但是你还必须定义宏来导出 MiniZip API(我没有尝试过,不知道是哪个宏你必须使用)。我想我还必须设置“使用 Windows 运行时扩展”(/ZW),设置“不使用预编译头文件”并将 _CRT_SECURE_NO_WARNINGS 和 _CRT_NONSTDC_NO_WARNINGS 添加到预处理器定义中。

至于 iowinrt 本身,我将其拆分为两个文件。一个拥有两个密封的 ref 类——读取器和写入器对象;他们接受 StorageFile^。 Reader 实现了 Read、Tell、SeekFromBeginning、SeekFromCurrent 和 SeekFromEnd(3 个 Seek 方法的原因是因为 ref 密封类必须坚持使用 RT 类型,并且显然不包括枚举,所以我只是采取了简单的方法)。 Writer目前只实现了Write,还没用过。

这是 FileReader 代码:

    #include "pch.h"
    #include "FileAccess.h"  // FileReader and FileWriter

    using namespace Concurrency;
    using namespace Windows::Security::Cryptography;
    using namespace CFileAccess;

    FileReader::FileReader(StorageFile^ archive)
    
        if (nullptr != archive)
        
            create_task(archive->OpenReadAsync()).then([this](IRandomAccessStreamWithContentType^ archiveStream)
            
                if (nullptr != archiveStream)
                
                    _readStream = archiveStream;
                
            ).wait();
        
     // end of constructor

    int32 FileReader::Read(WriteOnlyArray<byte>^ fileData)
    
        int32 bytesRead = 0;

        if ((nullptr != _readStream) && (fileData->Length > 0))
        
            try
            
                auto inputStreamReader = ref new DataReader(_readStream);
                create_task(inputStreamReader->LoadAsync(fileData->Length)).then([&](task<unsigned int> dataRead)
                
                    try
                    
                        bytesRead = dataRead.get();
                        if (bytesRead)
                        
                            inputStreamReader->ReadBytes(fileData);
                        
                    
                    catch (Exception^ e)
                    
                        bytesRead = -1;
                    

                    inputStreamReader->DetachStream();
                ).wait();
            
            catch (Exception^ e)
            
                bytesRead = -1;
            
        

        return (bytesRead);
     // end of method Read()

    int64 FileReader::Tell(void)
    
        int64 ret = -1;

        if (nullptr != _readStream)
        
            ret = _readStream->Position;
        

        return (ret);
     // end of method Tell()

    int64 FileReader::SeekFromBeginning(uint64 offset)
    
        int64 ret = -1;

        if ((nullptr != _readStream) && (offset < _readStream->Size))
        
            _readStream->Seek(offset);
            ret = 0;
        

        return (ret);
     // end of method SeekFromBeginning()

    int64 FileReader::SeekFromCurrent(uint64 offset)
    
        int64 ret = -1;

        if ((nullptr != _readStream) && ((_readStream->Position + offset) < _readStream->Size))
        
            _readStream->Seek(_readStream->Position + offset);
            ret = 0;
        

        return (ret);
     // end of method SeekFromCurrent()

    int64 FileReader::SeekFromEnd(uint64 offset)
    
        int64 ret = -1;

        if ((nullptr != _readStream) && ((_readStream->Size - offset) >= 0))
        
            _readStream->Seek(_readStream->Size - offset);
            ret = 0;
        

        return (ret);
     // end of method SeekFromEnd()

iowinrt 位于 MiniZip 和 FileReader(和 FileWriter)之间。在这里给出所有内容太长了,但这应该足以重建其余部分,因为它与不同的函数名称基本相同,加上一堆很明显的 fill_winRT_filefuncxxx():

    #include "zlib.h"
    #include "ioapi.h"
    #include "iowinrt.h"
    #include "FileAccess.h"

    using namespace Windows::Security::Cryptography;
    using namespace Platform;
    using namespace CFileAccess;

    static FileReader^ g_fileReader = nullptr;
    static FileWriter^ g_fileWriter = nullptr;
    static StorageFile^ g_storageFile = nullptr;

    [...]

    static voidpf winRT_translate_open_mode(int mode)
    
        if (nullptr != g_storageFile)
        
            if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
            
                g_fileWriter = nullptr;
                g_fileReader = ref new FileReader(g_storageFile);
            
            else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
            
                g_fileReader = nullptr;
                g_fileWriter = ref new FileWriter(g_storageFile);
            
            else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
            
                g_fileReader = nullptr;
                g_fileWriter = ref new FileWriter(g_storageFile);
            
        
        return (nullptr != g_fileReader ? reinterpret_cast<voidpf>(g_fileReader) : reinterpret_cast<voidpf>(g_fileWriter));
    


    voidpf ZCALLBACK winRT_open64_file_func (voidpf opaque,const void* storageFile,int mode)
    
        g_storageFile = reinterpret_cast<StorageFile^>(const_cast<void*>(storageFile));
        return (winRT_translate_open_mode(mode));
    

    [...]

    Long ZCALLBACK winRT_read_file_func (voidpf opaque, voidpf stream, void* buf,uLong size)
    
        uLong bytesRead = 0;
        if (nullptr != g_fileReader)
        
            auto fileData = ref new Platform::Array<byte>(size);
            bytesRead = g_fileReader->Read(fileData);
            memcpy(buf, fileData->Data, fileData->Length);
        
        return (bytesRead);
    


    uLong ZCALLBACK winRT_write_file_func (voidpf opaque,voidpf stream,const void* buf,uLong size)
    
        uLong bytesWritten = 0;
        if (nullptr != g_fileWriter)
        
            auto bytes = ref new Array<uint8>(reinterpret_cast<uint8*>(const_cast<void*>(buf)), size);
            IBuffer ^writeBuffer = CryptographicBuffer::CreateFromByteArray(bytes);
            bytesWritten = g_fileWriter->Write(writeBuffer);
        
        return (bytesWritten);
    

    long ZCALLBACK winRT_tell_file_func (voidpf opaque,voidpf stream)
    
        long long ret = 0;
        if (nullptr != g_fileReader)
        
            ret = g_fileReader->Tell();
        
        return (static_cast<long>(ret));
    

    ZPOS64_T ZCALLBACK winRT_tell64_file_func (voidpf opaque, voidpf stream)
    
        ZPOS64_T ret = 0;
        if (nullptr != g_fileReader)
        
            ret = g_fileReader->Tell();
        
        return (ret);
    

    [...]

    long ZCALLBACK winRT_seek64_file_func (voidpf opaque, voidpf stream,ZPOS64_T offset,int origin)
    
        long long ret = -1;
        if (nullptr != g_fileReader)
        
            switch (origin)
            
            case ZLIB_FILEFUNC_SEEK_CUR :
                ret = g_fileReader->SeekFromCurrent(offset);
                break;
            case ZLIB_FILEFUNC_SEEK_END :
                ret = g_fileReader->SeekFromEnd(offset);
                break;
            case ZLIB_FILEFUNC_SEEK_SET :
                ret = g_fileReader->SeekFromBeginning(offset);
                break;
            default:
                // should never happen!
                ret = -1;
                break;
            
        
        return (static_cast<long>(ret));
    

    int ZCALLBACK winRT_close_file_func (voidpf opaque, voidpf stream)
    
        g_fileWriter = nullptr;
        g_fileReader = nullptr;
        return (0);
    

    int ZCALLBACK winRT_error_file_func (voidpf opaque,voidpf stream)
    
        /// @todo Get errors from FileAccess
        return (0);
    

这足以让 MiniZip 运行(至少对于阅读而言),但您必须注意调用 MiniZip 函数的方式——因为 Metro 完全是关于异步的,并且阻塞 UI 线程最终会出现异常,您必须将访问权限包装在任务:

    FileOpenPicker^ openPicker = ref new FileOpenPicker();
    openPicker->ViewMode = PickerViewMode::List;
    openPicker->SuggestedStartLocation = PickerLocationId::ComputerFolder;
    openPicker->FileTypeFilter->Append(".zip");
    task<IVectorView<StorageFile^>^>(openPicker->PickMultipleFilesAsync()).then([this](IVectorView<StorageFile^>^ files)
    
        if (files->Size > 0)
        
            std::for_each(begin(files), end(files), [this](StorageFile ^file)
               // open selected zip archives
                create_task([this, file]()
                
                    OpenArchive(file);
                    [...]
                );
            );
        
        else
        
            rootPage->NotifyUserBackgroundThread("No files were returned.", NotifyType::ErrorMessage);
        
    );

    [...]

    bool OpenArchive(StorageFile^ archive)
    
        bool isArchiveOpened = false;

        if (nullptr != archive)
         // open ZIP archive
            zlib_filefunc64_def ffunc;
            fill_winRT_filefunc64(&ffunc); 

            unzFile archiveObject = NULL;
            create_task([this, &ffunc, archive]()
            
                archiveObject = unzOpen2_64(reinterpret_cast<const void*>(archive), &ffunc);
            ).wait();

            if (NULL != archiveObject)
            
                [...]

【讨论】:

谢谢。你是第一个鼓励我敢于将现有的 *nix OSS 项目 zlib 转换为 Windows 应用商店的人。我扩展了你的想法,终于可以compile zlibstat.lib for Windows Store x86, x64 and ARM archtectures。如果您愿意,我们可以逐步完善转换过程。

以上是关于在 Windows 8 上解压缩存档的主要内容,如果未能解决你的问题,请参考以下文章

压缩后如何立即使用 Phar 解压缩存档?

text Power Shell:在Windows 10上解压缩

在 Cordova 中解压缩多部分存档

Objective-C 奇怪的解压缩行为导致文件损坏

在 react-native 上解压缩文件的任何方式

解压缩 .gz 文件并将它们存储在 .tar.gz 存档中