tags:

views:

3085

answers:

7

I am looking for a way to create a ZIP file from a folder in Windows C/C++ APIs. I can find the way to do this in VBScript using the Shell32.Application CopyHere method, and I found a tutorial explaining how to do it in C# also, but nothing for the C API (C++ is fine too, project already uses MFC).

I'd be really grateful if anyone can share some sample C code that can successfully create a zip file on Windows XP/2003. Failing that, if someone can find solid docs or a tutorial that would be great, since MSDN searches don't turn up much. I'm really hoping to avoid having to ship a third-party lib for this, because the functionality is obviously there, I just can't figure out how to access it. Google searches turn up nothing useful, just tantalizing bits and pieces of information. Here's hoping someone in the community has sorted this out and can share it for posterity!

+1  A: 

I do not think that MFC or the Windows standard C/C++ APIs provide an interface to the built in zip functionality.

Sparr
+3  A: 

There is sample code to do that here

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("\x80\x75\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);
Lou Franco
I did find that sample code earlier, but it doesn't actually appear to work as is. I did eventually get it to compile and run after some tweaking and it sort of works, provided the ZIP file already exists. If no one else comes up with a more complete answer I'll accept this one.
Jay
Create the blank zip with:FILE* f = fopen("path", "wb");fwrite("\x80\x75\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);
Simon Buchan
The code provided to create a blank zip file does not create a valid file (it's unable to be opened by any zip software including winzip, Windows built-in ZIP, BOMArchiver on OS X, or unzip on the command line.
Jay
Martin Beckett
A: 

You could always statically link to the freeware zip library if you don't want to ship another library...

EmmEff
+2  A: 

A quick Google search came up with this site: http://www.example-code.com/vcpp/vcUnzip.asp which has a very short example to unzip a file using a downloadable library. There are plenty of other libraries available. Another example is availaible on Code Project entitled Zip and Unzip in the MFC way which has an entire gui example. If you want to do it with .NET then there is always the classes under System.Compression.

There is also the 7-Zip libarary http://www.7-zip.org/sdk.html. This includes source for several languages, and examples.

Vincent McNabb
+4  A: 

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

Sergey Kornilov
+4  A: 

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)
Jay
A: 

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.