为什么我的 IExtractIcon 处理程序没有被调用?
Why doesn't my IExtractIcon Handler get called?
我正在尝试根据示例在 C++ 中实现图标处理程序:
The Complete Idiot's Guide to Writing Shell Extensions - Part IX
使用示例项目让示例运行没有问题,但是当我尝试在我的 QT 项目中构建它时,我的处理程序从未被调用。
安装我的 DLL 后,'ShellExtView' 将其显示为“图标处理程序”,据我所知,注册表中的一切看起来都正常。
我使用了这里的注册码并用它来注册示例 shell 扩展并且它有效,所以我认为我注册 [=35= 的方式没有问题] 扩展。
这是我的代码:
头文件:
#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;
};
IPersistFile
和 IExtractIcon
方法(从不调用):
#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);
}
无法保证 .txt
文件扩展名的 ProgID 在每个系统上都是 txtfile
。许多应用为了自己的目的劫持 .txt
。您应该从 HKEY_CLASSES_ROOT\.txt
键中读取实际的 ProgID,然后为该 ProgID 注册您的处理程序。
但是,更重要的是,您真的不应该直接修改 HKEY_CLASSES_ROOT
子项,而是修改 HKEY_CURRENT_USER\Software\Classes
和 HKEY_LOCAL_MACHINE\Software\Classes
下的相应子项。 This is documented 在 MSDN 上:
Class registration and file name extension information is stored under both the HKEY_LOCAL_MACHINE
and HKEY_CURRENT_USER
keys. The HKEY_LOCAL_MACHINE\Software\Classes
key contains default settings that can apply to all users on the local computer. The HKEY_CURRENT_USER\Software\Classes
key contains settings that apply only to the interactive user. The HKEY_CLASSES_ROOT
key provides a view of the registry that merges the information from these two sources. HKEY_CLASSES_ROOT
also provides this merged view for applications designed for previous versions of Windows.
The user-specific settings have priority over the default settings. For example, the default setting might specify a particular application to handle .doc files. But a user can override this setting by specifying a different application in the registry.
Registry functions such as RegOpenKeyEx
or RegQueryValueEx
allow you to specify the HKEY_CLASSES_ROOT
key. When you call these functions from a process running in the interactive user account, the system merges the default settings in HKEY_LOCAL_MACHINE\Software\Classes
with the interactive user's settings at HKEY_CURRENT_USER\Software\Classes
. For more information on how these settings are merged, see Merged View of HKEY_CLASSES_ROOT.
To change the settings for the interactive user, store the changes under HKEY_CURRENT_USER\Software\Classes
rather than HKEY_CLASSES_ROOT
.
To change the default settings, store the changes under HKEY_LOCAL_MACHINE\Software\Classes
. If you write keys to a key under HKEY_CLASSES_ROOT
, the system stores the information under HKEY_LOCAL_MACHINE\Software\Classes
. If you write values to a key under HKEY_CLASSES_ROOT
, and the key already exists under HKEY_CURRENT_USER\Software\Classes
, the system will store the information there instead of under HKEY_LOCAL_MACHINE\Software\Classes
.
我发现了问题:
我在将 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}};
我正在尝试根据示例在 C++ 中实现图标处理程序:
The Complete Idiot's Guide to Writing Shell Extensions - Part IX
使用示例项目让示例运行没有问题,但是当我尝试在我的 QT 项目中构建它时,我的处理程序从未被调用。
安装我的 DLL 后,'ShellExtView' 将其显示为“图标处理程序”,据我所知,注册表中的一切看起来都正常。
我使用了这里的注册码并用它来注册示例 shell 扩展并且它有效,所以我认为我注册 [=35= 的方式没有问题] 扩展。
这是我的代码:
头文件:
#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;
};
IPersistFile
和 IExtractIcon
方法(从不调用):
#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);
}
无法保证 .txt
文件扩展名的 ProgID 在每个系统上都是 txtfile
。许多应用为了自己的目的劫持 .txt
。您应该从 HKEY_CLASSES_ROOT\.txt
键中读取实际的 ProgID,然后为该 ProgID 注册您的处理程序。
但是,更重要的是,您真的不应该直接修改 HKEY_CLASSES_ROOT
子项,而是修改 HKEY_CURRENT_USER\Software\Classes
和 HKEY_LOCAL_MACHINE\Software\Classes
下的相应子项。 This is documented 在 MSDN 上:
Class registration and file name extension information is stored under both the
HKEY_LOCAL_MACHINE
andHKEY_CURRENT_USER
keys. TheHKEY_LOCAL_MACHINE\Software\Classes
key contains default settings that can apply to all users on the local computer. TheHKEY_CURRENT_USER\Software\Classes
key contains settings that apply only to the interactive user. TheHKEY_CLASSES_ROOT
key provides a view of the registry that merges the information from these two sources.HKEY_CLASSES_ROOT
also provides this merged view for applications designed for previous versions of Windows.The user-specific settings have priority over the default settings. For example, the default setting might specify a particular application to handle .doc files. But a user can override this setting by specifying a different application in the registry.
Registry functions such as
RegOpenKeyEx
orRegQueryValueEx
allow you to specify theHKEY_CLASSES_ROOT
key. When you call these functions from a process running in the interactive user account, the system merges the default settings inHKEY_LOCAL_MACHINE\Software\Classes
with the interactive user's settings atHKEY_CURRENT_USER\Software\Classes
. For more information on how these settings are merged, see Merged View of HKEY_CLASSES_ROOT.To change the settings for the interactive user, store the changes under
HKEY_CURRENT_USER\Software\Classes
rather thanHKEY_CLASSES_ROOT
.To change the default settings, store the changes under
HKEY_LOCAL_MACHINE\Software\Classes
. If you write keys to a key underHKEY_CLASSES_ROOT
, the system stores the information underHKEY_LOCAL_MACHINE\Software\Classes
. If you write values to a key underHKEY_CLASSES_ROOT
, and the key already exists underHKEY_CURRENT_USER\Software\Classes
, the system will store the information there instead of underHKEY_LOCAL_MACHINE\Software\Classes
.
我发现了问题:
我在将 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}};