Home > Software design >  How to install hook procedure on external thread with custom data?
How to install hook procedure on external thread with custom data?

Time:10-03

in my scenario, I have an application (that I don't own) that has multiple windows and the same thread owns the multiple windows. So that, when I install the hook procedure (from my own application, using DLL file that includes this procedure) using SetWindowsHookEx, it will install a hook that monitors all windows that are owned by the same thread of the target application.

What I want to achieve is that the hook procedure that is installed will have a variable called for example targetHwnd. Once it have it, it will ignore anything that is not targetHwnd.

For example, I will have something like this:

LRESULT CALLBACK GetMsgHookProc(int code, WPARAM wParam, LPARAM lParam)
{

    // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644981(v=vs.85)#parameters
    if (code == HC_ACTION)
    {
        const auto msg = (MSG*)lParam;

        if (msg->hwnd != targetHwnd)
            // We skip processing any other window that owned by this thread
            return CallNextHookEx(hHook, code, wParam, lParam);


        // Here I will do specific processing for the window I want
        // ...
        // ...
        // ...
    }

    return CallNextHookEx(hHook, code, wParam, lParam);
}

The question is: How I can get to a point that the DLL with the hook procedure was injected with this external information that in our case is targetHwnd.

I don't see that the function SetWindowsHookEx has a parameter for custom data or something like that.

An alternative way I thought about is to send WM_COPYDATA to one of the windows that I need (and is owned by the external application thread that I installed the hook for). Then, the GetMsgHookProc will get this data via the WM_COPYDATA message. If in the DLL, the targetHwnd is NULL, it will take this data from the lParam parameter.

But then how can I tell if it was my application that sent it or it is just in case another application that also sending WM_COPYDATA to the window? It does not sound safe.. So I thought that I should use the wParam ("A handle to the window passing the data") and then verify that it was sent from my application by calling GetClassName and check if the class name is something unique that I expect.

But I am not sure that it is safe to use these APIs from DLL because according to MS, it may be unsafe.

So it will help me if you will give me a clue what I should do and if what I think to do is the right way.

Thank you.

CodePudding user response:

WM_COPYDATA is perfectly safe to use across thread boundaries and DLL boundaries. You can use the COPYDATASTRUCT::dwData field to identify whether the message belongs to you are not, simply store a unique value in that field. Typically, RegisterWindowMessage() is used for that purpose.

However, WM_COPYDATA is a bit overkill for this task. Since an HWND fits directly inside a WPARAM and LPARAM (as evidenced by WM_COPYDATA), you could just send (or even post) a custom message instead, one that only your DLL hook knows how to handle. Again, RegisterWindowMessage() can be used for that purpose.

Another option is to use shared memory instead, such as via CreateFileMapping() and MapViewOfFile() (or, via #pragma data_seg, if your compiler supports it). The app that installs the hook can allocate a block of shared memory and store the desired HWND in it, and then the injected DLL can access it. See Using Shared Memory in a Dynamic-Link Library.

CodePudding user response:

Thanks a lot to Remy Lebeau that help me to figure out the direction for the full solution. However, there are missing parts in his answer so here is the full answer I found:

The following code is copied from my app. Some parts may be missing such as methods in Win32Native class. All methods in Win32Native class are just collection of DllImport of native Win32 APIs. So you can find it all in the pvpoke website.

The host app that installs the hook:

using System;
using System.Runtime.InteropServices;
using WindowTop.Helpers;
using WindowTop.Managers;

namespace WindowTop.UI.Features.ShrinkBox
{
    public class ShrinkInteractHookNative
    {
        [DllImport("ShrinkInteractHook.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr InstallHook(uint threadId);
    }


    public class ShrinkInteractHook : ShrinkInteractHookNative
    {
        public static IntPtr InstallHook(WindowItem windowItem)
        {
            var processId = windowItem.ProcessId;
            var windowThread = Win32Native.GetWindowThreadProcessId(windowItem.Hwnd, ref processId);
            var hHook = InstallHook(windowThread);

            var WM_SEND_TARGET_HWND = Win32Native.RegisterWindowMessage("WINDOWTOP_WM_SEND_TARGET_HWND");

            Win32Native.PostThreadMessage(windowThread, WM_SEND_TARGET_HWND, UIntPtr.Zero,
                windowItem.Hwnd);

            return hHook;
        }

        public static void UnInstallHook(IntPtr hHook)
        {
            Win32Native.UnhookWindowsHookEx(hHook);
        }
    }
}

The DLL

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"


HINSTANCE hInstHookDll = NULL;
HHOOK hHook = NULL;


HWND targetHwnd = NULL;
UINT WM_SEND_TARGET_HWND = 0;




LRESULT CALLBACK GetMsgHookProc(int code, WPARAM wParam, LPARAM lParam)
{
    // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644981(v=vs.85)#parameters
    if (LOWORD(msg->message) == WM_SEND_TARGET_HWND)
    {
        targetHwnd = (HWND)msg->lParam; // Here we get the targetHwnd
    }
    
    // Specific processing goes here:
    // ...
    // ...

    // Pass this to the next hook
    return CallNextHookEx(hHook, code, wParam, lParam);
}


extern "C" {

__declspec(dllexport) HHOOK __stdcall InstallHook(unsigned int threadId)
{
    if (hHook != NULL)
    {
        UnhookWindowsHookEx(hHook);
        hHook = NULL;
    }

    return SetWindowsHookEx(WH_GETMESSAGE, GetMsgHookProc, hInstHookDll, threadId);
}
}


HANDLE pipe_file = NULL;
DWORD bytes_readed = 0;

BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID lpReserved)
{
    switch (reasonForCall)
    {
    case DLL_PROCESS_ATTACH:
        hInstHookDll = (HINSTANCE)hModule;
        WM_SEND_TARGET_HWND = RegisterWindowMessage(L"WINDOWTOP_WM_SEND_TARGET_HWND");
        break;
    case DLL_PROCESS_DETACH:
        // TODO: Logic in case the dll was unloaded 
        break;
    }
    return TRUE;
}

  • Related