user12707
user12707

Reputation: 304

Troubleshooting Right-Click Menu in Visual C++

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

Answers (1)

Remy Lebeau
Remy Lebeau

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

Related Questions