{"id":2869,"date":"2024-01-13T18:15:18","date_gmt":"2024-01-13T23:15:18","guid":{"rendered":"https:\/\/badecho.com\/?p=2869"},"modified":"2024-01-17T11:17:27","modified_gmt":"2024-01-17T16:17:27","slug":"external-window-messages","status":"publish","type":"post","link":"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/","title":{"rendered":"Managed Interception of External Window Messages Using Win32 Hooks"},"content":{"rendered":"\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/ExternalWindowMessagesUsingHooks.png\" alt=\"Managed Interception of External Window Messages Using Win32 Hooks\" class=\"wp-image-2878\" width=\"856\" height=\"448\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/ExternalWindowMessagesUsingHooks.png 856w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/ExternalWindowMessagesUsingHooks-300x157.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/ExternalWindowMessagesUsingHooks-768x402.png 768w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/ExternalWindowMessagesUsingHooks-480x251.png 480w\" sizes=\"(max-width: 856px) 100vw, 856px\" \/><\/figure><\/div>\n\n\n\n<p>Hello, and Happy New Year! This article is the second in a series that looks at the interception of window messages with managed code and shall be my first article in this (hopefully glorious) new year of 2024.<\/p>\n\n\n\n<p>Whereas the first article focused on intercepting messages from windows belonging to the same process hosting our .NET code via subclassing, this article will look at a much spicier topic: viewing messages being sent to external, out-of-process windows using <em>Win32 hooks.<\/em><\/p>\n\n\n\n<p>This topic is extra spicy because, as you may or may not know, (the vast majority of) Win32 hooks are not supported in .NET. And by not supported, I mean they <em>do not work<\/em>. And for good reason. <\/p>\n\n\n\n<p>So how do we get them to work and spy on those juicy external window messages? Well, read on&#8230;<\/p>\n\n\n\n<h2>Receiving Out-Of-Process Window Messages<\/h2>\n\n\n\n<p>When thinking of intercepting messages sent to arbitrary windows, the first thing that comes to mind is the Spy++ tool that gets bundled with Visual Studio.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img loading=\"lazy\" width=\"762\" height=\"596\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/spyxx.png\" alt=\"Shows a screenshot of the Spy++ (spyxx.exe) tool.\" class=\"wp-image-2877\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/spyxx.png 762w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/spyxx-300x235.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/spyxx-480x375.png 480w\" sizes=\"(max-width: 762px) 100vw, 762px\" \/><figcaption>The original window message interception program!<\/figcaption><\/figure><\/div>\n\n\n\n<p>So, when asking myself how to intercept external window messages, what better teacher to learn from than this fabled debugging tool? I donned my research hat and did some Googling until I <a href=\"https:\/\/devblogs.microsoft.com\/cppblog\/spy-internals\/\" target=\"_blank\" rel=\"noreferrer noopener\">came across an older article written by a VC++ design engineer who worked on Spy++<\/a> that answered this very question.<\/p>\n\n\n\n<p>As the article explains, it intercepts messages through the use of three Win32 hooks, namely:<\/p>\n\n\n\n<ul><li><code>WH_CALLWNDPROC<\/code> (called prior to a window procedure executing)<\/li><li><code>WH_CALLWNDPROCRET<\/code> (called after a window procedure returns)<\/li><li><code>WH_GETMESSAGE<\/code> (called when a message is about to be returned from a message queue)<\/li><\/ul>\n\n\n\n<p><code>WH_CALLWNDPROC<\/code> is all we need to leverage to intercept external window messages, but we will also be spending some time looking at <code>WH_GETMESSAGE<\/code> for a bit of extra credit.<\/p>\n\n\n\n<h3>But, What Are Win32 Hooks Anyway?<\/h3>\n\n\n\n<p>Microsoft <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/winmsg\/hooks\" target=\"_blank\" rel=\"noreferrer noopener\">documentation refers to them simply as <em>hooks<\/em><\/a>; however, I find that much too general of a designation, so I find myself having to add the additional qualifier of &#8220;Win32&#8221;.<\/p>\n\n\n\n<p>Win32 hooks are a means to be notified of potentially system-wide events. You can <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/winmsg\/about-hooks\" target=\"_blank\" rel=\"noreferrer noopener\">find<\/a> general <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/api\/winuser\/nf-winuser-setwindowshookexw\" target=\"_blank\" rel=\"noreferrer noopener\">documentation<\/a> on their <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/winmsg\/using-hooks\" target=\"_blank\" rel=\"noreferrer noopener\">use<\/a> on Microsoft&#8217;s website.<\/p>\n\n\n\n<p>The use of Win32 hooks is an advanced Windows OS programming topic, where little mistakes can bring your whole system down to its knees! <\/p>\n\n\n\n<p>That isn&#8217;t what we should be worried about, however. Rather, we should be more concerned with the fact that we&#8217;re writing managed code, something that doesn&#8217;t get along nicely with Win32 hooks.<\/p>\n\n\n\n<h2>Why Win32 Hooks Don&#8217;t Work With .NET<\/h2>\n\n\n\n<p>External hook procedures are installed via the injection of DLLs containing said procedures into a target process or processes. There are a few issues when trying to do this with .NET: <\/p>\n\n\n\n<ul><li>Managed DLL assemblies cannot export functions like native DLLs can.<\/li><li>Processes running only unmanaged code and lacking a loaded .NET runtime cannot execute managed code.<\/li><\/ul>\n\n\n\n<p>There&#8217;s really no way around it: our hook procedure for capturing external window messages cannot be written in managed code.<\/p>\n\n\n\n<h3>A Deeper Look At Hook DLL Injection<\/h3>\n\n\n\n<p>Hook procedures are installed using the <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/api\/winuser\/nf-winuser-setwindowshookexw\" target=\"_blank\" rel=\"noreferrer noopener\"><code>SetWindowsHookEx<\/code><\/a> Win32 function and can be installed either to a specific thread or system-wide (resulting in a <em>global hook<\/em>).<\/p>\n\n\n\n<p>If either a thread not belonging to the current process (*<em>cough<\/em>* such as a thread an <em>external window<\/em> is running on) or a global hook is intended, then the <code>hMod<\/code> parameter of <code>SetWindowsHookEx<\/code> must be set to a handle to the DLL containing the hook procedure pointed to by the <code>lpfn<\/code> parameter.<\/p>\n\n\n\n<p>The DLL pointed to by <code>hMod<\/code> is injected into the target process or processes, with the expectation that the hook procedure is a DLL export that can act as a valid, consistent function to call into. <\/p>\n\n\n\n<p>.NET does not support DLL exports: managed DLL assemblies lack the necessary facilities that allow arbitrary processes to execute code via proffered &#8220;function pointers&#8221;. Managed code has no concept of consistent values for function pointers because marshalled function pointers are merely dynamically built proxies.<\/p>\n\n\n\n<p>Even if .NET DLLs did have such facilities, there is as good a chance as any that our targeted processes are unmanaged and do not have a loaded .NET runtime.<\/p>\n\n\n\n<h3>Some Hooks Do Work With .NET<\/h3>\n\n\n\n<p>Hooks that always run on the same thread as the code that installed them actually do work with .NET. That&#8217;s because there is no DLL injection required. Two examples of hooks like this are the low-level keyboard and mouse hooks.<\/p>\n\n\n\n<p>These kinds of hooks are unique in the sense that despite being global, they are run on the thread that installed them as opposed to the thread processing the hook.<\/p>\n\n\n\n<h2>A Native Hooks Library We Can Inject<\/h2>\n\n\n\n<p>If you thought you would get to spy on those juicy external window messages without having to step out of the safety of Land of the Managed Code, you were dead wrong my friend.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/CreatingNativeDll-1024x679.png\" alt=\"Shows us creating a native DLL project in Visual Studio.\" class=\"wp-image-2882\" width=\"1024\" height=\"679\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/CreatingNativeDll-1024x679.png 1024w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/CreatingNativeDll-300x199.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/CreatingNativeDll-768x509.png 768w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/CreatingNativeDll-480x318.png 480w, https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/CreatingNativeDll.png 1026w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption>Oh boy, time to wade into unmanaged waters.<\/figcaption><\/figure><\/div>\n\n\n\n<p>We will need to write a native DLL, and we&#8217;ll be doing it using good ol&#8217; C++ (though the code we write will be a bit more C-like as we&#8217;re not going to deal with creating any classes or the like). We&#8217;ll add the things we need it to do in order for it to intercept external window messages, and we&#8217;ll also take a look at how we can add support for other hook types.<\/p>\n\n\n\n<p>But, to start things off just what <em>does <\/em>it need to do?<\/p>\n\n\n\n<h3>Native Hooks Library Requirements<\/h3>\n\n\n\n<ul><li>Exports functions that will add\/remove Win32 hooks<\/li><li>Has some type of IPC allowing it to share data with other (injected) instances of itself<\/li><li>Has some kind of mechanism allowing for the native DLL to send message data back to a managed caller<\/li><li>Has the means for a managed caller to modify hook messages that are mutable (this depends on the hook type)<\/li><\/ul>\n\n\n\n<p>Lets examine each of the requirements and how we&#8217;ll fulfill them.<\/p>\n\n\n\n<h3>Function Exports For Adding\/Removing Win32 Hooks<\/h3>\n\n\n\n<p>We&#8217;ll have one function for installing the hook procedure, and one for uninstalling them.<\/p>\n\n\n\n<h6>Hooks.h &#8211; Adding\/Removing Hooks<\/h6>\n\n\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#define HOOKS_API extern &quot;C&quot; __declspec(dllexport)\n\n\/**\n * Installs a new Win32 hook procedure into the specified thread.\n * @param hookType The type of hook procedure to install.\n * @param threadId The identifier of the thread with which the hook procedure is to be associated.\n * @param destination A handle to the window that will receive messages sent to the hook procedure.\n * @return True if successful; otherwise, false.\n *\/\nHOOKS_API bool __cdecl AddHook(HookType hookType, int threadId, HWND destination);\n\n\/**\n * Uninstalls a Win32 hook procedure from the specified thread.\n * @param hookType The type of hook procedure to uninstall.\n * @param threadId The identifier of the thread to remove the hook procedure from.\n * @return True if successful; otherwise, false.\n *\/\nHOOKS_API bool __cdecl RemoveHook(HookType hookType, int threadId);\n<\/pre>\n\n\n<p>There are two exported functions for installing and uninstalling hook procedures shown above. The specific hook type installed\/uninstalled when invoking them depends on the <code>HookType<\/code> enum value provided.<\/p>\n\n\n\n<p>As one can judge by the signatures, these functions are for hooks targeting specific threads\/processes, as evidenced by the threadId. We&#8217;ll add some additional functions <\/p>\n\n\n\n<h6>Hooks.h &#8211; HookType Enum<\/h6>\n\n\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/**\n * Specifies a type of hook procedure.\n *\/\nenum HookType\n{\t\n\t\/**\n\t * Monitors \\c WH_CALLWNDPROC messages before the system sends them to a destination window\n\t * procedure.\n\t *\/\n\tCallWindowProcedure,\n\t\/**\n\t * Monitors \\c WH_CALLWNDPROCRET messages after they have been processed by the destination\n\t * window procedure.\n\t *\/\n\tCallWindowProcedureReturn,\n\t\/**\n\t * Monitors \\c WH_GETMESSAGE messages posted to a message queue prior to their retrieval.\n\t *\/\n\tGetMessage\n};\n<\/pre>\n\n\n<p>We only have three hook types specified in the above enum definition; however, this is all we&#8217;ll need for this article. <code>CallWindowProcedure<\/code> will be used to intercept external window messages, and we&#8217;ll also look at <code>GetMessage<\/code> and all the interesting things that emerge with its use.<\/p>\n\n\n\n<p>An individual callback for each hook procedure type is used.<\/p>\n\n\n\n<h6>Hooks.h &#8211; Hook Procedures<\/h6>\n\n\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/\/ Installable hook procedures.\nLRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam);\nLRESULT CALLBACK CallWndProcRet(int nCode, WPARAM wParam, LPARAM lParam);\nLRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam);\n<\/pre>\n\n\n<p>That&#8217;s not a typo in the first parameter for <code>GetMsgProc<\/code>&#8230;for whatever reason, Microsoft kept flip-flopping between using <code>code<\/code> and <code>nCode<\/code> for their various hook procedures (I like to be at parity with Microsoft&#8217;s docs, for the most part).<\/p>\n\n\n\n<p>We&#8217;ll go into implementations for all the above declarations later in the article; for now, let&#8217;s look at some of the other requirements our native Hooks DLL must fulfill.<\/p>\n\n\n\n<h3>Sharing Memory Between DLL Instances<\/h3>\n\n\n\n<p>Our managed code will call hook registration functions exported from our native Hooks library via P\/Invoke. These registration functions will call <code>SetWindowsHookEx<\/code> and provide a handle to the DLL that contains them for the <code>hMod<\/code> parameter, causing the Hooks DLL to be injected into the targeted process.<\/p>\n\n\n\n<p>These registration functions will also accept some configuration information, such as where or how to relay messages received by the hook procedure. However, the instance of our DLL processing the hook procedure will be in a separate process and share no memory with the instance we have configured via P\/Invoke.<\/p>\n\n\n\n<p>So, how do we get the configuration information over to the injected DLL instances? By&nbsp;<a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/memory\/creating-named-shared-memory\" target=\"_blank\" rel=\"noreferrer noopener\">sharing memory<\/a>, of course! Memory shall be shared by means of memory-mapped files that the system paging file stores.<\/p>\n\n\n\n<p>This is done by calling <code>CreateFileMapping<\/code>, passing <code>INVALID_HANDLE_VALUE<\/code> so the paging file is used, and then <code>MapViewOfFile<\/code> to associate the shared data with the virtual address space of the process. This will be done while inside the <code>DllMain<\/code> method.<\/p>\n\n\n\n<h6>DllMain.cpp &#8211; Shared Memory Initialization<\/h6>\n\n\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nBOOL APIENTRY DllMain(HINSTANCE instance, DWORD reason, LPVOID)\n{\n    BOOL init;\n\n    switch (reason)\n    {\n    \tcase DLL_PROCESS_ATTACH:        \n            Instance = instance;\n            FileMapping = CreateFileMapping(\n                INVALID_HANDLE_VALUE,\n                nullptr,\n                PAGE_READWRITE,\n                0,\n                SharedMemorySize,\n                TEXT(&quot;BadEcho.Hooks.FileMappingObject&quot;));            \n\n            if (FileMapping == nullptr)\n                return FALSE;\n\n            init = GetLastError() != ERROR_ALREADY_EXISTS;\n\n            SharedMemory\n    \t\t\t= MapViewOfFile(FileMapping, FILE_MAP_WRITE, 0, 0, 0);\n\n            if (SharedMemory == nullptr)\n                return FALSE;\n\n            SharedSectionMutex\n    \t\t\t= CreateMutex(nullptr, FALSE, TEXT(&quot;BadEcho.Hooks.MutexObject&quot;));\n\n            if (SharedSectionMutex == nullptr)\n                return FALSE;\n\n            if (init)\n                memset(SharedMemory, '\\0', SharedMemorySize);\n\n            SharedData = static_cast&lt;ThreadData*&gt;(SharedMemory);\n            break;\n\n        case DLL_THREAD_ATTACH:\n        case DLL_THREAD_DETACH:\n            break;   \t\n    \tcase DLL_PROCESS_DETACH:\n            UnmapViewOfFile(SharedMemory);\n            CloseHandle(FileMapping);\n            CloseHandle(SharedSectionMutex);\n            break;\n    \tdefault:\n            return FALSE;\n    }\n\n\treturn TRUE;    \n}\n<\/pre>\n\n\n<p>The above code will load the shared memory for every instance of the DLL being injected into a process, initializing the memory during the first <code>DllMain<\/code> invocation (typically from a P\/Invoke call).<\/p>\n\n\n\n<p>You&#8217;ll notice that the data stored in the shared memory is a pointer to a bunch of <code>ThreadData<\/code> objects. Let&#8217;s take a look at that.<\/p>\n\n\n\n<h6>Hook.h &#8211; ThreadData and HookData<\/h6>\n\n\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/**\n * Represents configurations settings for a hook procedure.\n *\/\nstruct HookData\n{\n\t\/**\n\t * A handle to the hook procedure.\n\t *\/\n\tHHOOK Handle;\n\t\/**\n\t * A handle to the window that hook messages will be sent to.\n\t *\/\n\tHWND Destination;\n};\n\n\/**\n * Represents shared hook data specific to a thread.\n *\/\nstruct ThreadData\n{\n\t\/**\n\t * The thread the data is associated with.\n\t *\/\n\tint ThreadId;\n\t\/**\n\t * The installed \\c WH_CALLWNDPROC hook procedure for the thread, if one exists.\n\t *\/\n\tHookData CallWndProcHook;\n\t\/**\n\t * The installed \\c WH_CALLWNDPROCRET hook procedure for the thread, if one exists.\n\t *\/\n\tHookData CallWndProcRetHook;\n\t\/**\n\t * The installed \\c WH_GETMESSAGE hook procedure for the thread, if one exists.\n\t *\/\n\tHookData GetMessageHook;\n};\n<\/pre>\n\n\n<p>Each thread targeted by one or more installed hooks gets allocated an associated <code>ThreadData<\/code> value. Each value contains the thread&#8217;s ID and the various <code>HookData<\/code> values for the hooks that we&#8217;ve registered.<\/p>\n\n\n\n<p><code>HookData<\/code> values contain the handle to the hook and where to send the message when our hook procedure is processing it &#8212; a topic we&#8217;ll cover now.<\/p>\n\n\n\n<h3>Sending Messages Back to Managed Code<\/h3>\n\n\n\n<p>One of the biggest seeming challenges here is the matter of relaying the intercepted messages from the process that is receiving them back to the one running our managed code. We need a way for unmanaged code running in one process to notify a managed component running in another.<\/p>\n\n\n\n<p>Not the simplest of problems to solve. One potential solution would be to have our managed code run as a managed out-of-proc COM server; however, that approach is, for lack of better words<em> a bit much<\/em>&#8230;<\/p>\n\n\n\n<p>Perhaps that&#8217;s something that can be explored later; until then, a much simpler solution would be to actually reroute the messages via a call to <code>SendMessage<\/code>, with the target <code>HWND<\/code> being a message-only window running in our managed process.<\/p>\n\n\n\n<p>This target <code>HWND<\/code> is what the <code>Destination<\/code> member of the relevant <code>HookData<\/code> will be set to, and it is provided as a parameter when calling the <code>AddHook<\/code> registration function.<\/p>\n\n\n\n<p>We&#8217;ll take a closer look at how we receive the messages on the managed side of things during the .NET counterpart of this article.<\/p>\n\n\n\n<h3>Modifying Mutable Hook Messages From Managed Code<\/h3>\n\n\n\n<p>Some hook types have message parameters that can be modified by the hook procedure, potentially affecting the behavior of the process we&#8217;ve injected into. If we wish to engage in this practice, we must devise a way to propagate the desired changes from our managed code following a message notification back to the appropriate DLL instance.<\/p>\n\n\n\n<p>The first thing we&#8217;ll need is another exported function that we can call from our managed code to modify the hook message&#8217;s details.<\/p>\n\n\n\n<h6>Hooks.h &#8211; Changing Message Details<\/h6>\n\n\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/**\n * Changes the details of a hook message currently being intercepted.\n * @param message The message identifier to use.\n * @param wParam Additional information about the message to use.\n * @param lParam Additional information about the message to use.\n * @note\n * This function should only be called from window procedures that handle hook types supporting\n * mutable messages.\n *\/\nHOOKS_API void __cdecl ChangeMessageDetails(UINT message, WPARAM wParam, LPARAM lParam);\n<\/pre>\n\n\n<p>The above function can be P\/Invoked while a managed window procedure set as a destination for a hook type supporting mutable messages is executing. The supplied parameter values will overwrite the values found in the message details struct passed in by the hook procedure.<\/p>\n\n\n\n<p>However, the DLL instance receiving this function call will be the one loaded into the managed caller&#8217;s process. How will it share these updated values with the DLL instance processing the hook message?<\/p>\n\n\n\n<p>There are a number of ways, but let us mix it up a bit and use DLL shared data segments.<\/p>\n\n\n\n<h6>Hooks.h &#8211; Shared Data Segments<\/h6>\n\n\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/\/ Add a data section to our binary file for variables we want shared across all injected\n\/\/ processes.\n\/\/ The variables that are shared mainly deal with the number of active hooks and message\n\/\/ parameters up for modification.\n#pragma data_seg(&quot;.shared&quot;)\ninline bool ChangeMessage = false;\ninline UINT ChangedMessage = 0;\ninline WPARAM ChangedWParam = 0;\ninline LPARAM ChangedLParam = 0;\ninline int ThreadCount = 0;\n#pragma data_seg()\n#pragma comment(linker, &quot;\/SECTION:.shared,RWS&quot;)\n<\/pre>\n\n\n<p>The PE format Windows uses for its EXE and DLL files defines the notion of&nbsp;<em>data sections<\/em>. The above code creates a new data section that will appear in our binary file and then instructs the linker that the just-created section should be marked as shared.<\/p>\n\n\n\n<p>By default, each process using a DLL has its own instance of all the DLL&#8217;s global and static variables. However, variables declared inside a shared data segment will be shared across all processes using the DLL.<\/p>\n\n\n\n<p>Note that the DLL instances must all originate from the exact same physical file for the shared data segment members to be shared. This will be the case for us, so we needn&#8217;t worry.<\/p>\n\n\n\n<p>Using a memory-mapped file like we do with our <code>ThreadData<\/code> would also work here, but again, I wanted to mix it up a bit (not to mention that this approach simplifies things).<\/p>\n\n\n\n<p>However, we will need to provide synchronization in the form of a mutex to support having the particular hook procedure installed in multiple processes.<\/p>\n\n\n\n<h3>Function Definitions<\/h3>\n\n\n\n<p>With the groundwork laid out, let&#8217;s take a look at the actual body of code for the various functions mentioned above.<\/p>\n\n\n\n<h6>DllMain.cpp &#8211; Function Definitions<\/h6>\n\n\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nThreadData *GetLocalData(int threadId, bool addEntry)\n{\n    int index;\n\n\tfor (index = 0; index &lt; ThreadCount; index++)\n    {\n        if (SharedData[index].ThreadId == threadId)\n            break;\n    }\n\n    \/\/ Thread not registered -- attempt to initialize data.\n    if (index == ThreadCount)\n    {\n        if (!addEntry || ThreadCount == MaxThreads)\n            return nullptr;\n\n        SharedData[index].ThreadId = threadId;\n\n        SharedData[index].CallWndProcHook.Handle = nullptr;\n        SharedData[index].CallWndProcHook.Destination = nullptr;\n        SharedData[index].CallWndProcRetHook.Handle = nullptr;\n        SharedData[index].CallWndProcRetHook.Destination = nullptr;\n        SharedData[index].GetMessageHook.Handle = nullptr;\n        SharedData[index].GetMessageHook.Destination = nullptr;\n\n        \/\/ Synchronization is required as multiple processes may be attempting to increment the\n        \/\/ thread count.\n        WaitForSingleObject(SharedSectionMutex, INFINITE);\n        ThreadCount++;\n        ReleaseMutex(SharedSectionMutex);\n    }\n\n    return &amp;SharedData[index];\n}\n\nbool __cdecl AddHook(HookType hookType, int threadId, HWND destination)\n{\n    ThreadData* localData = GetLocalData(threadId, true);\n\n    if (localData == nullptr)\n        return false;\n    \n    HookData* hookData;\n    int idHook;\n    HOOKPROC lpfn;\n\n    switch (hookType)\n    {\n    \tcase CallWindowProcedure:\n            hookData = &amp;localData-&gt;CallWndProcHook;\n            idHook = WH_CALLWNDPROC;\n            lpfn = CallWndProc;\n            break;\n\n    \tcase CallWindowProcedureReturn:\n            hookData = &amp;localData-&gt;CallWndProcRetHook;\n            idHook = WH_CALLWNDPROCRET;\n            lpfn = CallWndProcRet;\n            break;\n\n    \tcase GetMessage:\n            hookData = &amp;localData-&gt;GetMessageHook;\n            idHook = WH_GETMESSAGE;\n            lpfn = GetMsgProc;\n            break;\n\n\t\tdefault:\n            return false;\n    }\n\n    HHOOK hook = SetWindowsHookEx(idHook, lpfn, Instance, threadId);\n\n    if (hook == nullptr)\n        return false;\n\n    hookData-&gt;Handle = hook;\n    hookData-&gt;Destination = destination;\n\n    return true;\n}\n\nbool __cdecl RemoveHook(HookType hookType, int threadId)\n{\n    ThreadData* localData = GetLocalData(threadId, false);\n\n    if (localData == nullptr)\n        return false;\n\n    HookData* hookData;\n\n    switch (hookType)\n    {\n\t\tcase CallWindowProcedure:\n            hookData = &amp;localData-&gt;CallWndProcHook;\n            break;\n\t\tcase CallWindowProcedureReturn:\n            hookData = &amp;localData-&gt;CallWndProcRetHook;\n            break;\n\t\tcase GetMessage:\n            hookData = &amp;localData-&gt;GetMessageHook;\n            break;\n\t\tdefault:\n            return false;\n    }\n\n    if (hookData-&gt;Handle == nullptr)\n        return false;\n\n    bool result = UnhookWindowsHookEx(hookData-&gt;Handle);\n\n    hookData-&gt;Handle = nullptr;\n    hookData-&gt;Destination = nullptr;\n\n    return result;\n}\n\nvoid __cdecl ChangeMessageDetails(UINT message, WPARAM wParam, LPARAM lParam)\n{\n    ChangedMessage = message;\n    ChangedWParam = wParam;\n    ChangedLParam = lParam;\n    ChangeMessage = true;\n}\n\n\nLRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)\n{\n    int threadId = static_cast&lt;int&gt;(GetCurrentThreadId());\n\n    if (ThreadData* localData = GetLocalData(threadId, false); nCode == HC_ACTION)\n    {\n        HWND destination = localData-&gt;CallWndProcHook.Destination;\n\n        auto messageParameters = PointTo&lt;CWPSTRUCT&gt;(lParam);\n\n        if (destination != nullptr)\n        {\n            SendMessage(\n                destination, \n                messageParameters-&gt;message, \n                messageParameters-&gt;wParam, \n                messageParameters-&gt;lParam);\n        }\n    }\n\n    return CallNextHookEx(nullptr, nCode, wParam, lParam);\n}\n\nLRESULT CALLBACK CallWndProcRet(int nCode, WPARAM wParam, LPARAM lParam)\n{\n    int threadId = static_cast&lt;int&gt;(GetCurrentThreadId());    \n\n    if (ThreadData* localData = GetLocalData(threadId, false); nCode == HC_ACTION)\n    {\n        HWND destination = localData-&gt;CallWndProcRetHook.Destination;\n\n        auto messageParameters = PointTo&lt;CWPRETSTRUCT&gt;(lParam);\n\n        if (destination != nullptr)\n        {\n            SendMessage(\n                destination, \n                messageParameters-&gt;message, \n                messageParameters-&gt;wParam, \n                messageParameters-&gt;lParam);\n        }\n    }\n\n    return CallNextHookEx(nullptr, nCode, wParam, lParam);\n}\n\nLRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)\n{\n    int threadId = static_cast&lt;int&gt;(GetCurrentThreadId());    \n\n    if (ThreadData* localData = GetLocalData(threadId, false); code == HC_ACTION)\n    {   \n        if (HWND destination = localData-&gt;GetMessageHook.Destination; destination != nullptr)\n        {\n            \/\/ Unlike some of these other hooks, we are able to modify messages of this hook type\n            \/\/ before control is returned to the system.\n            auto messageParameters = PointTo&lt;MSG&gt;(lParam);\n\n            WaitForSingleObject(SharedSectionMutex, INFINITE);\n\n            __try\n            {\n                ChangeMessage = false;\n\n                SendMessage(\n                    destination, \n                    messageParameters-&gt;message, \n                    messageParameters-&gt;wParam, \n                    messageParameters-&gt;lParam);\n\n                if (ChangeMessage)\n                {\n                    messageParameters-&gt;message = ChangedMessage;\n                    messageParameters-&gt;wParam = ChangedWParam;\n                    messageParameters-&gt;lParam = ChangedLParam;\n                }\n            }\n            __finally\n            {\n                ReleaseMutex(SharedSectionMutex);\n            }\n        }        \n    }\n\n    return CallNextHookEx(nullptr, code, wParam, lParam);\n}\n<\/pre>\n\n\n<p>These functions are pretty straightforward and fulfill the expectations we laid out in the previous sections. You can find the complete, up-to-date source code for the <a href=\"https:\/\/github.com\/BadEcho\/core\/tree\/master\/src\/Hooks\" target=\"_blank\" rel=\"noreferrer noopener\">BadEcho.Hooks library here<\/a>.<\/p>\n\n\n\n<p>Let us now leap out of the unmanaged universe and dive into the managed code counterpart to all of this C++ tomfoolery.<\/p>\n\n\n\n<h2>Wrapping an External Window and Its Messages<\/h2>\n\n\n\n<p>In the <a href=\"https:\/\/badecho.com\/index.php\/2023\/12\/29\/local-window-messages\/\" target=\"_blank\" rel=\"noreferrer noopener\">previous article of this series<\/a>, we looked at classes concerned with <a href=\"https:\/\/badecho.com\/index.php\/2023\/12\/29\/local-window-messages\/#window-wrapper\" target=\"_blank\" rel=\"noreferrer noopener\">wrapping a local (in-process) window&#8217;s handle<\/a> and the messages it receives, with the star of the show being the <code>LocalWindowWrapper<\/code> class.<\/p>\n\n\n\n<p>To intercept messages from out-of-proc windows, we&#8217;ll create another derivation of the <code>WindowWrapper<\/code> base class, which <code>LocalWindowWrapper<\/code> shares as an ancestor: the <code>GlobalWindowWrapper<\/code>.<\/p>\n\n\n\n<h3>How It Will Work<\/h3>\n\n\n\n<p>The <code>GlobalWindowWrapper<\/code> will be similar to the <code>LocalWindowWrapper<\/code> type in that it will take a <code>HWND<\/code>, or <code>IntPtr<\/code> if you will, to &#8220;wrap&#8221; as well as offering us the ability to add our custom hooks to the window procedure via the <code>AddHook<\/code>\/<code>RemoveHook <\/code>methods.<\/p>\n\n\n\n<p><code>LocalWindowWrapper<\/code> used subclassing to intercept the window&#8217;s messages; however, that option is not available for <code>GlobalWindowWrapper<\/code> because the windows are in a different process. Instead of subclassing, we will be P\/Invoking the various unmanaged function exports we just wrote.<\/p>\n\n\n\n<p>To do that, we need to find the ID of the thread that the external window is running on (easy with the <code>GetWindowThreadProcessId<\/code> function), but we&#8217;ll also need to provide a destination <code>HWND<\/code> for our native DLL to use to relay an intercepted message back on.<\/p>\n\n\n\n<p>This destination <code>HWND<\/code> must be an independent window local to our managed code&#8217;s process. There&#8217;s a good chance that (unless we happen to be a desktop application) we don&#8217;t have a spare <code>HWND<\/code> and an equally good chance that we don&#8217;t even want one.<\/p>\n\n\n\n<p>We need a window that is not a window; no, not Schr\u00f6dinger&#8217;s cat, but a <em>message-only window<\/em>.<\/p>\n\n\n\n<h3>Wrapping a Message-Only Window<\/h3>\n\n\n\n<p>A message-only window is one with the singular purpose of receiving window messages. It has no physical manifestation; no shape, size, look, or feel. There&#8217;s a good chance you have many, many message-only windows spawned on your desktop this <em>very <\/em>instant, deviously lurking in the shadows.<\/p>\n\n\n\n<p>To create one from a .NET codebase, we&#8217;ll once again take advantage of our window wrapping object hierarchy we started in the previous article. Deriving from <code>WindowWrapper<\/code>, we have a <code>MessageOnlyWindowWrapper<\/code> type, which provides window procedure hook management for a message-only window.<\/p>\n\n\n\n<p>Initializing a <code>MessageOnlyWindowWrapper<\/code> will create and manage a message-only window. Details about how message-only windows work and how to make them are outside this article&#8217;s scope, so please refer to the <a href=\"https:\/\/github.com\/BadEcho\/core\/blob\/master\/src\/Common\/Interop\/MessageOnlyWindowWrapper.cs\" target=\"_blank\" rel=\"noreferrer noopener\">source of <code>MessageOnlyWindowWrapper<\/code> here<\/a>.<\/p>\n\n\n\n<h3>Dead Wrappers<\/h3>\n\n\n\n<p>The <code>MessageOnlyWindowWrapper<\/code> type will conduct all steps required to successfully create a message-only window whose window procedure points to our managed <code>WindowWrapper.WindowProcedure<\/code>.<\/p>\n\n\n\n<p>So, while that might seem to take care of everything, sorry, it doesn&#8217;t. In fact, if we try to use an instance of this type as the destination HWND, we&#8217;ll never see any messages appear in our managed window procedure.<\/p>\n\n\n\n<p>Remember, all the windows we&#8217;ve been wrapping thus far have been existing windows managed by code independent of ours. This is the first time we&#8217;ve actually created our own, and we&#8217;re missing a rather essential component to the correct operation of a window, something I <a href=\"https:\/\/badecho.com\/index.php\/2023\/12\/29\/local-window-messages#message-loops\" target=\"_blank\" rel=\"noreferrer noopener\">touched on briefly in the previous article<\/a>.<\/p>\n\n\n\n<p>That&#8217;s right; we need to man the pump: the window message pump! A window does nothing if its thread&#8217;s message queue isn&#8217;t being pumped in a loop.<\/p>\n\n\n\n<h3>But Doesn\u2019t SendMessage Bypass the Message Queue?<\/h3>\n\n\n\n<p>Yes! Using the <code>SendMessage<\/code> function (as we&#8217;re doing here) results in the message being sent to the window procedure, skipping the message queue. However, one needs to pay careful attention to the docs:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-style-plain\"><p>If the specified window was created by the calling thread, the window procedure is called immediately as a subroutine. If the specified window was created by a different thread, the system switches to that thread and calls the appropriate window procedure. <strong>Messages sent between threads are processed only when the receiving thread executes message retrieval code<\/strong>.<\/p><\/blockquote>\n\n\n\n<p>The bolded portion above is the most critical part. However, what exactly is &#8220;message retrieval code&#8221;? Simply put: a call to the <code>GetMessage<\/code> function, itself part and parcel of a Win32 message loop. If this does not occur, no cross-thread message, even ones sent by <code>SendMessage<\/code>, will be processed.<\/p>\n\n\n\n<p>With that clarified, we need an object type that can provide the requisite message retrieval code, and the <code>MessageOnlyExecutor<\/code> implementation of <code>IThreadExecutor<\/code> fits the bill quite nicely.<\/p>\n\n\n\n<h3 id=\"executors\">Executors and Happy Wrappers<\/h3>\n\n\n\n<p>The <code>MessageOnlyExecutor<\/code>, which uses a <code>MessageOnlyWindowWrapper<\/code> instance, is an involved class that will handle dispatching messages while allowing us to execute operations and receive window messages on a dedicated thread.<\/p>\n\n\n\n<p><code>MessageOnlyExecutor<\/code> was inspired by WPF&#8217;s <code>Dispatcher<\/code> class and is quite involved &#8212; to learn how it works, <a href=\"https:\/\/github.com\/BadEcho\/core\/blob\/master\/src\/Common\/Interop\/MessageOnlyExecutor.cs\" target=\"_blank\" rel=\"noreferrer noopener\">please consult its source here<\/a>.<\/p>\n\n\n\n<p>It does many things above and beyond the minimum required for dispatching window messages; you could certainly whip up a stripped-down version. Regardless of what you do, we&#8217;ll need it for our hook procedures to talk back to us.<\/p>\n\n\n\n<p>With the final requirement of our native functions satisfied, let&#8217;s get to P\/Invoking.<\/p>\n\n\n\n<h3>P\/Invoke Declarations<\/h3>\n\n\n\n<p>We spent all that time writing those native DLL exports; it&#8217;d be a shame to let them go to waste, so let us toss a P\/Invoke function for each one in a new <code>Hooks<\/code> class.<\/p>\n\n\n\n<h6>Hooks.cs<\/h6>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Provides interoperability with the Bad Echo Win32 hooks API.\n\/\/\/ &lt;\/summary&gt;\ninternal static partial class Hooks\n{\n    private const string LIBRARY_NAME = &quot;BadEcho.Hooks&quot;;\n    \n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Installs a new Win32 hook procedure into the specified thread.\n    \/\/\/ &lt;\/summary&gt;\n    \/\/\/ &lt;param name=&quot;hookType&quot;&gt;The type of hook procedure to install.&lt;\/param&gt;\n    \/\/\/ &lt;param name=&quot;threadId&quot;&gt;\n    \/\/\/ The identifier of the thread with which the hook procedure is to be associated.\n    \/\/\/ &lt;\/param&gt;\n    \/\/\/ &lt;param name=&quot;destination&quot;&gt;\n    \/\/\/ A handle to the window that will receive messages sent to the hook procedure.\n    \/\/\/ &lt;\/param&gt;\n    \/\/\/ &lt;returns&gt;True if successful; otherwise, false.&lt;\/returns&gt;\n    [LibraryImport(LIBRARY_NAME, SetLastError = true)]\n    [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })]\n    [return: MarshalAs(UnmanagedType.U1)]\n    [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]\n    public static partial bool AddHook(HookType hookType, int threadId, WindowHandle destination);\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Uninstalls a Win32 hook procedure from the specified thread.\n    \/\/\/ &lt;\/summary&gt;\n    \/\/\/ &lt;param name=&quot;hookType&quot;&gt;The type of hook procedure to uninstall.&lt;\/param&gt;\n    \/\/\/ &lt;param name=&quot;threadId&quot;&gt;\n    \/\/\/ The identifier of the thread to remove the hook procedure from.\n    \/\/\/ &lt;\/param&gt;\n    \/\/\/ &lt;returns&gt;True if successful; otherwise, false.&lt;\/returns&gt;\n    [LibraryImport(LIBRARY_NAME, SetLastError = true)]\n    [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })]\n    [return: MarshalAs(UnmanagedType.U1)]\n    [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]\n    public static partial bool RemoveHook(HookType hookType, int threadId);\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Changes the details of a hook message currently being intercepted.\n    \/\/\/ &lt;\/summary&gt;\n    \/\/\/ &lt;param name=&quot;message&quot;&gt;The message identifier to use.&lt;\/param&gt;\n    \/\/\/ &lt;param name=&quot;wParam&quot;&gt;Additional information about the message to use.&lt;\/param&gt;\n    \/\/\/ &lt;param name=&quot;lParam&quot;&gt;Additional information about the message to use.&lt;\/param&gt;\n    [LibraryImport(LIBRARY_NAME)]\n    [UnmanagedCallConv(CallConvs = new [] { typeof(CallConvCdecl)})]\n    [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]\n    public static partial void ChangeMessageDetails(uint message, IntPtr wParam, IntPtr lParam);\n}\n<\/pre>\n\n\n<p>Pretty much carbon copies of the C++ declarations, mixed with a bit of that ol&#8217; type marshalling jazz.<\/p>\n\n\n\n<p>Okay, time for the big boy: the <code>GlobalWindowWrapper<\/code>.<\/p>\n\n\n\n<h3>The Out-Of-Process Window Wrapper<\/h3>\n\n\n\n<p>Alright, everything is in place for us to finally create our <code>GlobalWindowWrapper<\/code> class, which is yet another derivation of <code>WindowWrapper<\/code>.<\/p>\n\n\n\n<h6>GlobalWindowWrapper.cs<\/h6>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Provides a wrapper around an &lt;c&gt;HWND&lt;\/c&gt; of a provided out-of-process window and the messages\n\/\/\/ it receives.\n\/\/\/ &lt;\/summary&gt;\n\/\/\/ &lt;remarks&gt;\n\/\/\/ &lt;para&gt;\n\/\/\/ Window messages intended for other applications cannot be intercepted via the usual technique\n\/\/\/ of subclassing, as it is not possible to subclass a window created by and running on another\n\/\/\/ process.\n\/\/\/ &lt;\/para&gt;\n\/\/\/ &lt;para&gt;\n\/\/\/ Instead, several global hook procedures are installed and associated with the thread owning\n\/\/\/ the wrapped window. This provides us with all pertinent window message traffic, even across\n\/\/\/ process boundaries. Registering a callback for a global hook procedure requires more than\n\/\/\/ just a function pointer to a managed delegate, however. Global hooks require the injection\n\/\/\/ of a native, standard Windows DLL containing the hook procedure into a target process.\n\/\/\/ &lt;\/para&gt;\n\/\/\/ &lt;para&gt; \n\/\/\/ Self-injection of this assembly isn't going to cut it, as a managed DLL simply cannot be\n\/\/\/ loaded in an unmanaged environment, which will fail to load our not-so-standard DLLs due\n\/\/\/ to the lack of a DllMain entry point. \n\/\/\/ &lt;\/para&gt;\n\/\/\/ &lt;para&gt;\n\/\/\/ Luckily for us, or maybe just me, we have the native BadEcho.Hooks library, written in C++\n\/\/\/ and super injectable! If this DLL can be located for the necessary platform invokes, the\n\/\/\/ necessary hooks are established and instances of said native DLL are loaded into the\n\/\/\/ address space of our target processes.\n\/\/\/ &lt;\/para&gt;\n\/\/\/ &lt;para&gt;\n\/\/\/ In order for our hooking DLL to be able to communicate back to our managed code, we create\n\/\/\/ a message-only window that is set up to receive messages from unmanaged-land.\n\/\/\/ So...there you go. Spy away!\n\/\/\/ &lt;\/para&gt;\n\/\/\/ &lt;\/remarks&gt;\npublic sealed class GlobalWindowWrapper : WindowWrapper, IDisposable\n{\n    private readonly MessageOnlyExecutor _hookExecutor = new();\n    private readonly int _threadId;\n    \n    private bool _disposed;\n    private bool _windowHooked;\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Initializes a new instance of the &lt;see cref=&quot;GlobalWindowWrapper&quot;\/&gt; class.\n    \/\/\/ &lt;\/summary&gt;\n    \/\/\/ &lt;param name=&quot;handle&quot;&gt;A handle to the window being wrapped.&lt;\/param&gt;\n    public GlobalWindowWrapper(WindowHandle handle) \n        : base(handle)\n    {\n        _threadId = (int) User32.GetWindowThreadProcessId(Handle, IntPtr.Zero);\n\n        if (_threadId == 0)\n            throw new Win32Exception(Marshal.GetLastWin32Error());\n    }\n\n    \/\/\/ &lt;inheritdoc\/&gt;\n    public void Dispose()\n    {\n        if (_disposed)\n            return;\n\n        CloseHook();\n\n        _hookExecutor.Dispose();\n        \n        _disposed = true;\n    }\n\n    \/\/\/ &lt;inheritdoc\/&gt;\n    protected override void OnHookAdded(WindowHookProc addedHook)\n    {\n        base.OnHookAdded(addedHook);\n\n        InitializeHook();\n    }\n\n    \/\/\/ &lt;inheritdoc\/&gt;\n    protected override void OnDestroyingWindow()\n    {\n        base.OnDestroyingWindow();\n\n        CloseHook();\n    }\n\n    private async void InitializeHook()\n    {\n        if (_hookExecutor.Window != null)\n            return;\n         \n        await _hookExecutor.RunAsync();\n            \n        if (_hookExecutor.Window == null)\n            throw new InvalidOperationException(Strings.MessageQueueForHookFailed);\n\n        _hookExecutor.Window.AddHook(WindowProcedure);\n\n        _windowHooked = Hooks.AddHook(HookType.CallWindowProcedure,\n                                      _threadId,\n                                      _hookExecutor.Window.Handle);\n    }\n\n    private void CloseHook()\n    {\n        if (!_windowHooked)\n            return;\n\n        _windowHooked = !Hooks.RemoveHook(HookType.CallWindowProcedure, _threadId);\n\n        if (_windowHooked)\n            Logger.Warning(Strings.UnhookWindowFailed.InvariantFormat(_threadId));\n    }\n}\n<\/pre>\n\n\n<p>When the external window is about to receive a message, our hook procedure will send said message to the <code>MessageOnlyWindowWrapper.WindowProcedure<\/code> being powered by our local <code>MessageOnlyExecutor<\/code>. Because our <code>GlobalWindowWrapper<\/code> added a hook to the message-only window, it will be passed along to the <code>GlobalWindowWrapper.WindowProcedure<\/code>, which will then of course share it with its own registered hooks.<\/p>\n\n\n\n<p>In other words, mission complete! This code will likely have some features and needed contingencies added to it, but it does the job in the tests I&#8217;ve run. You can find the up-to-date source for this class among its brethren at the <a href=\"https:\/\/github.com\/BadEcho\/core\/blob\/master\/src\/Common\/Interop\/GlobalWindowWrapper.cs\" target=\"_blank\" rel=\"noreferrer noopener\">ol&#8217; Bad Echo repository<\/a>. <\/p>\n\n\n\n<p>In the&nbsp;<strong>final<\/strong>&nbsp;article in this series of window message interception using managed code, we&#8217;ll be taking a look at how, through another implementation of <code>IMessageSource&lt;T&gt;<\/code>, we can add support for the <code>WH_GETMESSAGE<\/code> hook type, which features a different hook procedure signature and supports mutable message details.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hello, and Happy New Year! This article is the second in a series that looks at the interception of window [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[10],"tags":[41,42,85,84],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v14.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\r\n<title>.NET Interception of External Window Messages w\/ Win32 Hooks<\/title>\r\n<meta name=\"description\" content=\"Shows how, with managed code, we can intercept messages being sent to out-of-process windows using Win32 hooks.\" \/>\r\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\r\n<link rel=\"canonical\" href=\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/\" \/>\r\n<meta property=\"og:locale\" content=\"en_US\" \/>\r\n<meta property=\"og:type\" content=\"article\" \/>\r\n<meta property=\"og:title\" content=\".NET Interception of External Window Messages w\/ Win32 Hooks\" \/>\r\n<meta property=\"og:description\" content=\"Shows how, with managed code, we can intercept messages being sent to out-of-process windows using Win32 hooks.\" \/>\r\n<meta property=\"og:url\" content=\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/\" \/>\r\n<meta property=\"og:site_name\" content=\"omni&#039;s hackpad\" \/>\r\n<meta property=\"article:published_time\" content=\"2024-01-13T23:15:18+00:00\" \/>\r\n<meta property=\"article:modified_time\" content=\"2024-01-17T16:17:27+00:00\" \/>\r\n<meta property=\"og:image\" content=\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/ExternalWindowMessagesUsingHooks.png\" \/>\r\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\r\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/badecho.com\/#website\",\"url\":\"https:\/\/badecho.com\/\",\"name\":\"omni&#039;s hackpad\",\"description\":\"Game Code Disassembly. Omnified Modification. Madness.\",\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/badecho.com\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/badecho.com\/wp-content\/uploads\/2024\/01\/ExternalWindowMessagesUsingHooks.png\",\"width\":856,\"height\":448,\"caption\":\"Managed Interception of External Window Messages Using Win32 Hooks\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/#webpage\",\"url\":\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/\",\"name\":\".NET Interception of External Window Messages w\/ Win32 Hooks\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/#primaryimage\"},\"datePublished\":\"2024-01-13T23:15:18+00:00\",\"dateModified\":\"2024-01-17T16:17:27+00:00\",\"description\":\"Shows how, with managed code, we can intercept messages being sent to out-of-process windows using Win32 hooks.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/\"]}]},{\"@type\":\"Article\",\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/#webpage\"},\"author\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"headline\":\"Managed Interception of External Window Messages Using Win32 Hooks\",\"datePublished\":\"2024-01-13T23:15:18+00:00\",\"dateModified\":\"2024-01-17T16:17:27+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/#webpage\"},\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"image\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/#primaryimage\"},\"keywords\":\".NET,C#,C++,Win32\",\"articleSection\":\"General Dev\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/badecho.com\/index.php\/2024\/01\/13\/external-window-messages\/#respond\"]}]},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\",\"name\":\"Matt Weber\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/7e345ac2708b3a41c7bd70a4a0440d41?s=96&d=mm&r=g\",\"caption\":\"Matt Weber\"},\"logo\":{\"@id\":\"https:\/\/badecho.com\/#personlogo\"}}]}<\/script>\r\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2869"}],"collection":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/comments?post=2869"}],"version-history":[{"count":46,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2869\/revisions"}],"predecessor-version":[{"id":2990,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2869\/revisions\/2990"}],"wp:attachment":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/media?parent=2869"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/categories?post=2869"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/tags?post=2869"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}