2

So I have a very simple job to do: decompress a zip file. Thought I'd find a simple solution withing 5 seconds online but I'm still struggling.

I've obviously read these posts:

But the answers suggest to make use of zlib and libzip or miniz. I'm sure these approaches works just fine. However it seems that it is not straightforward trying to apply this approach in existing VS2013 solution.

Then I came across this simple solution, ref1, ref2, that make use of IShellDispatch object

I rushed to implement it:

bool DecompressZIP(_In_ const wpath& pathFile, _In_ const wpath& pathDstDir)
{
    BSTR source = _bstr_t(pathFile.string().c_str());
    BSTR dest = _bstr_t(pathDstDir.string().c_str());

    HRESULT hResult = S_FALSE;
    IShellDispatch *pIShellDispatch = NULL;
    Folder *pToFolder = NULL;
    VARIANT variantDir, variantFile, variantOpt;

    CoInitialize(NULL);

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

    if (SUCCEEDED(hResult) && NULL != pIShellDispatch)
    {
        VariantInit(&variantDir);
        variantDir.vt = VT_BSTR;
        variantDir.bstrVal = dest;
        hResult = pIShellDispatch->NameSpace(variantDir, &pToFolder);

        if (SUCCEEDED(hResult) && NULL != pToFolder)
        {
            Folder *pFromFolder = NULL;
            VariantInit(&variantFile);
            variantFile.vt = VT_BSTR;
            variantFile.bstrVal = source;
            hResult = pIShellDispatch->NameSpace(variantFile, &pFromFolder);

            if (SUCCEEDED(hResult) && NULL != pFromFolder)
            {
                FolderItems *fi = NULL;
                pFromFolder->Items(&fi);

                VariantInit(&variantOpt);
                variantOpt.vt = VT_I4;
                variantOpt.lVal = FOF_NO_UI;

                VARIANT newV;
                VariantInit(&newV);
                newV.vt = VT_DISPATCH;
                newV.pdispVal = fi;
                hResult = pToFolder->CopyHere(newV, variantOpt);
                Sleep(1000);
                pFromFolder->Release();
                pToFolder->Release();
            }
        }
        pIShellDispatch->Release();
    }

    CoUninitialize();

    return true;
}

BUT IT DOES NOT WORK !

line: hResult = pIShellDispatch->NameSpace(variantFile, &pFromFolder); always result in pFromFolder == NULL

  • hResult is S_FALSE
  • SUCCEEDED(hResult) is true
  • GetLastError is 0

Question

what am I doing wrong?

Community
  • 1
  • 1
idanshmu
  • 5,061
  • 6
  • 46
  • 92
  • 2
    You could start by telling us the `HRESULT` returned from the function that tosses a NULL pointer back at you. Are you claiming `SUCCEEDED(hResult)` is *true*, but `pFromFolder` is NULL ? An `else` condition with some detailed debug messaging may prove a worthwhile addition. If I had to *guess* it would be the `source` and `dest` BSTR values you're creating are from temporary `bstr_t` objects that are long-gone before you actually *use* their "inner-child".. I.e. you have a nice pair of dangling pointers there. – WhozCraig Aug 12 '15 at 06:14
  • 2
    This is a risky way to do it. Expect failures on machines with broken shell associations. Throw away this code and use a real zip library. – David Heffernan Aug 12 '15 at 06:16
  • 1
    Zip support in Explorer is very limited anyway. E.g. no unicode support, no support for secure encryption methods, no support for large (> 4GB) archives. – Jonathan Potter Aug 12 '15 at 06:35

3 Answers3

2
bool CUnZip::Unzip2Folder(BSTR lpZipFile, BSTR lpFolder)
{
IShellDispatch *pISD;
Folder  *pZippedFile = 0L;
Folder  *pDestination = 0L;

long FilesCount = 0;
IDispatch* pItem = 0L;
FolderItems *pFilesInside = 0L;

VARIANT Options, OutFolder, InZipFile, Item;
CoInitialize(NULL);
__try{
    if (CoCreateInstance(CLSID_Shell, NULL, CLSCTX_INPROC_SERVER, IID_IShellDispatch, (void **)&pISD) != S_OK)
        return 1;

    InZipFile.vt = VT_BSTR;
    InZipFile.bstrVal = lpZipFile;
    pISD->NameSpace(InZipFile, &pZippedFile);
    if (!pZippedFile)
    {
        pISD->Release();
        return 1;
    }

    OutFolder.vt = VT_BSTR;
    OutFolder.bstrVal = lpFolder;
    pISD->NameSpace(OutFolder, &pDestination);
    if (!pDestination)
    {
        pZippedFile->Release();
        pISD->Release();
        return 1;
    }

    pZippedFile->Items(&pFilesInside);
    if (!pFilesInside)
    {
        pDestination->Release();
        pZippedFile->Release();
        pISD->Release();
        return 1;
    }

    pFilesInside->get_Count(&FilesCount);
    if (FilesCount < 1)
    {
        pFilesInside->Release();
        pDestination->Release();
        pZippedFile->Release();
        pISD->Release();
        return 0;
    }

    pFilesInside->QueryInterface(IID_IDispatch, (void**)&pItem);

    Item.vt = VT_DISPATCH;
    Item.pdispVal = pItem;

    Options.vt = VT_I4;
    Options.lVal = 1024 | 512 | 16 | 4;//http://msdn.microsoft.com/en-us/library/bb787866(VS.85).aspx

    bool retval = pDestination->CopyHere(Item, Options) == S_OK;

    pItem->Release(); pItem = 0L;
    pFilesInside->Release(); pFilesInside = 0L;
    pDestination->Release(); pDestination = 0L;
    pZippedFile->Release(); pZippedFile = 0L;
    pISD->Release(); pISD = 0L;

    return retval;

}
__finally
{
    CoUninitialize();
}

}

Source: https://social.msdn.microsoft.com/Forums/vstudio/en-US/45668d18-2840-4887-87e1-4085201f4103/visual-c-to-unzip-a-zip-file-to-a-specific-directory?forum=vclanguage

In the main, you call:

CUnZip * Z = new CUnZip();
        BSTR bstrFile = strFileName.AllocSysString();
        BSTR bstrPath = m_strPathName.AllocSysString();
        Z->Unzip2Folder(bstrFile, bstrPath);
        delete Z;
        Z = NULL;
T Nguyen
  • 141
  • 1
  • 6
1

Did you try stepping through the code in a debugger? You have several bugs, and quite a few things that aren't bugs yet, but have lots of potential...

First, as WhozCraig said - you have two dangling pointers there, the two BSTRs. (In fact, if you manage to have the first first NameSpace call succeed, you'll see that the second BSTR changes to some GUID. Obviously the second call fails when it takes that argument.)

Second, you're using wpath instead of wstring or something like that. wpath puts forward slashes between the path components. But a lot of Windows code doesn't work with forward slashes, only backslashes. LoadLibrary() is one example. Practically everything in the Shell is another (see the screenshot at the bottom of this link, for example).

Changing the wpaths to wstrings and the BSTRs to _bstr_ts is enough to make the code work.


Of course you should also get rid of the VARIANT is favour of _variant_ts, when you really need them, and use _com_ptr_ts instead of naked pointers, and preferably use #import for that.

That way the NameSpace() method takes a _variant_t instead of a VARIANT, and _variant_t has an implicit constructor from _bstr_t, so you can directly pass the _bstr_t to the function, without fiddling around with VARIANTs or _variant_ts.

conio
  • 3,681
  • 1
  • 20
  • 34
0

I meet same problem, when I use absolute path and the path separated symbol is '\', the problem is slove