【问题标题】:Why doesn't my IExtractIcon Handler get called?为什么我的 IExtractIcon 处理程序没有被调用?
【发布时间】:2020-10-29 09:57:25
【问题描述】:

我正在尝试根据示例在 C++ 中实现一个图标处理程序:

The Complete Idiot's Guide to Writing Shell Extensions - Part IX

使用示例项目让示例运行没有问题,但是当我尝试在我的 QT 项目中构建它时,我的处理程序永远不会被调用。

安装我的 DLL 后,“ShellExtView”将其显示为“图标处理程序”,据我所知,注册表中的一切看起来都正常。

我拿了我这里的注册码并用它来注册示例 shell 扩展并且它有效,所以我不认为我注册 shell 扩展的方式有问题。

这是我的代码:

头文件:

#include <Windows.h>
// ATL
#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>
#include <atlconv.h>

// Win32
#include <comdef.h>
#include <ShlObj.h>

#define MAX_SUFFIX (32)

#define MY_ID "{E94EFFAC-DBD6-40EF-92FC-460FDEB3684A}"
#define TARGET_ICON_HANDLER_CLASS "txtfile"
const CLSID my_id = {0xE94EFFAC, 0xDBD6, 0x40EF, {0x92, 0XF, 0X46, 0X0F, 0XDE, 0XB3, 0X68, 0X4A}};
const CLSID myLib_id = {0xE94EFFAD, 0xDBD6, 0x40EF, {0x92, 0XF, 0X46, 0X0F, 0XDE, 0XB3, 0X68, 0X4A}};



class CIconShlExt :
        public CComObjectRootEx<CComSingleThreadModel>,
        public CComCoClass<CIconShlExt, &my_id>,
        public IPersistFile,
        public IExtractIcon
{
public:
     CIconShlExt() : m_haveSuffix(false) { }

    BEGIN_COM_MAP(CIconShlExt)
         COM_INTERFACE_ENTRY(IPersistFile)
         COM_INTERFACE_ENTRY(IExtractIcon)
     END_COM_MAP()

    DECLARE_NO_REGISTRY()

    // IPersistFile
    STDMETHODIMP GetClassID( CLSID* pClsId)  { return E_NOTIMPL; }
    STDMETHODIMP IsDirty() { return E_NOTIMPL; }
    STDMETHODIMP Save( LPCOLESTR, BOOL ) { return E_NOTIMPL; }
    STDMETHODIMP SaveCompleted( LPCOLESTR ) { return E_NOTIMPL; }
    STDMETHODIMP GetCurFile( LPOLESTR* ) { return E_NOTIMPL; }

     STDMETHODIMP Load( LPCOLESTR wszFile, DWORD );

     // IExtractIcon
     STDMETHODIMP GetIconLocation( UINT uFlags, LPTSTR szIconFile, UINT cchMax,
                                   int* piIndex, UINT* pwFlags );
     STDMETHODIMP Extract( LPCTSTR pszFile, UINT nIconIndex, HICON* phiconLarge,
                           HICON* phiconSmall, UINT nIconSize );

     WCHAR m_suffix[MAX_SUFFIX];
     bool  m_haveSuffix;

};

IPersistFileIExtractIcon 方法(从不调用):

#pragma region IPersistFile
STDMETHODIMP CIconShlExt::Load(LPCOLESTR wszFile, DWORD)
{
    // I never get here!
    return S_OK;
}
#pragma endregion


#pragma region IExtractIcon

STDMETHODIMP CIconShlExt::GetIconLocation(UINT /*uFlags*/, LPTSTR szIconFile, UINT cchMax, int* piIndex, UINT* pwFlags )
{
    // I never get here!

    // Give it a strange icon so I know it did something
    *piIndex = -218;
    lstrcpyn(szIconFile, "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\Common7\\IDE\\msenvico.dll", cchMax);
    *pwFlags = GIL_PERINSTANCE; // GIL_NOTFILENAME;

    return S_OK;
}

STDMETHODIMP CIconShlExt::Extract( LPCTSTR , UINT , HICON*, HICON* , UINT)
{
    return S_FALSE;
}

#pragma endregion

主DLL文件:

static DWORD SetRegistryKeyAndValue(HKEY root, const char *key, const char *value, const char *name)
{
    HKEY hKey = NULL;
    DWORD err;

    err = RegCreateKeyEx(root, key, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);

    if (err == ERROR_SUCCESS) {
        if (name != NULL) {
            // Set the specified value of the key.
            DWORD cbData = lstrlen(name) * sizeof(*name) +1;
            err = RegSetValueEx(hKey, value, 0, REG_SZ, reinterpret_cast<const BYTE *>(name), cbData);

        }
        RegCloseKey(hKey);
    }

    return err;
}

static void unregisterHandler()
{
    RegDeleteTree(HKEY_CLASSES_ROOT, "CLSID\\" MY_ID);

    RegDeleteKey(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved\\" MY_ID);
    RegDeleteTree(HKEY_CLASSES_ROOT, TARGET_ICON_HANDLER_CLASS "\\ShellEx\\IconHandler");
}

static DWORD registerHandler(const char *dll)
{
DWORD err;

    if ((err = SetRegistryKeyAndValue(HKEY_CLASSES_ROOT, "CLSID\\" MY_ID, nullptr, "My icon extension")) != ERROR_SUCCESS)
        goto error;

    if ((err = SetRegistryKeyAndValue(HKEY_CLASSES_ROOT, "CLSID\\" MY_ID"\\InprocServer32", NULL, dll)) != ERROR_SUCCESS)
        goto error;

    if ((err = SetRegistryKeyAndValue(HKEY_CLASSES_ROOT, "CLSID\\" MY_ID"\\InprocServer32", "ThreadingModel", "Apartment")) != ERROR_SUCCESS)
        goto error;

    if ((err = SetRegistryKeyAndValue(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", MY_ID, "My icon extension")) != ERROR_SUCCESS)
        goto error;

    if ((err = SetRegistryKeyAndValue(HKEY_CLASSES_ROOT, TARGET_ICON_HANDLER_CLASS "\\ShellEx\\IconHandler", nullptr, MY_ID)) != ERROR_SUCCESS)
        goto error;

    if ((err = SetRegistryKeyAndValue(HKEY_CLASSES_ROOT, TARGET_ICON_HANDLER_CLASS "\\DefaultIcon", nullptr, "%1")) != ERROR_SUCCESS)
        goto error;

    return err;

error:
    unregisterHandler();
    return err;
}


BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(my_id, CIconShlExt)
END_OBJECT_MAP()

CComModule _Module;

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        _Module.Init(ObjectMap, hInstance, &myLib_id);
        DisableThreadLibraryCalls(hInstance);
    }
    else if (dwReason == DLL_PROCESS_DETACH)
        _Module.Term();
    return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow()
{
    return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    return _Module.GetClassObject(rclsid, riid, ppv);
}

STDAPI DllRegisterServer()
{
    char dllPath[MAX_PATH];
    if (GetModuleFileName(_Module.m_hInst, dllPath, ARRAYSIZE(dllPath)) == 0)
    {
        HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
        return hr;
    }
    DWORD rtc = registerHandler(dllPath);
    if (rtc != ERROR_SUCCESS)
        return HRESULT_FROM_WIN32(rtc);

    return _Module.RegisterServer(false);
}

STDAPI  DllUnregisterServer(void)
{   
    unregisterHandler();
    return _Module.UnregisterServer(false);
}

【问题讨论】:

  • 附带说明,当为 REG_SZ 值调用 RegSetValueEx() 时,您需要包含字符串的空终止符:DWORD cbData = (lstrlen(name) + 1) * sizeof(*name);
  • 感谢您指出这一点,不幸的是它并没有改变任何东西。
  • 您是否检查过可能的 x86 与 x64 注册问题?
  • 在这两种情况下,他们都在注册一个 x64 shell 扩展。
  • @B.Leslie 在测试您的 Shell 扩展时,您使用的是 32 位还是 64 位版本的 explorer.exe? 32位版本不能使用64位shell扩展,64位版本不能使用32位扩展。您是否尝试过编译和注册 32 位和 64 位版本的扩展?

标签: c++ atl shell-extensions


【解决方案1】:

不能保证.txt 文件扩展名的ProgID 在每个系统上都是txtfile。许多应用程序为了自己的目的劫持.txt。您应该从 HKEY_CLASSES_ROOT\.txt 键中读取实际的 ProgID,然后为该 ProgID 注册您的处理程序。

但是,更重要的是,您真的不应该直接修改HKEY_CLASSES_ROOT 子键,而是修改HKEY_CURRENT_USER\Software\ClassesHKEY_LOCAL_MACHINE\Software\Classes 下的相应子键。 @MSDN 上的This is documented

类注册和文件扩展名信息存储在HKEY_LOCAL_MACHINEHKEY_CURRENT_USER 键下。HKEY_LOCAL_MACHINE\Software\Classes 键包含可应用于本地计算机上所有用户的默认设置。 HKEY_CURRENT_USER\Software\Classes 键包含仅适用于交互式用户的设置。 HKEY_CLASSES_ROOT 键提供了合并来自这两个来源的信息的注册表视图。 HKEY_CLASSES_ROOT 还为为以前版本的 Windows 设计的应用程序提供了这种合并视图。

用户特定设置优先于默认设置。例如,默认设置可能会指定一个特定的应用程序来处理 .doc 文件。但用户可以通过在注册表中指定不同的应用程序来覆盖此设置。

RegOpenKeyExRegQueryValueEx 等注册表函数允许您指定HKEY_CLASSES_ROOT 键。当您从交互式用户帐户中运行的进程调用这些函数时,系统会将HKEY_LOCAL_MACHINE\Software\Classes 中的默认设置与HKEY_CURRENT_USER\Software\Classes 中的交互式用户设置合并。有关如何合并这些设置的更多信息,请参阅Merged View of HKEY_CLASSES_ROOT

要更改交互式用户的设置,请将更改存储在HKEY_CURRENT_USER\Software\Classes 而不是HKEY_CLASSES_ROOT

要更改默认设置,请将更改存储在HKEY_LOCAL_MACHINE\Software\Classes 下。 如果您将密钥写入HKEY_CLASSES_ROOT 下的密钥,系统会将信息存储在HKEY_LOCAL_MACHINE\Software\Classes 下。如果你给HKEY_CLASSES_ROOT下的key写值,而HKEY_CURRENT_USER\Software\Classes下已经存在key,系统会将信息保存在HKEY_LOCAL_MACHINE\Software\Classes下。

【讨论】:

  • 这只是一个尝试让它工作的测试,所以硬编码到“txtfile”只是为了简化事情。我理解你关于使用 HKEY_CLASSES_ROOT 的观点,但在这种情况下,它实际上做了我想要的,并且将代码更改为使用 HKEY_CURRENT_USER\Software\Classes 或 HKEY_LOCAL_MACHINE\Software\Classes 并不能解决我的问题。当我得到这个工作时,我真正的实现将使用 HKEY_CURRENT_USER。
  • 实际上,在注册 shell 扩展时,您应该使用 HKEY_CLASS_ROOT。 docs.microsoft.com/en-us/windows/win32/shell/reg-shell-exts
  • @B.Leslie 该文档描述的内容早于 64 位 Windows。我引用的文档较新。最佳做法是从HKEY_CLASSES_ROOT读取,以便获得merged view of global and user-specific data,但您应该将数据写入HKEY_CURRENT_USER\Software\Classes 以获取用户特定的数据,并写入@ 987654353@ 用于全球数据,根据需要。这包括文件扩展名注册、Shell 扩展名注册、COM 注册等。
  • 早于 64 位 Windows?文档上的日期是 05/31/2018,与 HKEY_CLASSES_ROOT 的文档相同。但无论如何,这似乎与我的问题没有任何关系,因为当使用我用于我的 shell 扩展的相同代码注册时,示例 shell 扩展也可以工作。
  • @B.Leslie 信息早于文档本身。但无论如何,早期用户直接写信给HKEY_CLASSES_ROOT是很常见的,但现在已经不推荐了,而且已经很久没有这样做了。我引用的文件甚至是这样说的。但是无所谓。如果示例扩展有效,但您的扩展无效,那么显然您还没有考虑到差异。
【解决方案2】:

我发现了问题:

将 GUID 字符串转换为 CLSID 时出错。

错误转换:

#define MY_ID "{E94EFFAC-DBD6-40EF-92FC-460FDEB3684A}"
const CLSID my_id = {0xE94EFFAC, 0xDBD6, 0x40EF, {0x92, 0XF, 0X46, 0X0F, 0XDE, 0XB3, 0X68, 0X4A}};

正确的转换:

#define MY_ID "{E94EFFAC-DBD6-40EF-92FC-460FDEB3684A}"
const CLSID my_id = {0xE94EFFAC, 0xDBD6, 0x40EF, {0x92, 0XFC, 0X46, 0X0F, 0XDE, 0XB3, 0X68, 0X4A}};

【讨论】:

    猜你喜欢
    • 2015-06-03
    • 2019-09-26
    • 1970-01-01
    • 1970-01-01
    • 2020-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多