Home > Enterprise >  C - Windows Shell API - Wrong path while iterating through a folder
C - Windows Shell API - Wrong path while iterating through a folder

Time:06-10

Consider the following code:

bool ListFolderContent(const std::wstring& fileName)
{
    PIDLIST_ABSOLUTE pidl = nullptr;

    if (FAILED(::SHILCreateFromPath(fileName.c_str(), &pidl, nullptr)))
        return false;

    IShellFolder* pShellfolder = nullptr;
    LPCITEMIDLIST pidlRelative = nullptr;

    HRESULT hr = SHBindToParent(pidl, IID_IShellFolder, (void**)&pShellfolder, &pidlRelative);

    if (FAILED(hr))
    {
        ::CoTaskMemFree(pidl);
        return false;
    }

    IEnumIDList* pEnumIDList = nullptr;

    hr = pShellfolder->EnumObjects(nullptr, SHCONTF_NONFOLDERS, &pEnumIDList);

    if (FAILED(hr))
    {
        pShellfolder->Release();
        ::CoTaskMemFree(pidl);
        return false;
    }

    while (1)
    {
        LPITEMIDLIST pChild = nullptr;

        hr = pEnumIDList->Next(1, &pChild, nullptr);

        if (FAILED(hr))
        {
            pShellfolder->Release();
            ::CoTaskMemFree(pidl);
            return false;
        }

        if (hr == S_FALSE)
            break;

        wchar_t buffer[MAX_PATH   1];

        if (::SHGetPathFromIDListW(pChild, buffer))
        {
            ::OutputDebugString(buffer);
            ::OutputDebugString(L"\r\n");
        }
    }

    pShellfolder->Release();
    ::CoTaskMemFree(pidl);

    return true;
}

This code works well and logs the content of the folder owning the given file I pass through the fileName parameter.

However I have an issues with this code: Whatever I pass as file name, the logged path is always C:\Users\Admin\Desktop, and NOT the path to the parent folder I'm iterating, as expected. On the other hand the file names are correct.

Can someone explain me what I'm doing wrong?

CodePudding user response:

You are passing just the last component ("filename") here: SHGetPathFromIDListW(pChild, buffer). Since the desktop is the root you are basically asking to get the filesystem path of [Desktop] [Filename] from the namespace.

There are two solutions:

  • pShellfolder->GetDisplayNameOf(pChild, SHGDN_FORPARSING, ...) StrRetToBuf. This is fast since you already have an instance of the folder interface.

  • Call SHGetPathFromIDList with an absolute (full) pidl. On the pidl from SHILCreateFromPath, call ILCloneFull, ILRemoveLastID and ILCombine(clone, pChild).


HRESULT Example()
{
    WCHAR buf[MAX_PATH];
    GetWindowsDirectory(buf, MAX_PATH);
    PathAppend(buf, L"Explorer.exe");
    
    PIDLIST_ABSOLUTE pidl, pidlFullItem;
    HRESULT hr = SHILCreateFromPath(buf, &pidl, nullptr); // %windir%\Explorer.exe
    if (FAILED(hr)) return hr;

    LPITEMIDLIST pLeaf;
    IShellFolder*pShellfolder; // %windir%
    hr = SHBindToParent(pidl, IID_IShellFolder, (void**)&pShellfolder, nullptr);
    if (SUCCEEDED(hr))
    {
        IEnumIDList*pEnum;
        // Method 1:
        hr = pShellfolder->EnumObjects(nullptr, SHCONTF_NONFOLDERS, &pEnum);
        if (SUCCEEDED(hr))
        {
            for (; S_OK == (hr = pEnum->Next(1, &pLeaf, nullptr));)
            {
                STRRET sr;
                hr = pShellfolder->GetDisplayNameOf(pLeaf, SHGDN_FORPARSING, &sr);
                if (SUCCEEDED(hr))
                {
                    hr = StrRetToBuf(&sr, pLeaf, buf, MAX_PATH);
                    if (SUCCEEDED(hr)) wprintf(L"M1: Item: %s\n", buf);
                }
            }
            pEnum->Release();
        }

        // Method 2:
        ILRemoveLastID(pidl); // %windir%\Explorer.exe => %windir%
        hr = pShellfolder->EnumObjects(nullptr, SHCONTF_NONFOLDERS, &pEnum);
        if (SUCCEEDED(hr))
        {
            for (; S_OK == (hr = pEnum->Next(1, &pLeaf, nullptr));)
            {
                pidlFullItem = ILCombine(pidl, pLeaf); // %windir%   Filename
                if (pidlFullItem)
                {
                    hr = SHGetPathFromIDListW(pidlFullItem, buf);
                    if (SUCCEEDED(hr)) wprintf(L"M2: Item: %s\n", buf);
                    ILFree(pidlFullItem);
                }
            }
            pEnum->Release();
        }
        pShellfolder->Release();
    }
    ILFree(pidl);
    return hr;
}
  • Related