如何在Windows平台通过C++注入DLL实现Unity游戏无SendInput()的鼠标点击模拟?
我明白你现在的痛点:SendMessage完全没效果,SendInput又必须让窗口聚焦还占用鼠标,想在后台甚至窗口最小化时也能自动操作Unity游戏。下面结合Unity的底层运行机制,给你几个可行的方案,都是基于C++注入DLL的思路:
为什么SendMessage没用?
先给你理清楚根源:Unity并没有依赖Windows的WM_LBUTTONDOWN这类原生窗口消息来处理输入,它自己实现了一套基于Mono的输入系统。所以你发的Windows消息,Unity的消息循环根本不会把它当成用户的鼠标输入来处理,自然没效果。
方案1:正确Hook Unity的Mono Input类方法
你之前尝试HookInput.GetMouseButtonDown等方法失败,大概率是没找对Unity的Mono运行时Hook姿势。Unity的所有C# API都是跑在Mono虚拟机上的,所以必须通过Mono的API来Hook这些方法,而不是直接Hook原生函数。
核心步骤:
- 等待Mono运行时初始化:注入DLL后,不能立刻Hook,要等Unity加载完Mono环境(可以通过检测
mono.dll是否加载,或者等待Unity的主循环启动)。 - 获取Unity的程序集与类:找到
UnityEngine.dll(新版本Unity可能是UnityEngine.CoreModule.dll),从中获取UnityEngine.Input类的元数据。 - Hook目标方法:用Mono的API拿到
GetMouseButtonDown、GetMouseButtonUp、get_mousePosition的方法指针,替换成自己的实现。
示例代码片段:
#include <mono/jit/jit.h> #include <mono/metadata/assembly.h> #include <mono/metadata/class.h> #include <mono/metadata/method.h> #include <MinHook.h> // 用MinHook来做函数替换 // 全局变量保存原始方法指针 MonoMethod* original_GetMouseButtonDown = nullptr; MonoMethod* original_GetMouseButtonUp = nullptr; MonoMethod* original_get_mousePosition = nullptr; // 模拟的鼠标状态 bool simulate_left_down = false; bool simulate_left_up = false; POINT simulated_mouse_pos = {100, 100}; // Hook后的GetMouseButtonDown实现 bool Hooked_GetMouseButtonDown(MonoObject* instance, int button) { // 当需要模拟左键按下时,直接返回true if (button == 0 && simulate_left_down) { simulate_left_down = false; // 只触发一次 return true; } // 其他情况调用原始方法 bool result; mono_runtime_invoke(original_GetMouseButtonDown, instance, (void**)&button, &result); return result; } // Hook后的get_mousePosition实现 MonoObject* Hooked_get_mousePosition(MonoObject* instance) { // 返回我们模拟的鼠标坐标 MonoClass* vector2_class = mono_class_from_name(mono_assembly_get_image(mono_domain_assembly_open(mono_get_root_domain(), "UnityEngine.dll")), "UnityEngine", "Vector2"); MonoObject* vec2 = mono_object_new(mono_get_root_domain(), vector2_class); mono_field_set_value(vec2, mono_class_get_field_from_name(vector2_class, "x"), &simulated_mouse_pos.x); mono_field_set_value(vec2, mono_class_get_field_from_name(vector2_class, "y"), &simulated_mouse_pos.y); return vec2; } // 初始化Hook的入口函数 void SetupMonoHooks() { // 获取Mono根域 MonoDomain* domain = mono_get_root_domain(); // 加载UnityEngine程序集(注意新版本Unity的程序集名称) MonoAssembly* engine_assembly = mono_domain_assembly_open(domain, "UnityEngine.CoreModule.dll"); MonoImage* engine_image = mono_assembly_get_image(engine_assembly); // 获取Input类 MonoClass* input_class = mono_class_from_name(engine_image, "UnityEngine", "Input"); // 获取原始方法指针 original_GetMouseButtonDown = mono_class_get_method_from_name(input_class, "GetMouseButtonDown", 1); original_GetMouseButtonUp = mono_class_get_method_from_name(input_class, "GetMouseButtonUp", 1); original_get_mousePosition = mono_class_get_method_from_name(input_class, "get_mousePosition", 0); // 用MinHook替换方法的非托管入口 void* orig_func_down = mono_method_get_unmanaged_thunk(original_GetMouseButtonDown); void* orig_func_pos = mono_method_get_unmanaged_thunk(original_get_mousePosition); MH_Initialize(); MH_CreateHook(orig_func_down, &Hooked_GetMouseButtonDown, reinterpret_cast<void**>(&original_GetMouseButtonDown)); MH_CreateHook(orig_func_pos, &Hooked_get_mousePosition, reinterpret_cast<void**>(&original_get_mousePosition)); MH_EnableHook(MH_ALL_HOOKS); }
使用时,只需要在你的逻辑里设置simulate_left_down = true,就能让Unity认为左键被按下了,完全不需要窗口聚焦。
方案2:直接触发Unity UI的OnClick事件(更高效)
如果你的目标是点击UI按钮,那完全不需要模拟鼠标输入,直接调用按钮的onClick.Invoke()方法就行,这是最直接的方式。
示例代码片段:
// 假设你已经通过某种方式拿到了Button组件的MonoObject* button_obj void TriggerButtonClick(MonoObject* button_obj) { MonoClass* button_class = mono_object_get_class(button_obj); // 获取onClick属性的get方法 MonoProperty* onclick_prop = mono_class_get_property_from_name(button_class, "onClick"); MonoMethod* get_onclick = mono_property_get_get_method(onclick_prop); // 获取onClick对象 MonoObject* onclick_obj = mono_runtime_invoke(get_onclick, button_obj, nullptr, nullptr); // 调用Invoke方法触发点击 MonoMethod* invoke_method = mono_class_get_method_from_name(mono_object_get_class(onclick_obj), "Invoke", 0); mono_runtime_invoke(invoke_method, onclick_obj, nullptr, nullptr); }
这种方式跳过了所有输入模拟环节,直接触发业务逻辑,性能和可靠性都更高,适合UI自动化场景。
方案3:模拟Unity的输入事件队列
Unity内部维护了输入事件队列,你可以通过反射或Hook的方式往队列里添加鼠标事件。比如旧版输入系统可以找UnityEngine.Input内部的QueueInputEvent方法,新版Input System则可以操作InputSystem的事件队列。不过这个方案相对复杂,需要对Unity的输入系统源码有一定了解,一般前两个方案足够覆盖大部分需求。
关键注意事项
- Unity版本兼容性:不同版本的Unity可能拆分了程序集(比如新版把
UnityEngine.dll拆成了多个CoreModule、UI模块等),要根据目标游戏的Unity版本调整程序集名称。 - Hook时机:必须在Mono运行时完全初始化后再执行Hook操作,否则会导致崩溃或Hook失败。可以通过在DLL里创建线程,循环检测Mono相关API是否可用。
- 权限问题:注入DLL需要足够的权限,确保你的程序以管理员身份运行,或者目标游戏没有反注入保护。
内容的提问来源于stack exchange,提问作者BullyWiiPlaza

