5

I've been banging my head on the wall of how I can go about doing this. Basically my application needs to figure out the directory path of the active file explorer (ie the file explorer in the foreground) in windows in c++ using the winapi.

Instead of this:

TCHAR* getWindowDir(){
 TCHAR* windowTitle = new TCHAR[MAX_PATH];
 HWND windowHandle = GetForegroundWindow();
 GetWindowText(windowHandle,windowTitle,MAX_PATH);
 return windowTitle;
}

Which obviously returns the window title I want it to return the active directory.

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
Ian Bastos
  • 83
  • 2
  • 5

1 Answers1

14

Create an instance of IShellWindows and use that to enumerate all currently open Explorer windows. Using various related interfaces, you can get the window handle and the current folder in form of a PIDL from each item enumerated by IShellWindows. If the window handle is equal to the result of GetForegroundWindow(), convert the PIDL into a path.

In the following I present some code for getting information about all Explorer windows. It is partially based on code of Raymond Chen but uses smart pointers for less fragile and cleaner code. I've also added error handling via exceptions.

First the required includes and some utility code:

#include <Windows.h>
#include <shlobj.h>
#include <atlcomcli.h>  // for COM smart pointers
#include <atlbase.h>    // for COM smart pointers
#include <vector>
#include <system_error>
#include <memory>
#include <iostream>

// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
    if( FAILED( hr ) )
        throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}

// Deleter for a PIDL allocated by the shell.
struct CoTaskMemDeleter
{
    void operator()( ITEMIDLIST* pidl ) const { ::CoTaskMemFree( pidl ); }
};
// A smart pointer for PIDLs.
using UniquePidlPtr = std::unique_ptr< ITEMIDLIST, CoTaskMemDeleter >;

Now we define a function GetCurrentExplorerFolders() to return information about all currently open Explorer windows, including window handle and PIDL of the current folder.

// Return value of GetCurrentExplorerFolders()
struct ExplorerFolderInfo
{
    HWND hwnd = nullptr;  // window handle of explorer
    UniquePidlPtr pidl;   // PIDL that points to current folder
};

// Get information about all currently open explorer windows.
// Throws std::system_error exception to report errors.
std::vector< ExplorerFolderInfo > GetCurrentExplorerFolders()
{
    CComPtr< IShellWindows > pshWindows;
    ThrowIfFailed(
        pshWindows.CoCreateInstance( CLSID_ShellWindows ),
        "Could not create instance of IShellWindows" );

    long count = 0;
    ThrowIfFailed(
        pshWindows->get_Count( &count ),
        "Could not get number of shell windows" );

    std::vector< ExplorerFolderInfo > result;
    result.reserve( count );

    for( long i = 0; i < count; ++i )
    {
        ExplorerFolderInfo info;

        CComVariant vi{ i };
        CComPtr< IDispatch > pDisp;
        ThrowIfFailed(
            pshWindows->Item( vi, &pDisp ),
            "Could not get item from IShellWindows" );

        if( ! pDisp )
            // Skip - this shell window was registered with a NULL IDispatch
            continue;

        CComQIPtr< IWebBrowserApp > pApp{ pDisp };
        if( ! pApp )
            // This window doesn't implement IWebBrowserApp 
            continue;

        // Get the window handle.
        pApp->get_HWND( reinterpret_cast<SHANDLE_PTR*>( &info.hwnd ) );

        CComQIPtr< IServiceProvider > psp{ pApp };
        if( ! psp )
            // This window doesn't implement IServiceProvider
            continue;

        CComPtr< IShellBrowser > pBrowser;
        if( FAILED( psp->QueryService( SID_STopLevelBrowser, &pBrowser ) ) )
            // This window doesn't provide IShellBrowser
            continue;

        CComPtr< IShellView > pShellView;
        if( FAILED( pBrowser->QueryActiveShellView( &pShellView ) ) )
            // For some reason there is no active shell view
            continue;

        CComQIPtr< IFolderView > pFolderView{ pShellView };
        if( ! pFolderView )
            // The shell view doesn't implement IFolderView
            continue;

        // Get the interface from which we can finally query the PIDL of
        // the current folder.
        CComPtr< IPersistFolder2 > pFolder;
        if( FAILED( pFolderView->GetFolder( IID_IPersistFolder2, (void**) &pFolder ) ) )
            continue;

        LPITEMIDLIST pidl = nullptr;
        if( SUCCEEDED( pFolder->GetCurFolder( &pidl ) ) )
        {
            // Take ownership of the PIDL via std::unique_ptr.
            info.pidl = UniquePidlPtr{ pidl };
            result.push_back( std::move( info ) );
        }
    }

    return result;
}

Example showing how to call GetCurrentExplorerFolders(), convert PIDL into path and catch exceptions.

int main()
{
    ::CoInitialize( nullptr );
        
    try
    {
        std::wcout << L"Currently open explorer windows:\n";
        for( const auto& info : GetCurrentExplorerFolders() )
        {
            CComHeapPtr<wchar_t> pPath;
            if( SUCCEEDED( ::SHGetNameFromIDList( info.pidl.get(), SIGDN_FILESYSPATH, &pPath ) ) )
            {
                std::wcout << L"hwnd: 0x" << std::hex << info.hwnd 
                           << L", path: " << static_cast<LPWSTR>( pPath ) << L"\n";
            }
        }
    }
    catch( std::system_error& e )
    {
        std::cout << "ERROR: " << e.what() << "\nError code: " << e.code() << "\n";
    }

    ::CoUninitialize();
}

Possible output:

Currently open explorer windows:
hwnd: 0x0030058E, path: C:\Windows
hwnd: 0x000C06D4, path: C:\Program Files
zett42
  • 25,437
  • 3
  • 35
  • 72
  • This is way more than I expected, seriously thank you! Works like a charm! Man I've really got read more about COM – Ian Bastos May 06 '17 at 20:02
  • It would be great if there is way to get the path length, instead of guessing a buffer size like `32767`. – raymai97 Aug 07 '17 at 04:54
  • 1
    The alternative would be to use `SHGetNameFromIDList(SIGDN_FILESYSPATH)`, or `SHCreateItemFromIDList()` and `IShellItem::GetDisplayName(SIGDN_FILESYSPATH)`, or `SHGetDesktopFolder()` and `IShellFolder::GetDisplayNameOf(SHGDN_FORPARSING)`. Any of those will allocate the necessary path string for you. – Remy Lebeau Jul 08 '22 at 22:32
  • @RemyLebeau Thanks! I've updated the code to use `SHGetNameFromIDList()`. – zett42 Jul 09 '22 at 13:08