Jump to content
Nytro

Dropping Like Files – Zipping Without Libraries

Recommended Posts

Posted

[h=3]Dropping Like Files – Zipping Without Libraries[/h]adeyblue @ 6:46 pm

Contents:

[h=2]Introduction[/h] 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.

[h=2]Creating the Zip File[/h] 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.

[h=2]Creating the IDropTarget[/h] 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.

[h=2]Doing the Zipping[/h] 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
};

zipfail.png

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 0×80040064, 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().

[h=3]The ZipFileDataObject[/h] 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:

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

[h=2]Finishing Up[/h] 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.

Sursa: Dropping Like Files – Zipping Without Libraries

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...