在Windows(XP/2003)上使用C/C++创建ZIP文件

13

我正在寻找一种使用Windows C/C++ API从文件夹创建ZIP文件的方法。我可以找到使用Shell32.Application CopyHere方法在VBScript中执行此操作的方法,并且我也找到了一个C#的教程,但没有适用于C API的内容(使用MFC的C++也可以)。

如果有人能分享一些能够成功在Windows XP/2003上创建zip文件的样本C代码,我将非常感激。如果没有这样的代码,如果有人能够找到可靠的文档或教程,那就太好了,因为MSDN搜索并没有发现很多结果。我真的希望避免不得不使用第三方库,因为该功能显然已经存在,我只是无法找到如何访问它的方法。谷歌搜索没有找到有用的信息,只有少量零散的信息。希望社区中的某些人已经解决了这个问题并可以分享给后人!


1
如何使用Win32 API在Windows中创建压缩文件夹:http://ixmx.wordpress.com/2009/09/16/how-to-create-a-compress-folder-in-windows-using-win32-api/ - anno
你可以在C或C++中使用Shell32.CopyHere,只需调用必要的win32 API来实例化COM对象。 - Motomotes
7个回答

13

如评论中所述,这仅适用于已创建的Zip文件。内容也不能已经存在于Zip文件中,否则将显示错误。以下是我根据已接受答案创建的工作示例代码。您需要链接shell32.lib和kernel32.lib(用于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;

}

尽管我已经得到了半功能代码,但在进一步调查后,我决定不走这条路,因为似乎Folder::CopyHere()方法并没有实际遵循传递给它的vOptions参数,这意味着您不能强制覆盖文件或不向用户显示错误对话框。

鉴于此,我也试用了另一位发帖者提到的XZip库。该库用于创建Zip存档的功能良好,但请注意,使用ZIP_FOLDER调用的ZipAdd()函数不是递归的 - 它只在存档中创建一个文件夹。为了递归压缩存档,您需要使用AddFolderContent()函数。例如,要创建C:\Sample.zip并将C:\Temp文件夹添加到其中,请使用以下内容:

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

重要提示:AddFolderContent()函数在XZip库中的功能不可用。它将递归进入目录结构,但由于传递给ZipAdd()的路径中存在错误,无法将任何文件添加到zip存档中。为了使用此函数,您需要编辑源代码并更改此行:

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

以下是相关内容:

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)

有没有更聪明的方法来检测仍在工作的shell?例如,使用IShellDispatch.Windows在自定义时间间隔内检查打开的shell窗口?[https://msdn.microsoft.com/en-us//library/windows/desktop/gg537733(v=vs.85).aspx] - Youka
这段代码可以工作,但是如果在此行之前一个线程已经完成,函数WaitForMultipleObjects会一直等待。根据msdn文档=> WaitForMultipleObjects所述,它说“如果其中一个句柄在等待仍然挂起时关闭,则函数的行为未定义。”为了避免这种情况,句柄必须只有SYNCHRONIZE访问权限标志。因此,在OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);中将THREAD_ALL_ACCESS替换为SYNCHRONIZE,就可以正常工作了! - thibsc

7

5

编辑:本答案已过时,但我无法删除它,因为它被接受了。请参见下一个答案。

https://dev59.com/4HVD5IYBdhLWcg3wAWoO#121720

----- 原始答案 -----

这里有示例代码可以实现此功能。

[编辑:链接已失效]

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

确保你阅读如何处理监视线程以完成的相关内容。

编辑:来自评论,此代码仅适用于现有zip文件,但@Simon提供了创建空白zip文件的代码。

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

1
使用以下代码创建空白zip文件: 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
1
提供的代码用于创建空白zip文件,但并未创建有效的文件(无法被任何zip软件打开,包括winzip、Windows内置ZIP、OS X上的BOMArchiver或命令行上的unzip)。 - Jay
1
具体的代码在这个评论中:http://www.eggheadcafe.com/conversation.aspx?messageid=31056680&threadid=31056644 - Martin Beckett
1
链接已经失效了。最好把答案的要点放在这里,而不仅仅是链接。 - Werner Henze
这个十六进制字符串可以在注册表中找到。请参见HKEY_CLASSES_ROOT.zip\CompressedFolder\ShellNew中的数据。 - Vladyslav Litunovsky
显示剩余2条评论

4

上述创建空zip文件的代码有问题,正如注释所述,但我成功地让它工作了。我在十六进制编辑器中打开了一个空的zip文件,并注意到了一些不同之处。以下是我修改后的示例:

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

这对我很有帮助。然后我能够打开压缩文件夹。未测试第三方应用程序,如winzip。


2

1

我认为MFC或Windows标准C/C++ API没有提供与内置zip功能的接口。


0
如果您不想再安装其他的库文件,您可以尝试将免费压缩库静态链接到程序中...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接