当右键单击文件夹背景并调用上下文菜单时,如何获取文件夹路径?

huangapple go评论88阅读模式
英文:

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

问题

如何获取用户在文件夹背景上右键单击以调用上下文菜单的文件夹路径?例如,用户打开了“D:\projects”文件夹,并在该文件夹的空白背景区域右键单击,然后在上下文菜单中看到一个名为“显示路径”的菜单项。单击它后,应该调用一个简单的控制台应用程序来显示字符串“D:\projects”。

可以通过在注册表中添加“%V”作为控制台应用程序的命令参数来完成这个任务,例如,“C:\myfolder\myapp.exe” “%V”。因此,这个%V将文件夹路径提供给myuapp.exe的main()参数列表。很简单吧!

如何使用Shell扩展菜单处理程序来实现呢?我写了一个简单的Shell上下文菜单dll,它的工作正常,但我不知道如何获取用户在背景上右键单击的文件夹路径作为字符串。

我发现路径作为PCIDLIST_ABSOLUTE pidl参数出现在IShellExtInit::Initialize()方法中。但是,我无法以简单的字符串格式获取它。以下是代码,当然会崩溃。

HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID)
{
    std::wstring s = L"null";

    // 检查消息,这个消息框如预期般显示
    MessageBox(NULL, L"Before", L"Initialize()", MB_OK);

    // 在这一行出现问题,我猜测
    SHGetPathFromIDList((LPCITEMIDLIST)pidlFolder, (PWSTR)&s);

    // 检查消息,有时这个消息框也会如预期般显示
    MessageBox(NULL, L"After", L"Initialize()", MB_OK);

    // 但是这个消息框从未显示过。我已将其删除,但代码仍然崩溃
    MessageBox(NULL, std::wstring(s).c_str(), L"Initialize()", MB_OK);

    return S_OK;
}

当我右键单击文件夹背景时,它会崩溃并重新启动资源管理器。

有人知道问题及其解决方案吗?如何在使用Shell扩展时获取右键单击文件夹背景以调用上下文菜单时的文件夹路径?

此外,如何在右键单击文件/文件夹以调用Shell扩展上下文菜单时获取文件/文件夹路径?

提前感谢。

英文:

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

答案1

得分: 1

你正在尝试让SHGetPathFromIDList()将字符串数据写入一个std::wstring对象所在的内存地址,这不会起作用。

而是应该使用一个固定的WCHAR[]数组,例如:

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

或者,如果你想将字符串数据接收到一个std::wstring对象中,那么你必须预先分配其内部字符缓冲区,然后接收到该缓冲区中,例如:

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'
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;
}
'
));
MessageBox(NULL, s.c_str(), L"Initialize()", MB_OK); return S_OK; }

否则,你可以简单地接收到一个WCHAR[],然后将其赋值给你的std::wstring,例如:

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

你的第二个示例由于几个原因而不起作用:

  • 你的IShellFolder *sf没有指向有意义的地方。使用SHGetDesktopFolder()来获取顶层的IShellFolder对象,然后可以用它来解析pidlFilder

  • 你只为wchar_t *d分配了1个wchar_t的内存,但然后尝试复制多于1个wchar_t的数据到该内存中。实际上,你根本不需要分配任何内存,因为已解析的STRRET已经包含了必要的字符串数据,所以可以直接使用它。否则,你可以将STRRET传递给StrRetToBuf()StrRetToStr()以获取更可用的格式中的数据。

  • 你没有注意STRRET::uType字段,以知道它保存的是什么类型的字符串数据。除非uType字段设置为STRRET_CSTR,否则不要访问cStr字段。StrRetToBufW()/StrRetToStr()会为你处理这些。

尝试改用以下代码:

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;
}
英文:

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

huangapple
  • 本文由 发表于 2023年6月2日 01:39:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76384401.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定