Just Let It Flow

October 17, 2012

Dropping Like Files – Zipping Without Libraries

Filed under: Code,Windows — adeyblue @ 6:46 pm

Contents:

Introduction

We haven’t been living under rocks. We know that since Windows XP, it’s had zip file extraction and creation. what it doesn’t have is a defined API so us normals can leverage it programmatically. But we have ways and means…

Think about it, the usual way you interact with zip files is through the shell. You highlight a bunch a files and “Send To” a Compressed Folder or drag them into an existing folder and its done. There’s obviously some code behind that’s actually doing those things, and since you can do from ‘Open File’ dialogs and the like, it can’t be code within the Explorer executable.

You can look through all the dll exports you want, but you’ll only find that MSDN isn’t sandbagging here and there are no real defined functions to create zips. What you will find in shell32 and friends are real functions and interfaces to duplicate the Shell’s methods of dragging, dropping and sending to, so that seems a good lead to follow.

Creating the Zip File

There’s no point doing the equivalent of the highlighting, if we’ve nowhere to equivalently drop it on. The zip file can be created in any old way, all that matters is that exists. The method below uses the Shell interfaces to create it, both because it doesn’t seem to be too well documented how to do it that way, and because we need to use them later anyway.

Several methods are available, but since zip files have been available since XP we’ll go with the most compatible.

IShellFolder* pDesktopFolder = NULL;
SHGetDesktopFolder(&pDesktopFolder);

All interactions, using convenience functions or not, start at the desktop folder which is the root of the shell’s namespace. From there, you can navigate to other folders with the BindToObject member function. Fine and dandy, except that the function takes something called an Item-Id-List and not a human understandable path. Time to call in the big gun, namely ParseDisplayName.

pDesktopFolder->ParseDisplayName(
    NULL, // HWND - we have no window
    NULL, // IBindCtx* - we have no specific parsing options to set
    pDirectoryOfZip, // LPWSTR - The folder that will contain our file
    NULL, // ULONG* - the number of characters in the folder name that were used in parsing
    &pPidl, // PIDLIST_RELATIVE* - the equivalent of pDirectoryOfZip in shell talk
    NULL // ULONG* - attributes of the object to query, we don't want to
);

We now have the address of the folder, but we’re still only at the sorting office. Let’s go find the house, our SatNav is the BindToObject function.

IShellFolder* pZipFileParentFolder = NULL;
pDesktopFolder->BindToObject(
    pPidl, // PIDLIST_RELATIVE - the address of the thing to bind to
    NULL, // IBindCtx* - specific options for binding, we have none
    IID_PPV_ARGS(&pZipFileParentFolder) // REFIID & ppv - the object interface we want, and the pointer to it
);

That done, pDesktopFolder can be Release-d and pPidl can be freed since we’ve finished with them. The next step is to actually create the file on the disk, for this we need to query the IShellFolder for its storage interface:

IStorage* pZipFileParentFolderStorage = NULL;
pZipFileFolder->QueryInterface(IID_PPV_ARGS(&pZipFileParentFolderStorage));

In shell folder parlance, folder objects are represented by IStorage interfaces and the files within them are treated as IStream-s within that storage[1]. We could now enumerate the folder contents with pStorage->EnumElements(), or as is our want, finally and actually create the zip file with CreateStream()

pZipFileParentFolderStorage->CreateStream(
    pZipFileName, // the name of the stream (file) to create
    STGM_CREATE, // flags controlling the creation of the stream, create is the only thing we need
    0, 0, // next two params are reserved
    &pZipStream // the stream represeting the folder
);

If that succeeds, we don’t need to do anything with pZipStream, just creating it is enough to put the zip file on the hard drive. It’s now ready to have stuff (pseudo) dropped on it but for that we need another interface, one that allows drag and drop simulation, IDropTarget.

Creating the IDropTarget

There’s nothing really new to what we did above here. The only obstacle in our way to our goal of GetUIObjectOf() is that it also takes a ItemIdList and not a file name, but we now know how to do that.

LPITEMIDLIST pZipPidl = NULL;
pZipFileFolder->ParseDisplayName(
    NULL,
    NULL,
    pZipFileName,
    NULL,
    &pZipPidl,
    NULL
);
IDropTarget* pDropTarget = NULL;
pZipFileFolder->GetUIObjectOf(
    NULL, // HWND - we still don't have a window
    1, // ULONG - number of itemidlists in the next parameter
    &pZipPidl, // LPITEMIDLIST* - array of itemidlists to get the object for
    IID_IDropTarget, // REFIID - id of the interface we want
    NULL, // UINT* - reserved parameter
    reinterpret_cast<void**>(&pDropTarget) // void** - array of returned interfaces, we only have one
);

And that’s it, we now have a drop target interface ready to simulate the dragging and dropping [2]. The only things we need now, are the files to compress.

Doing the Zipping

IDropTarget has four methods. The two most interesting ones DragEnter and Drop, take something called an IDataObject that encapsulates the thing being dropped or dragged. If you looked at the MSDN page for GetUIObjectOf, you’ll have seen that apart from telling you to do something you can’t [3], it can retrieve these IDataObject interfaces. Well, this seems simple:

IDataObject* pFileData = NULL;
// pseudo function, its content isn't important
GetIDataObjectForFile(L"C:\\myfile.ext", &pFileData);
DWORD effect = DROPEFFECT_COPY;
POINTL pt = {0};
pDropTarget->DragEnter(
    pFileData, // IDataObject* - the thing(s) we're supposedly dragging
    MK_LBUTTON, // DWORD - Mouse and key flags, we pretend we're holding the left mouse button
    pt, // POINTL - mouse coords of the drag, pretend the we've dragged the object at {0, 0}
    &effect // DWORD* - the type of drag to effect, we want to copy the object into it
);
effect &= DROPEFFECT_COPY; // mask off any flags offered we don't understand or want
pDropTarget->Drop(
    pFileData, // all these parameters are the same as for DragEnter
    MK_LBUTTON,
    pt,
    &effect
};

Zipping fails miserably

Gee thanks Windows, we know it’s empty that’s why we’re trying to put something in it. As is usual, the generic failure message displayed to users is horribly lacking in useful info for us developers. Fortunately, after Ok-ing the message box [4], the return value of Drop gives us a bit more of an idea of the problem. Here, it’s 0x80040064, DV_E_FORMATETC, or “Invalid FORMATETC structure”. Well, I did say a bit. In essence it means the format of the data in the IDataObject is incompatble with what’s expected by the drop target.

IDataObject’s allow enumeration of their formats so we can see what we’re trying to send:

Format c07d (Shell IDList Array, CFSTR_SHELLIDLIST)
Format c0ad (Preferred DropEffect, CFSTR_PREFERREDDROPEFFECT)

IDropTarget’s don’t though, so let’s dive into its disassembly.

mov     eax, [ebp+pDataObject]
....
push    0Fh
pop     ecx
mov     word ptr [ebp+var_18], cx; set this word value to 0xf (15)
...
lea     edx, [ebp+var_18]; Get ptr to something. If it's a struct, that 15 value is the first member
push    edx ; send it as second argument, first in prototype, so it is a struct. A FORMATETC struct
...
mov     ecx, [eax]
push    eax ; send the this ptr as first stack argument, not counted in prototype
call    dword ptr [ecx+0Ch] ; call fourth function in IDataObject vtable, that's GetData(FORMATETC*, STGMEDIUM*)

The zip file’s drop target looks for data in a standard clipboard format (less than CF_MAX), while the only ones being offered are registered formats (greater than 0xc000), hence the error. Format 15 is CF_HDROP, the same format as the WM_DROPFILES message provides, except in this case the HDROP handle is wrapped in the data object.

The format of data represented by a HDROP handle is actually documented on MSDN so all we need to do is wrap that up in a DataObject facade and send that to DragEnter() and Drop().

The ZipFileDataObject

If we’re creating our own IDataObject, that’s nine functions that need implementing above and beyond IUnknown. Or is it. Our object only has to be read from, and only supports the one format type, that cuts the number of functions to implement to four and two of those are almost identical.

Other than the interface functions; a constructor, destructor and a method to add files are also needed but all in all, the facade barely covers 70 functional lines.

The constructor sets up an empty HDROP:

// members
HGLOBAL hMem;
size_t curSize;
LONG refCount;
 
ZipFileDataObject()
{
    // keep track of our big our allocation is
    // the two WCHARs are the double-null termination
    curSize = sizeof(DROPFILES) + (2 * sizeof(WCHAR));
    hMem = GlobalAlloc(GHND, curSize);
    if(hMem)
    {
        // fill in the bits we need
        DROPFILES* pFiles = static_cast<DROPFILES*>(GlobalLock(hMem));
        // unicode filenames
        pFiles->fWide = TRUE;
        // offset of filename list, straight after the structure
        pFiles->pFiles = sizeof(DROPFILES);
        GlobalUnlock(hMem);
    }
    refCount = 1;
}

The destructor frees this memory, and the IUnknown functions are as bog standard as they always are. The most difficult function to create is the one to add the filenames to the HDROP, and that’s not very hard at all:

void AddFileToObject(LPCWSTR fileName)
{
    size_t fileNameBytes = ((wcslen(fileName) + 1) * sizeof(WCHAR));
    size_t newSize = curSize + fileNameBytes;
    HGLOBAL hNewMem = GlobalReAlloc(hMem, newSize, GMEM_ZEROINIT);
    if(hNewMem)
    {
        hMem = hNewMem;
        // get a pointer to the memory
        char* pDataPos = static_cast<char*>(GlobalLock(hMem));
        // position it at the end of the current file name list
        pDataPos += (curSize - (2 * sizeof(WCHAR)));
        // and copy in this filename
        // we want the NULL to be copied too, since that separates the names in the list
        memcpy(pDataPos, fileName, fileNameBytes);
        GlobalUnlock(hNewMem);
        curSize = newSize;
    }
}

IDataObject::GetData and IDataObject::GetDataHere are the nearly identical functions. The only difference been that in the former, we have to allocate the out buffer, in the latter it is already supplied. It makes sense then to have a helper function do the same heavy lifting:

STDMETHODIMP GetData(FORMATETC* pEtc, STGMEDIUM* pStg)
{
    // check for valid pointers
    if(!(pEtc && pStg))
    {
        return E_POINTER;
    }
    // check the request is for something we can supply
    if(pEtc->cfFormat != CF_HDROP || (pEtc->tymed & TYMED_HGLOBAL) == 0)
    {
        return DV_E_FORMATETC;
    }
    // then fill in the data
    return FillInStgMedium(pStg, TRUE);
}
 
STDMETHODIMP GetDataHere(FORMATETC* pEtc, STGMEDIUM* pStg)
{
    if(!(pEtc && pStg))
    {
        return E_POINTER;
    }
    // since the buffer is supplied here, we do some additional checks
    if((pEtc->cfFormat != CF_HDROP) || 
      ((pEtc->tymed & TYMED_HGLOBAL) == 0) ||
       (pEtc->lindex != -1) || // check the caller wants all the data
       (pEtc->dwAspect != DVASPECT_CONTENT) // and the actual data at that
    )
    {
        return DV_E_FORMATETC;
    }
    return FillInStgMedium(pStg, FALSE);
}
 
HRESULT FillInStgMedium(STGMEDIUM* pStg, BOOL shouldAlloc)
{
    HGLOBAL hOutMem = NULL;
    if(shouldAlloc)
    {
        // allocate a big enough buffer
        if(!(hOutMem = GlobalAlloc(GHND, curSize)))
        {
            return E_OUTOFMEMORY;
        }
        // and fill in the buffer description of what type it is
        pStg->tymed = TYMED_HGLOBAL;
        pStg->pUnkForRelease = NULL;
        pStg->hGlobal = hOutMem;
    }
    else
    {
        // otherwise, check there was a passed in buffer and its big enough
        // for our DROPFILES and trailing filenames
        if(!(pStg->hGlobal && (GlobalSize(pStg->hGlobal) >= curSize)))
        {
            return STG_E_MEDIUMFULL;
        }
        hOutMem = pStg->hGlobal;
    }
    // OK, we're good, copy over all the data
    PVOID pOutData = GlobalLock(hOutMem);
    PVOID pOurData = GlobalLock(hMem);
    memcpy(pOutData, pOurData, curSize);
    GlobalUnlock(hOutMem);
    GlobalUnlock(hMem);
    return S_OK;
}

The two other functions are trivial to implement and just check what we can deliver

STDMETHODIMP QueryGetData(FORMATETC* pEtc)
{
    // again check the pointer
    if(!pEtc)
    {
        return E_POINTER;
    }
    // and if it conforms to our ideal
    if((pEtc->cfFormat != CF_HDROP) || 
      ((pEtc->tymed & TYMED_HGLOBAL) == 0) ||
       (pEtc->lindex != -1) ||
       (pEtc->dwAspect != DVASPECT_CONTENT)
    )
    {
        // fail if not
        return DV_E_FORMATETC;
    }
    // succeed if so
    return S_OK;
}
 
STDMETHODIMP GetCanonicalFormatEtc(FORMATETC* pInEtc, FORMATETC* pOutEtc)
{
    if(!pInEtc)
    {
        return E_POINTER;
    }
    if(pInEtc->cfFormat != CF_HDROP)
    {
        return DV_E_FORMATETC;
    }
    // Eschew filling in pOutEtc by using the
    // "It would be the same as the input one" return value
    return DATA_S_SAMEFORMATETC;
}

Finishing Up

All that’s left is to put it all together, add some file names and drop our new data object. Bing-oh no. That would be it, except for one thing. The zip folder’s DropTarget creates a new thread to do the actual zipping, unfortunately it uses the SHCreateThread function to do so. Unfortunate because that function returns neither the thread handle nor its id. Of course, with no way of being able to identify it, we, nor the zipping code can know when it has finished its work. When called from Explorer it’s no problem since that is always running, but if our program exits while the thread is in progress we could have anything from missing files to a completely corrupt zip.

There’s an easy workaround for this problem, in the shape of change notifications. Set one up for size changes, and when you stop receiving them for the zip file we can be confident the process has finished. That finally gives us a fully workable program that looks something like this:

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shlwapi.h>
#include <shellapi.h>
#include <shlobj.h>
#include <ole2.h>
#include <cstdio>
 
class ZipFileDataObject : public IDataObject
{
    HGLOBAL hMem;
    size_t curSize;
    LONG refCount;
 
public:
    ZipFileDataObject()
    {
        curSize = sizeof(DROPFILES) + (2 * sizeof(WCHAR));
        hMem = GlobalAlloc(GHND, curSize);
        if(hMem)
        {
            DROPFILES* pFiles = static_cast<DROPFILES*>(GlobalLock(hMem));
            pFiles->fWide = TRUE;
            pFiles->pFiles = sizeof(DROPFILES);
            GlobalUnlock(hMem);
        }
        refCount = 1;
    }
 
    ~ZipFileDataObject()
    {
        GlobalFree(hMem);
    }
 
    void AddFileToObject(LPCWSTR fileName)
    {
        size_t fileNameBytes = ((wcslen(fileName) + 1) * sizeof(WCHAR));
        size_t newSize = curSize + fileNameBytes;
        HGLOBAL hNewMem = GlobalReAlloc(hMem, newSize, GMEM_ZEROINIT);
        if(hNewMem)
        {
            hMem = hNewMem;
            char* pDataPos = static_cast<char*>(GlobalLock(hMem));
            // the last two bytes are the terminating double null
            pDataPos += (curSize - (2 * sizeof(WCHAR)));
            // and copy in this filename
            // we want the NULL to be copied too, since that separates the names in the list
            memcpy(pDataPos, fileName, fileNameBytes);
            GlobalUnlock(hNewMem);
            curSize = newSize;
        }
    }
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
    {
        if(!ppv) return E_POINTER;
        *ppv = NULL;
        HRESULT hr = S_OK;
        if(iid == IID_IUnknown || iid == IID_IDataObject)
        {
            *ppv = this;
            AddRef();
        }
        else hr = E_NOINTERFACE;
        return hr;
    }
 
    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&refCount);
    }
 
    STDMETHODIMP_(ULONG) Release()
    {
        LONG ret = InterlockedDecrement(&refCount);
        if(ret == 0)
        {
            delete this;
        }
        return ret;
    }
 
    HRESULT FillInStgMedium(STGMEDIUM* pStg, BOOL shouldAlloc)
    {
        HGLOBAL hOutMem = NULL;
        if(shouldAlloc)
        {
            if(!(hOutMem = GlobalAlloc(GHND, curSize)))
            {
                return E_OUTOFMEMORY;
            }
            pStg->tymed = TYMED_HGLOBAL;
            pStg->pUnkForRelease = NULL;
            pStg->hGlobal = hOutMem;
        }
        else
        {
            if(!(pStg->hGlobal && (GlobalSize(pStg->hGlobal) >= curSize)))
            {
                return STG_E_MEDIUMFULL;
            }
            hOutMem = pStg->hGlobal;
        }
        PVOID pOutData = GlobalLock(hOutMem);
        PVOID pOurData = GlobalLock(hMem);
        memcpy(pOutData, pOurData, curSize);
        GlobalUnlock(hOutMem);
        GlobalUnlock(hMem);
        return S_OK;
    }
 
    // IDataObject
    STDMETHODIMP GetData(FORMATETC* pEtc, STGMEDIUM* pStg)
    {
        if(!(pEtc && pStg))
        {
            return E_POINTER;
        }
        if(pEtc->cfFormat != CF_HDROP || (pEtc->tymed & TYMED_HGLOBAL) == 0)
        {
            return DV_E_FORMATETC;
        }
        return FillInStgMedium(pStg, TRUE);
    }
 
    STDMETHODIMP GetDataHere(FORMATETC* pEtc, STGMEDIUM* pStg)
    {
        if(!(pEtc && pStg))
        {
            return E_POINTER;
        }
        if((pEtc->cfFormat != CF_HDROP) || 
          ((pEtc->tymed & TYMED_HGLOBAL) == 0) ||
           (pEtc->lindex != -1) ||
           (pEtc->dwAspect != DVASPECT_CONTENT)
        )
        {
            return DV_E_FORMATETC;
        }
        return FillInStgMedium(pStg, FALSE);
    }
 
    STDMETHODIMP QueryGetData(FORMATETC* pEtc)
    {
        if(!pEtc)
        {
            return E_POINTER;
        }
        if((pEtc->cfFormat != CF_HDROP) || 
          ((pEtc->tymed & TYMED_HGLOBAL) == 0) ||
           (pEtc->lindex != -1) ||
           (pEtc->dwAspect != DVASPECT_CONTENT)
        )
        {
            return DV_E_FORMATETC;
        }
        return S_OK;
    }
 
    STDMETHODIMP GetCanonicalFormatEtc(FORMATETC* pInEtc, FORMATETC* pOutEtc)
    {
        if(!pInEtc)
        {
            return E_POINTER;
        }
        if(pInEtc->cfFormat != CF_HDROP)
        {
            return DV_E_FORMATETC;
        }
        return DATA_S_SAMEFORMATETC;
    }
 
    STDMETHODIMP SetData(FORMATETC*, STGMEDIUM*, BOOL)
    {
        return E_NOTIMPL;
    }
 
    STDMETHODIMP EnumFormatEtc(DWORD, IEnumFORMATETC**)
    {
        return E_NOTIMPL;
    }
 
    STDMETHODIMP DAdvise(FORMATETC*, DWORD, IAdviseSink*, DWORD*)
    {
        return E_NOTIMPL;
    }
 
    STDMETHODIMP DUnadvise(DWORD)
    {
        return E_NOTIMPL;
    }
 
    STDMETHODIMP EnumDAdvise(IEnumSTATDATA**)
    {
        return E_NOTIMPL;
    }
};
 
#define FAIL_ON_ERR(exp) \
    hr = exp; \
    if(FAILED(hr)) \
    { \
        printf("%s failed with error %#x\n", #exp, hr); \
        return NULL; \
    }
 
IDropTarget* CreateZipTarget(LPCWSTR pFolderName, LPCWSTR pZipFileName)
{
    HRESULT hr = S_OK;
    IShellFolder* pFolder = NULL;
    IStorage* pZipFileParentFolderStorage = NULL;
    IDropTarget* pTarget = NULL;
    IStream* pZipStream = NULL;
    IShellFolder* pZipFileFolder = NULL;
    LPITEMIDLIST pFolderPidl = NULL, pFilePidl = NULL;
    __try
    {
        FAIL_ON_ERR(SHGetDesktopFolder(&pFolder));
        FAIL_ON_ERR(pFolder->ParseDisplayName(NULL, NULL, const_cast<PWSTR>(pFolderName), NULL, &pFolderPidl, NULL));
        FAIL_ON_ERR(pFolder->BindToObject(pFolderPidl, NULL, IID_PPV_ARGS(&pZipFileFolder)));
        FAIL_ON_ERR(pZipFileFolder->QueryInterface(IID_PPV_ARGS(&pZipFileParentFolderStorage)));
        FAIL_ON_ERR(pZipFileParentFolderStorage->CreateStream(pZipFileName, STGM_FAILIFTHERE | STGM_CREATE, 0, 0, &pZipStream));
        FAIL_ON_ERR(pZipFileFolder->ParseDisplayName(NULL, NULL, const_cast<PWSTR>(pZipFileName), NULL, &pFilePidl, NULL));
        FAIL_ON_ERR(pZipFileFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST*)&pFilePidl, IID_IDropTarget, 0, (PVOID*)&pTarget));
        return pTarget;
    }
    __finally
    {
        if(pZipFileFolder) pZipFileFolder->Release();
        if(pFolder) pFolder->Release();
        if(pZipFileParentFolderStorage) pZipFileParentFolderStorage->Release();
        if(pZipStream) pZipStream->Release();
        CoTaskMemFree(pFilePidl);
        CoTaskMemFree(pFolderPidl);
    }
}
 
void GetDirAndEventHandles(LPCWSTR pDirName, HANDLE* phDir, HANDLE * phEvent)
{
    *phDir = CreateFile(
        pDirName,
        FILE_LIST_DIRECTORY,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
        NULL
    );
    *phEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
}
 
int __cdecl wmain(int argc, wchar_t** argv)
{
    if(argc <= 2)
    {
        return puts("Usage: ZipFiles.exe OutputDir file1, file2, ...");
    }
    OleInitialize(NULL);
    WCHAR zipFileFolder[MAX_PATH];
    LPCWSTR pAbsoluteZipFolder = argv[1];
    LPCWSTR pZipFileName = L"files.zip";
    if(PathIsRelative(argv[1]))
    {
        GetFullPathName(argv[1], ARRAYSIZE(zipFileFolder), zipFileFolder,  NULL);
        pAbsoluteZipFolder = zipFileFolder;
    }
 
    IDropTarget* pDropTarget = CreateZipTarget(pAbsoluteZipFolder, pZipFileName);
    if(pDropTarget)
    {
        ZipFileDataObject* pZfdo = new ZipFileDataObject();
        for(int i = 2; i < argc; ++i)
        {
             pZfdo->AddFileToObject(argv[i]);
        }
 
        POINTL pt = {0};
        DWORD effect = DROPEFFECT_COPY;
        HRESULT hr = pDropTarget->DragEnter(pZfdo, MK_LBUTTON, pt, &effect);
        if(SUCCEEDED(hr))
        {
            // grab handles to the directory containing the zip, and a bog standard event
            HANDLE hDir, hEvent;
            GetDirAndEventHandles(pAbsoluteZipFolder, &hDir, &hEvent);
            // setup for the notifications
            BYTE notifyBuffer[sizeof(FILE_NOTIFY_INFORMATION) + (MAX_PATH * sizeof(WCHAR))] = {0};
            OVERLAPPED ol = {0};
            ol.hEvent = hEvent;
            // and start the monitoring
            ReadDirectoryChangesW(
                hDir,
                notifyBuffer,
                sizeof(notifyBuffer),
                FALSE,
                FILE_NOTIFY_CHANGE_SIZE,
                NULL,
                &ol,
                NULL
            );
            // then do the drop as normal
            effect &= DROPEFFECT_COPY;
            hr = pDropTarget->Drop(pZfdo, MK_LBUTTON, pt, &effect);
            pDropTarget->Release();
            pZfdo->Release();
            if(FAILED(hr))
            {
                printf("Failed to drop files because of error %#x\n", hr);
                CloseHandle(hDir);
                CloseHandle(hEvent);
                OleUninitialize();
                return 1;
            }
 
            // Timeout our zip modification, it should be finished
            // if we don't get any after this period
            DWORD zipLastModified = GetTickCount();
            DWORD zipModificationTimeout = 5000;
            while(((GetTickCount() - zipLastModified) >= zipModificationTimeout) && 
                (WaitForSingleObject(hEvent, zipModificationTimeout) == WAIT_OBJECT_0)
            )
            {
                // we got a notifications, process them
                DWORD numBytes = 0;
                GetOverlappedResult(hDir, &ol, &numBytes, FALSE);
                BYTE* pIter = notifyBuffer, *pEnd = pIter + numBytes;
                FILE_NOTIFY_INFORMATION* pFni = NULL;
                do
                {
                    pFni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(pIter);
                    // if its an update that pertains to our zip, update the modified time
                    if(pFni->Action == FILE_ACTION_MODIFIED && 
                       wcsnicmp(pFni->FileName, pZipFileName, pFni->FileNameLength / sizeof(WCHAR)) == 0
                    )
                    {
                        zipLastModified = GetTickCount();
                    }
                    pIter += pFni->NextEntryOffset;
                }
                // there can be multiple notificatons per call, make sure we catch 'em all
                while((pIter < pEnd) && pFni->NextEntryOffset);
                // anf kick off the next batch
                ReadDirectoryChangesW(
                    hDir,
                    notifyBuffer,
                    sizeof(notifyBuffer),
                    FALSE,
                    FILE_NOTIFY_CHANGE_SIZE,
                    NULL,
                    &ol,
                    NULL
                );
            }
            // if we're here, we've finished
            CloseHandle(hEvent);
            CloseHandle(hDir);
        }
    }
    OleUninitialize();
    return 0;
}

[1]: As zip files contain other files, zipfldr.dll (the zip file provider) lets you create IStorage interfaces on them. You can also BindToObject() on them directly and get an IShellFolder interface representing the zip file and its contents.

[2]: You can create drop targets for any type of file not just zips. What happens when you drag and drop things onto them though, is defined by the filetype. Zip files will compress the files, executable will launch with them as the arguments, etc.

[3]: It tells you to use IID_PPV_ARGS, but the reserved parameter in between the REFIID and void** arguments stops it from expanding properly.

[4]: This is one of the downsides of using the zipping functionality, it assumes it’s running in an application with an always on UI like Explorer. Apart from error message dialogs, it also displays progress bars when zipping takes place.

1 Comment »

  1. You can’t use SHSetInstanceExplorer to help you wait for the thread?

    People have been using the same trick in WSH for a while ( http://superuser.com/a/112094 ) but the waiting problem is even worse there.

    The sad thing is of course that the zip compatibility is crap; http://blogs.msdn.com/b/michkap/archive/2012/01/04/10252916.aspx etc

    Comment by WndSks — October 24, 2012 @ 12:19 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress