Creating a ZIP file on Windows (XP/2003) in C/C++

EDIT: This answer is old, but I cannot delete it because it was accepted. See the next one

https://stackoverflow.com/a/121720/3937

----- ORIGINAL ANSWER -----

There is sample code to do that here

[EDIT: Link is now broken]

http://www.eggheadcafe.com/software/aspnet/31056644/using-shfileoperation-to.aspx

Make sure you read about how to handle monitoring for the thread to complete.

Edit: From the comments, this code only works on existing zip file, but @Simon provided this code to create a blank zip file

FILE* f = fopen("path", "wb");
fwrite("\x50\x4B\x05\x06\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 22, 1, f);
fclose(f);

As noted elsewhere in the comments, this will only work on a already-created Zip file. The content must also not already exist in the zip file, or an error will be displayed. Here is the working sample code I was able to create based on the accepted answer. You need to link to shell32.lib and also kernel32.lib (for CreateToolhelp32Snapshot).

#include <windows.h>
#include <shldisp.h>
#include <tlhelp32.h>
#include <stdio.h>

int main(int argc, TCHAR* argv[])
{
    DWORD strlen = 0;
    char szFrom[] = "C:\\Temp",
         szTo[] = "C:\\Sample.zip";
    HRESULT hResult;
    IShellDispatch *pISD;
    Folder *pToFolder = NULL;
    VARIANT vDir, vFile, vOpt;
    BSTR strptr1, strptr2;

    CoInitialize(NULL);

    hResult = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_INPROC_SERVER, IID_IShellDispatch, (void **)&pISD);

    if  (SUCCEEDED(hResult) && pISD != NULL)
    {
        strlen = MultiByteToWideChar(CP_ACP, 0, szTo, -1, 0, 0);
        strptr1 = SysAllocStringLen(0, strlen);
        MultiByteToWideChar(CP_ACP, 0, szTo, -1, strptr1, strlen);

        VariantInit(&vDir);
        vDir.vt = VT_BSTR;
        vDir.bstrVal = strptr1;
        hResult = pISD->NameSpace(vDir, &pToFolder);

        if  (SUCCEEDED(hResult))
        {
            strlen = MultiByteToWideChar(CP_ACP, 0, szFrom, -1, 0, 0);
            strptr2 = SysAllocStringLen(0, strlen);
            MultiByteToWideChar(CP_ACP, 0, szFrom, -1, strptr2, strlen);

            VariantInit(&vFile);
            vFile.vt = VT_BSTR;
            vFile.bstrVal = strptr2;

            VariantInit(&vOpt);
            vOpt.vt = VT_I4;
            vOpt.lVal = 4;          // Do not display a progress dialog box

            hResult = NULL;
            printf("Copying %s to %s ...\n", szFrom, szTo);
            hResult = pToFolder->CopyHere(vFile, vOpt); //NOTE: this appears to always return S_OK even on error
            /*
             * 1) Enumerate current threads in the process using Thread32First/Thread32Next
             * 2) Start the operation
             * 3) Enumerate the threads again
             * 4) Wait for any new threads using WaitForMultipleObjects
             *
             * Of course, if the operation creates any new threads that don't exit, then you have a problem. 
             */
            if (hResult == S_OK) {
                //NOTE: hard-coded for testing - be sure not to overflow the array if > 5 threads exist
                HANDLE hThrd[5]; 
                HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPALL ,0);  //TH32CS_SNAPMODULE, 0);
                DWORD NUM_THREADS = 0;
                if (h != INVALID_HANDLE_VALUE) {
                    THREADENTRY32 te;
                    te.dwSize = sizeof(te);
                    if (Thread32First(h, &te)) {
                        do {
                            if (te.dwSize >= (FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) ) {
                                //only enumerate threads that are called by this process and not the main thread
                                if((te.th32OwnerProcessID == GetCurrentProcessId()) && (te.th32ThreadID != GetCurrentThreadId()) ){
                                    //printf("Process 0x%04x Thread 0x%04x\n", te.th32OwnerProcessID, te.th32ThreadID);
                                    hThrd[NUM_THREADS] = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
                                    NUM_THREADS++;
                                }
                            }
                            te.dwSize = sizeof(te);
                        } while (Thread32Next(h, &te));
                    }
                    CloseHandle(h);

                    printf("waiting for all threads to exit...\n");
                    //Wait for all threads to exit
                    WaitForMultipleObjects(NUM_THREADS, hThrd , TRUE , INFINITE);

                    //Close All handles
                    for ( DWORD i = 0; i < NUM_THREADS ; i++ ){
                        CloseHandle( hThrd[i] );
                    }
                } //if invalid handle
            } //if CopyHere() hResult is S_OK

            SysFreeString(strptr2);
            pToFolder->Release();
        }

        SysFreeString(strptr1);
        pISD->Release();
    }

    CoUninitialize();

    printf ("Press ENTER to exit\n");
    getchar();
    return 0;

}

I have decided not to go this route despite getting semi-functional code, since after further investigation, it appears the Folder::CopyHere() method does not actually respect the vOptions passed to it, which means you cannot force it to overwrite files or not display error dialogs to the user.

In light of that, I tried the XZip library mentioned by another poster as well. This library functions fine for creating a Zip archive, but note that the ZipAdd() function called with ZIP_FOLDER is not recursive - it merely creates a folder in the archive. In order to recursively zip an archive you will need to use the AddFolderContent() function. For example, to create a C:\Sample.zip and Add the C:\Temp folder to it, use the following:

HZIP newZip = CreateZip("C:\\Sample.zip", NULL, ZIP_FILENAME);
BOOL retval = AddFolderContent(newZip, "C:", "temp");

Important note: the AddFolderContent() function is not functional as included in the XZip library. It will recurse into the directory structure but fails to add any files to the zip archive, due to a bug in the paths passed to ZipAdd(). In order to use this function you'll need to edit the source and change this line:

if (ZipAdd(hZip, RelativePathNewFileFound, RelativePathNewFileFound, 0, ZIP_FILENAME) != ZR_OK)

To the following:

ZRESULT ret;
TCHAR real_path[MAX_PATH] = {0};
_tcscat(real_path, AbsolutePath);
_tcscat(real_path, RelativePathNewFileFound);
if (ZipAdd(hZip, RelativePathNewFileFound, real_path, 0, ZIP_FILENAME) != ZR_OK)

We use XZip for this purpose. It's free, comes as C++ source code and works nicely.

http://www.codeproject.com/KB/cpp/xzipunzip.aspx


The above code to create an empty zip file is broken, as the comments state, but I was able to get it to work. I opened an empty zip in a hex editor, and noted a few differences. Here is my modified example:

FILE* f = fopen("path", "wb"); 
fwrite("\x50\x4B\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 22, 1, f);
fclose(f);

This worked for me. I was able to then open the compressed folder. Not tested with 3rd party apps such as winzip.

Tags:

C++

Winapi

Zip