在Windows 8上解压归档文件

8
我以前使用 MiniZip(zlib包装器)来解压缩档案。MiniZip 不能用于 Metro 应用程序,因为它在“iowin32.c”中使用了已弃用的API--CreateFile() 和 SetFilePointer()。
我认为这是一个简单的修复,于是创建了 "iowinrt.c",用 CreateFile2() 和 SetFilePointerEx() 替换了 CreateFile() 和 SetFilePointer()。虽然我这样得到了一个仅使用批准的 Win8 API 的 MiniZip 版本,但结果还是没有用 -- 我忘记了沙箱。如果我使用 FileOpenPicker() 选择一个文件并将其路径传递给我修改后的 MiniZip,我仍然无法打开它 -- CreateFile2() 将会失败并显示 “访问被拒绝”的消息。
因此,看起来旧的 C 文件访问 API 现在基本上无用了; 我的理解是,为了修复这个问题,我需要使用新的异步文件访问重新实现我的 "iowinrt",并使用 C++/CX。还有其他选择吗?我想我在某个地方看到过 WinRT 确实具有压缩/解压缩功能,但仅适用于单个文件,而不是存档。
额外的要求是我需要在内存中工作。
有一刻我认为我有一个 .NET Framework 4.5 的解决方案:
  1. 我找到了这篇关于如何从 C++/CX 创建可以用的 .NET 类的信息: http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/3ff383d0-0c9f-4a30-8987-ff2b23957f01
  2. .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对象。你是否查看过进程监视器并检查了I/O调用和相关错误? - Nathan Moinvaziri
@Nathan 感谢您的建议 - 我还没有尝试过,会试一下。然而,我基本上已经放弃了Win8 C++。在WinRT C++文档赶上C#/JS文档之前,再投入更多精力到WinRT C++编程中是浪费时间。由于微软不认为C++文档很重要(请参见此处的评论:http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/04cbe02b-700f-4be5-b6e9-fe98f3f2cd2e/),我想我会等一两年再试一次。 - Galadrius Krunthar
太糟糕了,伙计。看起来你已经完成了大部分工作。 - Nathan Moinvaziri
1个回答

5

已经成功从档案中读取数据。下面会提供解释和代码,但目前只是一种hack的方式,仅仅是为了看看是否可能实现。我一直在修改东西,直到我得到可行的结果;这只是可行的一个示例,绝不是生产质量的代码(起始位置没有再输入)。毫无疑问,有许多不好/不必要/令人困惑的事情,因此请随意使用注释来帮助清理。

如先前提到的那样,仅仅传递路径给库已经不够了——除非文件位于已知文件夹之一(documents、home、media、music、pictures、removable或videos),否则您将得到“拒绝访问”的消息。相反,图书馆必须能够接受StorageFile^,并从FileOpenPicker返回。至少我没有找到其他方法,也许有人知道更好的方法?

MiniZip通过iowin32.h/.c为zlib提供Windows文件系统访问层。虽然它仍然适用于旧式应用程序的桌面模式,但对于Metro应用程序却无法工作,因为它使用了弃用API并依赖路径。为了使MiniZip适用于Windows 8,需要完全重写iowin32。

为了重新让事情运作起来,第一件事就是找到一种方法将StorageFile^一直传递到iowinrt(iowin32的Windows 8替代品)中。幸运的是,MiniZip提供了两种样式的打开文件函数——一种接受char指针,另一种接受void指针。由于^仍然只是一个指针,因此将StorageFile^转换为void*,然后再转换回StorageFile^即可。

既然我已经能够将StorageFile^传递给我的新iowinrt,下一个问题是如何使新的异步C++文件访问API与Zlib配合工作。为了支持非常老的C编译器,Zlib是用旧版K&R风格的C编写的。VisualStudio编译器会拒绝将其编译为C ++,它必须被编译为C,并且新的iowinrt当然必须被编译为C ++——这一点在创建项目时需要记住。有关VS项目的其他注意事项是,尽管DLL也可以工作,但我将其设置为Visual C++ Windows Metro样式静态库,但那时您还必须定义宏以导出MiniZip API(我还没有尝试过,不知道应该使用哪个宏)。我认为我还必须设置“Consume Windows Runtime Extension”(/ZW),设置“Not Using Precompiled Headers”,并将_CRT_SECURE_NO_WARNINGS和_CRT_NONSTDC_NO_WARNINGS添加到Preprocessor Definitions中。

至于iowinrt本身,我将它分成了两个文件。一个包含两个密封的ref类——读取器和写入器对象;它们都接受StorageFile^。读取器实现了Read、Tell、SeekFromBeginning、SeekFromCurrent和SeekFromEnd(3种Seek方法的原因是因为ref sealed类必须坚持RT类型,而这显然排除了枚举,所以我选择了简单的路线)。目前,写入器仅实现了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 Store的人。我扩展了你的想法,最终能够为Windows Store x86、x64和ARM架构编译zlibstat.lib(http://stackoverflow.com/q/13900749/1712065)。如果你愿意,我们可以一起走过完善转换过程的步骤。 - Annie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接