全局挂钩 DLL 仅在 C# 主窗口处于活动状态/前台时调用 C# 回调方法

Global hook DLL only calls C# callback method when the C# main window is active/foreground

本文关键字:前台 活动状态 方法 回调 调用 DLL 仅在 窗口 全局      更新时间:2023-10-16

我有一个 C# 应用程序可以打开一个C++全局键钩。dll 全局侦听按键,并在按下某个键时调用主应用回调方法。

dll 挂钩进程有效,只是当主应用程序不是活动屏幕时它不会调用回调函数,这在这种状态下毫无意义。有解决方法吗?我可能错过了一些东西。

C# 代码:

//Global Key DLL callback delegate
public delegate void KeyCallBackFunction(int keyPoints);
[DllImport("Dll1_64.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern void SetCallBack(IntPtr pCallBack);
IntPtr hInstance = IntPtr.Zero;
private delegate void HookSetting();

private void Window_Loaded(object sender, RoutedEventArgs e)
{
...
KeyCallBackFunction keyPointDelegate = new KeyCallBackFunction(DetectKey);
// Use GCHandle to hold the delegate object in memory.
GCHandle gch = GCHandle.Alloc(keyPointDelegate);
// Obtain an unmanaged function pointer for the delegate as usual.
IntPtr intptr_delegate = Marshal.GetFunctionPointerForDelegate(keyPointDelegate);
// Callback is setup as a thread
Thread keyThread= new Thread(() => SetCallBack(intptr_delegate));
keyThread.Start();

// Global hook Setup
hInstance = LoadLibrary("Dll1_64");
hProc = GetProcAddress(hInstance, "SetHook");
HookSetting hookset = (HookSetting)Marshal.GetDelegateForFunctionPointer(hProc, typeof(HookSetting));
hookset();
}
public static void DetectKey(int point)
{
System.Windows.Application.Current.Dispatcher.Invoke(new Action(() =>
{
if (keyPoints == 0)
{
// do something for key point zero
}
else
{
// do something for other key points
}
}));
}

C++全局键钩子 dll:

#include "pch.h"
#include <stdio.h>
#include <ctime>
//these variables will be shared among all processes to which this dll is linked
#pragma data_seg("Shared")
//our hook handle which will be returned by calling SetWindowsHookEx function
HHOOK hkKey = NULL;
HINSTANCE hInstHookDll = NULL;  //our global variable to store the instance of our DLL
#pragma data_seg() //end of our data segment
#pragma comment(linker,"/section:Shared,rws")
// Tell the compiler that Shared section can be read,write and shared
HWND pHWnd = NULL;
typedef void(__stdcall* PCallBack)(int points);
PCallBack g_pCallBack = NULL;
// used to register callback
extern "C" __declspec(dllexport) void __stdcall SetCallBack(PCallBack pCallBack)
{
g_pCallBack = pCallBack;
}
// call callbacak function
extern "C" __declspec(dllexport) void __stdcall PerformAction(int points)
{
if (g_pCallBack)
{
g_pCallBack(points);
}
}
//this is the hook procedure
__declspec(dllexport) LRESULT CALLBACK procCharMsg(int nCode, WPARAM wParam, LPARAM lParam)
{
//a pointer to hold the MSG structure that is passed as lParam
MSG* msg;
//to hold the character passed in the MSG structure's wParam
char charCode;
if (nCode >= 0 && nCode == HC_ACTION)
//if nCode is less than 0 or nCode
//is not HC_ACTION we will call CallNextHookEx
{
//lParam contains pointer to MSG structure.
msg = (MSG*)lParam;
if (msg->message == WM_CHAR)
{
charCode = msg->wParam;
if (IsCharLower(charCode))
//we check if the character pressed is a small letter
{
//if so, make it to capital letter
charCode -= 32;
msg->wParam = (WPARAM)charCode;
//overwrite the msg structure's wparam 
//with our new value. 
}
if (charCode == 'A') {
PerformAction(0); // Point Zero
}
else if (charCode == 'B') {
PerformAction(1); // Point One
}
else {
PerformAction(2); // Point Two
}
}
}
return CallNextHookEx(hkKey, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void __stdcall SetHook()
{
if (hkKey == NULL)
hkKey = SetWindowsHookEx(WH_GETMESSAGE, procCharMsg, hInstHookDll, 0);
}
//remove the hook
extern "C" __declspec(dllexport) void __stdcall RemoveHook()
{
if (hkKey != NULL)
UnhookWindowsHookEx(hkKey);
hkKey = NULL;
}
INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved) {
switch (Reason)
{
case DLL_PROCESS_ATTACH:
//we initialize our variable with the value that is passed to us
hInstHookDll = (HINSTANCE)hDLL;
break;
default:
break;
}
return TRUE;
}

编辑:我更改了指向主窗口的共享HWND指针的方法。然后,只需向该HWND发送/发布消息,我就可以通过WndProc接收信息

全局挂钩将加载到每个正在运行的进程中,这意味着每个进程都将加载其自己的 DLL 副本。

挂钩 DLL 通过将HHOOK句柄存储在共享数据段中供所有进程共享来说明这一点。但是g_pCallBack变量不存储在该段中,因此不会共享。只有实际调用SetCallback()的原始进程才会分配其g_pCallBack,因此只有该进程中的键盘活动才会报告给回调。

也不能在共享段中存储g_pCallBack,因为无法跨进程边界调用原始函数指针。您需要使用 COM 或 RPC 等进程间通信框架来执行此操作。

一个简单的解决方案是将g_pCallBack替换为存储在共享段中的HWND,然后加载到每个进程中的钩子可以在需要时将窗口消息发布到HWND。C# 应用可以分配一个窗口来接收这些消息,并将该窗口提供给其 DLL 副本以存储在共享段中。或者,如果要继续使用delegateSetHook()创建自己的HWND,然后让窗口处理程序调用委托(如果已分配)。