Search code examples
c++dllcomcontextmenuwindows-shell

How can I obtain the folder path when right-clicking the background of a folder and invoking a context menu using a shell extension?


How to obtain path to folder in which user made right click in its background to invoke context menu? For example, user opened "D:\projects" folder and made right click in empty background area of that folder and it sees a menu item in context menu named 'Display Path'. Upon clicking it, it should invoke a simple console app to display string "D:\projects".

It can be done by registry by adding "%V" as argument to command to console app, for example, "C:\myfolder\myapp.exe" "%V". Hence, this %V gives folder path to argument list of main() of myuapp.exe. Easy huh!

How it can be done using shell extension menu handler? I wrote a simple shell context menu dll which works fine and do its job, except that I don't known how to get that folder path as string where user made right click in background.

I found that path comes as PCIDLIST_ABSOLUTE pidl argument in IShellExtInit::Initialize() method. But, I couldn't get it in simple string format. The code is below which crashes, of course.

HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFilder, IDataObject* pdtobj, HKEY hkeyProgID)
    {
        std::wstring s = L"null";
        
        // check msg, this msgbox is shown as expected
        MessageBox(NULL, L"Before", L"Initialize()", MB_OK);
                
                //have problem in this line, I guess
        SHGetPathFromIDList((LPCITEMIDLIST) pidlFilder, (PWSTR) &s);
        
        // check msg, sometimes this msgbox is also shown as expected
        MessageBox(NULL, L"After", L"Initialize()", MB_OK);
        
        // but this msgbox is never shown. I removed it but code still crashes
        MessageBox(NULL, std::wstring(s).c_str(), L"Initialize()", MB_OK);
        
        return S_OK;
    }

When I right click on folder background, it crashes and explorer restarts.

Does anyone know the problem and its solution? How to get folder path when right clicking background of folder to invoke context menu using shell extension?

In addition, how to get file/folder path when right clicking on it to invoke context menu using shell extension?

Thanks in advance

tried using this code too, still crashes

                IShellFolder *sf = NULL;
        STRRET pName = {};
        sf->GetDisplayNameOf(pidlFilder, SHGDN_FORPARSING, &pName);
        wchar_t *d = new wchar_t;
        lstrcpyW(d,L"nulld");
        size_t inst = MAX_PATH, outst ;
        mbstowcs_s(&outst, d, inst, pName.cStr, MAX_PATH);
        s = std::wstring(d);
                MessageBox(NULL, std::wstring(s).c_str(), L"Initialize()", MB_OK);

Solution

  • You are trying to make SHGetPathFromIDList() write the string data to the memory address where a std::wstring object resides, which will not work.

    Use a fixed WCHAR[] array instead, eg:

    HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFilder, IDataObject* pdtobj, HKEY hkeyProgID)
    {
        WCHAR szPath[MAX_PATH] = {};
    
        SHGetPathFromIDList(pidlFilder, szPath);
    
        MessageBox(NULL, szPath, L"Initialize()", MB_OK);
            
        return S_OK;
    }
    

    Alternatively, if you want to receive the string data into a std::wstring object, then you have to pre-allocate its internal character buffer and then receive into that buffer, eg:

    HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFilder, IDataObject* pdtobj, HKEY hkeyProgID)
    {
        std::wstring s;
        s.resize(MAX_PATH);
            
        SHGetPathFromIDList(pidlFilder, s.data() /* or &s[0] before C++17 */ );
        s.erase(s.find(L'\0'));
    
        MessageBox(NULL, s.c_str(), L"Initialize()", MB_OK);
            
        return S_OK;
    }
    

    Otherwise, you can simply receive into a WCHAR[] and then assign that to your std::wstring, eg:

    HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFilder, IDataObject* pdtobj, HKEY hkeyProgID)
    {
        WCHAR szPath[MAX_PATH] = {};
        SHGetPathFromIDList(pidlFilder, szPath);
            
        std::wstring s = szPath;
        MessageBox(NULL, s.c_str(), L"Initialize()", MB_OK);
            
        return S_OK;
    }
    

    Your 2nd example doesn't work for several reasons:

    • Your IShellFolder *sf doesn't point anywhere meaningful. Use SHGetDesktopFolder() to get the top-level IShellFolder object which you can then use to parse pidlFilder.

    • you are allocating only 1 wchar_t for wchar_t *d to point at, but then you are trying to copy more than 1 wchar_t into that memory. You don't really need to allocate any memory at all, as the parsed STRRET already contains the necessary string data, so just use it as-is. Otherwise, you can pass the STRRET to StrRetToBuf() or StrRetToStr() to get the data in a more usable format.

    • you are not paying attention to the STRRET::uType field to know what kind of string data it is holding. Don't access the cStr field unless the uType field is set to STRRET_CSTR. StrRetToBuf()/StrRetToStr() will handle this for you.

    Try this instead:

    HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFilder, IDataObject* pdtobj, HKEY hkeyProgID)
    {
        IShellFolder *sf = NULL;
        if (SUCCEEDED(SHGetDesktopFolder(&sf))
        {
            STRRET pName = {};
            if (SUCCEEDED(sf->GetDisplayNameOf(pidlFilder, SHGDN_FORPARSING, &pName))
            {
                WCHAR szPath[MAX_PATH] = {};
                StrRetToBufW(&pName, pidlFilder, szPath, MAX_PATH);
    
                MessageBox(NULL, szPath, L"Initialize()", MB_OK);
            }
    
            sf->Release();
        }
    
        return S_OK;
    }