如何在带有自定义数据的外线程上安装钩子程序?
How to install hook procedure on external thread with custom data?
在我的场景中,我有一个应用程序(我不拥有)有多个 windows 并且同一个线程拥有多个 windows。因此,当我使用 SetWindowsHookEx
安装挂钩程序(从我自己的应用程序,使用包含此程序的 DLL 文件)时,它将安装一个挂钩来监视同一线程拥有的所有 windows目标应用程序。
我要实现的是安装的钩子程序会有一个变量,例如targetHwnd
。一旦拥有它,它将忽略任何不是 targetHwnd
.
的东西
比如我会有这样的东西:
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);
}
问题是:
我如何得出这样的结论,即带有钩子过程的 DLL 被注入了这个外部信息,在我们的例子中是 targetHwnd
.
我没看到函数 SetWindowsHookEx
有自定义数据或类似参数的参数。
我想到的另一种方法是将 WM_COPYDATA
发送到我需要的 windows 之一(并且由我为其安装挂钩的外部应用程序线程拥有)。
然后,GetMsgHookProc
将通过 WM_COPYDATA
消息获取此数据。
如果在 DLL 中,targetHwnd
是 NULL
,它将从 lParam
参数中获取此数据。
但是我怎么知道是我的应用程序发送了它,还是只是为了以防万一另一个应用程序也向 window 发送了 WM_COPYDATA
?听起来不安全..
所以我认为我应该使用 wParam
(“window 传递数据的句柄”),然后通过调用 GetClassName
验证它是从我的应用程序发送的,并检查是否class name 是我期望的独一无二的名字。
但我不确定从 DLL 使用这些 API 是否安全,因为根据 MS,它可能不安全。
所以如果你能给我一个线索,我应该做什么,如果我认为这样做是正确的方法,这将对我有帮助。
谢谢。
WM_COPYDATA
跨线程边界和 DLL 边界使用是完全安全的。您可以使用 COPYDATASTRUCT::dwData
字段来识别消息是否属于您,不属于您,只需在该字段中存储一个唯一值即可。通常,RegisterWindowMessage()
用于该目的。
然而,WM_COPYDATA
对于这个任务来说有点矫枉过正。由于 HWND
直接适合 WPARAM
和 LPARAM
(由 WM_COPYDATA
证明),您可以发送(甚至 post)自定义消息,只有您的 DLL 挂钩知道如何处理的一个。同样,RegisterWindowMessage()
可用于该目的。
另一种选择是使用共享内存,例如通过 CreateFileMapping()
和 MapViewOfFile()
(或者,通过 #pragma data_seg
,如果您的编译器支持的话)。安装钩子的应用程序可以分配一块共享内存并将所需的 HWND
存储在其中,然后注入的 DLL 可以访问它。参见 Using Shared Memory in a Dynamic-Link Library。
非常感谢 Remy Lebeau 帮助我找到完整解决方案的方向。但是,他的回答中有遗漏的部分,所以这是我找到的完整答案:
以下代码是从我的应用程序中复制的。 Win32Native
class.
中的某些部分可能会丢失,例如方法
Win32Native
class 中的所有方法只是 DllImport
本机 Win32
API 的集合。所以你可以在pvpoke网站上找到它。
- 有一个名为
WindowItem
的习俗 class。你不会在任何地方找到它。
你应该知道的唯一相关信息是这个 class 有成员: Hwnd
- 我们挂钩的目标 window 的句柄,而 ProcessId
只是pid
拥有 window 的进程。我只在这段代码中使用这些成员
安装挂钩的主机应用:
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);
}
}
}
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);
}
}
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;
}
在我的场景中,我有一个应用程序(我不拥有)有多个 windows 并且同一个线程拥有多个 windows。因此,当我使用 SetWindowsHookEx
安装挂钩程序(从我自己的应用程序,使用包含此程序的 DLL 文件)时,它将安装一个挂钩来监视同一线程拥有的所有 windows目标应用程序。
我要实现的是安装的钩子程序会有一个变量,例如targetHwnd
。一旦拥有它,它将忽略任何不是 targetHwnd
.
比如我会有这样的东西:
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);
}
问题是:
我如何得出这样的结论,即带有钩子过程的 DLL 被注入了这个外部信息,在我们的例子中是 targetHwnd
.
我没看到函数 SetWindowsHookEx
有自定义数据或类似参数的参数。
我想到的另一种方法是将 WM_COPYDATA
发送到我需要的 windows 之一(并且由我为其安装挂钩的外部应用程序线程拥有)。
然后,GetMsgHookProc
将通过 WM_COPYDATA
消息获取此数据。
如果在 DLL 中,targetHwnd
是 NULL
,它将从 lParam
参数中获取此数据。
但是我怎么知道是我的应用程序发送了它,还是只是为了以防万一另一个应用程序也向 window 发送了 WM_COPYDATA
?听起来不安全..
所以我认为我应该使用 wParam
(“window 传递数据的句柄”),然后通过调用 GetClassName
验证它是从我的应用程序发送的,并检查是否class name 是我期望的独一无二的名字。
但我不确定从 DLL 使用这些 API 是否安全,因为根据 MS,它可能不安全。
所以如果你能给我一个线索,我应该做什么,如果我认为这样做是正确的方法,这将对我有帮助。
谢谢。
WM_COPYDATA
跨线程边界和 DLL 边界使用是完全安全的。您可以使用 COPYDATASTRUCT::dwData
字段来识别消息是否属于您,不属于您,只需在该字段中存储一个唯一值即可。通常,RegisterWindowMessage()
用于该目的。
然而,WM_COPYDATA
对于这个任务来说有点矫枉过正。由于 HWND
直接适合 WPARAM
和 LPARAM
(由 WM_COPYDATA
证明),您可以发送(甚至 post)自定义消息,只有您的 DLL 挂钩知道如何处理的一个。同样,RegisterWindowMessage()
可用于该目的。
另一种选择是使用共享内存,例如通过 CreateFileMapping()
和 MapViewOfFile()
(或者,通过 #pragma data_seg
,如果您的编译器支持的话)。安装钩子的应用程序可以分配一块共享内存并将所需的 HWND
存储在其中,然后注入的 DLL 可以访问它。参见 Using Shared Memory in a Dynamic-Link Library。
非常感谢 Remy Lebeau 帮助我找到完整解决方案的方向。但是,他的回答中有遗漏的部分,所以这是我找到的完整答案:
以下代码是从我的应用程序中复制的。 Win32Native
class.
Win32Native
class 中的所有方法只是DllImport
本机Win32
API 的集合。所以你可以在pvpoke网站上找到它。- 有一个名为
WindowItem
的习俗 class。你不会在任何地方找到它。 你应该知道的唯一相关信息是这个 class 有成员:Hwnd
- 我们挂钩的目标 window 的句柄,而ProcessId
只是pid
拥有 window 的进程。我只在这段代码中使用这些成员
安装挂钩的主机应用:
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);
}
}
}
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);
}
}
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;
}