Unzipping archive on Windows 8
Asked Answered
U

1

8

I have previously used MiniZip (zlib wrapper) to unzip archives. MiniZip cannot be used for Metro applications as it uses deprecated APIs in "iowin32.c" -- CreateFile() and SetFilePointer().

I thought that would be an easy fix and created "iowinrt.c" with CreateFile() and SetFilePointer() replaced with CreateFile2() and SetFilePointerEx(). While this way I obtained a version of MiniZip that uses only approved Win8 APIs, it still turned out to be useless -- I forgot about sandboxing. If I pick a file using FileOpenPicker() and pass its path to my modified MiniZip I still cannot open it -- CreateFile2() will fail with "Access is denied." message.

So it appears that old C API for file access if now mostly useless; it is my understanding that in order to fix this I would need to reimplement my "iowinrt" in C++/CX using the new async file access. Are there any other options? I think I saw somewhere that WinRT does have compress/uncompress functionality but that it only works on individual files, not archives.

Additional requirements it that I need this to work in memory.

For a moment I thought I had a solution via .NET Framework 4.5:

  1. I found this piece of info about how to create .NET classes that can be used from C++/CX: http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/3ff383d0-0c9f-4a30-8987-ff2b23957f01

  2. .NET Framework 4.5 contains ZipArchive and ZipArchiveEntry classes in System.IO.Compression: 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

I thought I could create C# Metro Class Library with WinMD Output type exposing ZipArchive and ZipArchiveEntry then use that in my C++/CX project. However, even if it worked it would not work in-memory; it appears that ZipArchive and ZipArchiveEntry work only with files.

Uella answered 7/6, 2012 at 6:30 Comment(3)
Your approach is correct and straight-forward as far as the minizip library goes. You pass in the path to minizip then have I/O callbacks recreate the StorageFile object internally. Have you looked in Process Monitor and examined the I/O calls and associated errors?Wendy
@Nathan Thanks for your suggestions – haven’t tried that, will give it a go. However, I basically gave up on Win8 C++ for the moment. Until WinRT C++ documentation catches up with C#/JS documentation putting any further effort into WinRT C++ programming is a waste of time. As MS does not consider C++ documentation important (see comments here: social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/…) I think I'll wait for a year or two before giving it another go.Uella
That's too bad man. It looks like you got most of the way there.Wendy
U
5

Got reading from archive working. Explanation and code below but really just a hack at this point, to see if it's possible at all. I just kept modifying things until I got something working; this is just an example of what works and by no means a production quality code (it's not re-entrant for start). There are undoubtedly many things that are bad/unnecessary/wtf so feel free to use comments to help with clean up.

As mentioned previously, it is no longer enough to pass path to the library -- unless file is in one of KnownFolders (documents, home, media, music, pictures, removable or videos) you end up with "access is denied" message. Instead, library must be able to accept StorageFile^, as returned from FileOpenPicker. At least I haven't found any other way to do it, maybe someone knows better?

MiniZip provides Windows filesystem access layer for zlib via iowin32.h/.c. This still works in desktop mode for old-style apps, but does not work for Metro apps as it uses deprecated APIs and relies on paths. To get MiniZip going on Windows 8, a complete rewrite of iowin32 is required.

To get things working again, first thing was to find a way to pass StorageFile^ all the way down to iowinrt (Windows 8 replacement for iowin32). Fortunately, that was not a problem as MiniZip provides two styles of open file functions -- ones that accept pointer to char, and the others accepting pointer to void. Since ^ is still just a pointer, casting StorageFile^ to void* and than back to StorageFile^ works fine.

Now that I was able to pass StorageFile^ to my new iowinrt, the next problem was how to make new async C++ file access API work with Zlib. In order to support very old C compilers, Zlib is written with old K&R style C. VisualStudio compiler will refuse to compile this as C++, it has to be compiled as C, and new iowinrt must be compiled as C++ of course -- keep that in mind when creating your project. Other things to note about VS project is that I did it as Visual C++ Windows Metro style Static Library although DLL should also work but then you must also define macro to export MiniZip API (I haven't tried this, don't know which macro you have to use). I think I also had to set "Consume Windows Runtime Extension" (/ZW), set "Not Using Precompiled Headers" and add _CRT_SECURE_NO_WARNINGS and _CRT_NONSTDC_NO_WARNINGS to Preprocessor Definitions.

As for iowinrt itself, I've split it in two files. One holds two sealed ref classes -- reader and writer objects; they accept StorageFile^. Reader implements Read, Tell, SeekFromBeginning, SeekFromCurrent and SeekFromEnd (the reason for 3 Seek methods is because ref sealed classes have to stick with RT types and that apparently excludes enums so I just took the easy route). Writer implements just Write at the moment, haven't used it yet.

This is FileReader code:

    #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 sits between MiniZip and FileReader (and FileWriter). It's too long to give everything here but this should be sufficient to reconstruct the rest since it's mostly just more of the same with different function names, plus a bunch of fill_winRT_filefuncxxx() which are obvious:

    #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);
    }

This is enough to get MiniZip going (at least for reading) but you have to take care how you call MiniZip functions -- since Metro is all about async and blocking UI thread will end up with exception, you must wrap access in tasks:

    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)
            {
                [...]
Uella answered 14/6, 2012 at 23:44 Comment(1)
And thank you. You are the first person, who encouraged my idea to dare converting existing *nix OSS project, zlib, to Windows Store. I extended your idea and finally able to compile zlibstat.lib for Windows Store x86, x64 and ARM archtectures. If you want, we can walk through the steps to refine the conversion process.Ln

© 2022 - 2024 — McMap. All rights reserved.