Reputation: 304
I've been trying to create a Visual C++ program that displays a menu item when the user right-clicks, following several online tutorials. However, despite registering the DLL and restarting Windows Explorer, it's not working at all. It's not clear what I'm missing here.
regsvr32.exe ShellExtension.dll works without any problem as well.
ShellExtension.h
#pragma once
#include <windows.h>
#include <shlobj.h>
#include <comdef.h>
#include <tchar.h> // Adicione isso
#define MYUUID "{6f81fd22-1c80-4619-a401-22d66dc3410b}"
#define NomeDaExtensao "ShellExtension"
#define Title "Shell Extension"
class __declspec(uuid(MYUUID)) ShellExtension : public IShellExtInit, public IContextMenu
{
public:
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// IShellExtInit
STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID);
// IContextMenu
STDMETHODIMP QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici);
STDMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pReserved, LPSTR pszName, UINT cchMax);
private:
ULONG m_cRef;
};
ShellExtension.cpp
#include <windows.h>
#include <strsafe.h>
#include "ShellExtension.h"
#include "resource.h"
HANDLE g_hEventSource = NULL;
STDMETHODIMP ShellExtension::QueryInterface(REFIID riid, void** ppvObject)
{
if (riid == IID_IUnknown || riid == IID_IShellExtInit || riid == IID_IContextMenu)
{
*ppvObject = this;
AddRef();
return S_OK;
}
*ppvObject = NULL;
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) ShellExtension::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) ShellExtension::Release()
{
ULONG cRef = InterlockedDecrement(&m_cRef);
if (cRef == 0)
delete this;
return cRef;
}
STDMETHODIMP ShellExtension::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID)
{
// Implementação do Initialize
m_cRef = 1; // Inicialize o contador de referências aqui
return S_OK;
}
#define IDI_MYICON 101 // Certifique-se de que este número corresponde ao do arquivo de recursos
#define IDM_DISPLAY 0
STDMETHODIMP ShellExtension::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
if (uFlags & CMF_DEFAULTONLY)
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
// Define o ícone a ser usado
HICON hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON));
// Exemplo de uso de ReportEvent para registrar um evento
if (g_hEventSource)
{
LPCWSTR pInsertStrings[1] = { TEXT("Tentando adicionar item.") };
ReportEvent(g_hEventSource,
EVENTLOG_INFORMATION_TYPE, // Tipo de evento
0, // Categoria do evento
1, // ID do evento, você deve definir isso em um arquivo de mensagem
NULL, // SID do usuário
1, // Número de strings de inserção
0, // Tamanho dos dados binários
pInsertStrings, // Array de strings de inserção
NULL); // Dados binários
}
InsertMenu(hmenu,
indexMenu,
MF_STRING | MF_BYPOSITION,
idCmdFirst + IDM_DISPLAY,
_T("&Display File Name"));
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(1)); // Indica que adicionamos um item ao menu
}
STDMETHODIMP ShellExtension::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
if (!HIWORD(pici->lpVerb))
{
if (LOWORD(pici->lpVerb) == 0) // O primeiro comando (idCmdFirst)
{
MessageBox(NULL, TEXT("Item de menu clicado!"), TEXT("Aviso"), MB_OK);
}
}
return S_OK;
}
STDMETHODIMP ShellExtension::GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pReserved, LPSTR pszName, UINT cchMax)
{
return E_NOTIMPL;
}
STDAPI DllRegisterServer(void)
{
HKEY hKey;
LONG lRes;
// Registra a fonte de eventos
lRes = RegCreateKeyEx(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\" NomeDaExtensao), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
if (lRes == ERROR_SUCCESS)
{
TCHAR szPath[MAX_PATH];
GetModuleFileName(GetModuleHandle(NULL), szPath, MAX_PATH);
// Define o caminho para o arquivo de mensagem (pode ser o próprio módulo, mas normalmente você teria um arquivo de mensagem separado)
RegSetValueEx(hKey, TEXT("EventMessageFile"), 0, REG_SZ, (BYTE*)szPath, (lstrlen(szPath) + 1) * sizeof(TCHAR));
// Define o tipo de arquivo de mensagem
DWORD dwSupport = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
RegSetValueEx(hKey, TEXT("TypesSupported"), 0, REG_DWORD, (BYTE*)&dwSupport, sizeof(DWORD));
RegCloseKey(hKey);
}
else
{
// Trate o erro de registro
return SELFREG_E_CLASS;
}
// Cria a chave CLSID para a classe
lRes = RegCreateKeyEx(HKEY_CLASSES_ROOT, TEXT("CLSID\\" MYUUID), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
if (lRes != ERROR_SUCCESS) return SELFREG_E_CLASS;
// Define o nome da classe
RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE*)TEXT(Title), sizeof(TEXT(Title)));
// Adiciona a entrada para o InprocServer32
HKEY hSubKey;
lRes = RegCreateKeyEx(hKey, TEXT("InprocServer32"), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hSubKey, NULL);
if (lRes == ERROR_SUCCESS)
{
// Caminho completo para a DLL
TCHAR szModule[MAX_PATH];
GetModuleFileName(GetModuleHandle(NULL), szModule, MAX_PATH);
RegSetValueEx(hSubKey, NULL, 0, REG_SZ, (BYTE*)szModule, (lstrlen(szModule) + 1) * sizeof(TCHAR));
RegSetValueEx(hSubKey, TEXT("ThreadingModel"), 0, REG_SZ, (BYTE*)TEXT("Apartment"), sizeof(TEXT("Apartment")));
RegCloseKey(hSubKey);
}
// Adicionar a extensão de shell ao menu "Enviar para"
lRes = RegCreateKeyEx(HKEY_CLASSES_ROOT, TEXT("AllFileSystemObjects\\shellex\\ContextMenuHandlers\\" NomeDaExtensao), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hSubKey, NULL);
if (lRes == ERROR_SUCCESS)
{
RegSetValueEx(hSubKey, NULL, 0, REG_SZ, (BYTE*)TEXT(MYUUID), sizeof(TEXT(MYUUID)));
RegCloseKey(hSubKey);
}
RegCloseKey(hKey);
return lRes == ERROR_SUCCESS ? S_OK : SELFREG_E_CLASS;
}
STDAPI DllUnregisterServer(void)
{
RegDeleteKey(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\" NomeDaExtensao));
// Remover as entradas de registro correspondentes
RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("AllFileSystemObjects\\shellex\\ContextMenuHandlers\\" NomeDaExtensao));
RegDeleteTree(HKEY_CLASSES_ROOT, TEXT("CLSID\\" MYUUID));
return S_OK;
}
// DllMain - ponto de entrada para a DLL
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hEventSource = RegisterEventSource(NULL, TEXT(NomeDaExtensao));
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
if (g_hEventSource)
{
DeregisterEventSource(g_hEventSource);
g_hEventSource = NULL;
}
break;
}
return TRUE;
}
config.def
LIBRARY "ShellExtensionCpp"
EXPORTS
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
Upvotes: 1
Views: 67
Reputation: 597916
Inside a DLL, calling GetModuleHandle()
with a NULL filename will return a handle to the EXE that has loaded the DLL:
If this parameter is NULL, GetModuleHandle returns a handle to the file used to create the calling process (.exe file).
So, you end up registering the EXE file (regsvr32.exe
) instead of your DLL file (ShellExtension.dll
) in your Registry keys. If you had looked at your keys, you should have seen that.
In this situation, you need to instead use the HMODULE
that is being passed to your DllMain()
function, eg:
...
HMODULE g_hModule = NULL;
...
STDMETHODIMP ShellExtension::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
...
HICON hIcon = LoadIcon(g_hModule, ...);
...
}
...
STDAPI DllRegisterServer(void)
{
...
TCHAR szPath[MAX_PATH];
GetModuleFileName(g_hModule, szPath, MAX_PATH);
...
TCHAR szModule[MAX_PATH];
GetModuleFileName(g_hModule, szModule, MAX_PATH);
...
}
...
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hModule = hModule;
...
break;
...
}
...
}
Alternatively, you can use GetModuleHandleEx()
with the GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
flag, passing in the address of a function that belongs to your DLL, eg:
STDAPI DllRegisterServer(void)
{
HMODULE hModule = hModule;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)&DllRegisterServer, &hModule);
...
TCHAR szPath[MAX_PATH];
GetModuleFileName(hModule, szPath, MAX_PATH);
...
TCHAR szModule[MAX_PATH];
GetModuleFileName(hModule, szModule, MAX_PATH);
...
}
On a side note:
per MSDN guidelines, you should not register your keys under HKEY_CLASSES_ROOT
itself. Register them under HKEY_LOCAL_MACHINE\SOFTWARE\Classes
(all users) or HKEY_CURRENT_USER\SOFTWARE\Classes
(current user) instead.
your DllMain()
is unregistering your EventSource
whenever the DLL is unloaded from every thread started by the hosting EXE. Since your code is not actually doing anything per-thread, your DLL_THREAD_ATTACH
/DLL_THREAD_DETACH
handlers should be no-ops, so just put a break
in those cases:
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
...
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break; // <-- add this
case DLL_PROCESS_DETACH:
...
break;
...
}
return TRUE;
}
Better, you can just remove those handlers completely, and your DLL_PROCESS_ATTACH
handler should call DisableThreadLibraryCalls()
to disable the OS from generating the DLL_THREAD_ATTACH
/DLL_THREAD_DETACH
notifications at all:
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hModule); // <-- add this
...
break;
/* remove these
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
*/
case DLL_PROCESS_DETACH:
...
break;
}
return TRUE;
}
Upvotes: 2