如何在C++ WinAPI中获取当前文件资源管理器窗口的路径

5
我是一个有用的助手,可以翻译文本。
我一直在思考如何解决这个问题。基本上,我的应用程序需要使用winapi在c++中找出Windows中活动文件资源管理器(即前台的文件资源管理器)的目录路径。
而不是这样:
TCHAR* getWindowDir(){
 TCHAR* windowTitle = new TCHAR[MAX_PATH];
 HWND windowHandle = GetForegroundWindow();
 GetWindowText(windowHandle,windowTitle,MAX_PATH);
 return windowTitle;
}

这显然会返回窗口标题,我希望它返回活动目录。


也许这个线程可以给你一些提示:https://dev59.com/dWEi5IYBdhLWcg3wx-ts - OnNIX
https://en.wikipedia.org/wiki/Dangling_pointer - Hans Passant
停止黑客攻击,使用Shell。此外,如果您要调用Win32函数,请养成检查错误的习惯。忽略返回值将会带来危险。 - David Heffernan
1个回答

14
创建一个 IShellWindows 实例,用它来枚举所有当前打开的资源管理器窗口。使用各种相关接口,可以从 IShellWindows 枚举的每个项目中获取窗口句柄和当前文件夹形式的 PIDL。如果窗口句柄等于 GetForegroundWindow() 的结果,则将 PIDL 转换为路径。
以下是一些关于获取所有资源管理器窗口信息的代码。它部分基于 Raymond Chen 的代码,但使用智能指针使代码更加健壮和清晰。我还通过异常添加了错误处理。
首先是所需的包含文件和一些实用程序代码:
#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 >;

现在我们定义一个函数GetCurrentExplorerFolders(),用于返回所有当前打开的资源管理器窗口的信息,包括窗口句柄和当前文件夹的PIDL
// 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;
}

这个示例展示了如何调用 GetCurrentExplorerFolders() 方法,将 PIDL 转换为路径并捕获异常。

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

可能的输出:

可能的输出:

Currently open explorer windows:
hwnd: 0x0030058E, path: C:\Windows
hwnd: 0x000C06D4, path: C:\Program Files

这比我预期的要多得多,真的非常感谢!运行得非常好!伙计,我真的需要更多地了解COM。 - Ian Bastos
如果有一种方法可以获取路径长度,而不是猜测缓冲区大小(如“32767”),那就太好了。 - raymai97
1
另一种选择是使用SHGetNameFromIDList(SIGDN_FILESYSPATH),或者SHCreateItemFromIDList()IShellItem::GetDisplayName(SIGDN_FILESYSPATH),或者SHGetDesktopFolder()IShellFolder::GetDisplayNameOf(SHGDN_FORPARSING)。这些方法都会为您分配必要的路径字符串。 - Remy Lebeau
@RemyLebeau 谢谢!我已经更新了代码,使用 SHGetNameFromIDList() - zett42

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