You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Windows平台通过C++注入DLL实现Unity游戏无SendInput()的鼠标点击模拟?

解决Unity游戏无需聚焦窗口的模拟点击问题(C++ DLL注入方案)

我明白你现在的痛点: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原生函数。

核心步骤:

  1. 等待Mono运行时初始化:注入DLL后,不能立刻Hook,要等Unity加载完Mono环境(可以通过检测mono.dll是否加载,或者等待Unity的主循环启动)。
  2. 获取Unity的程序集与类:找到UnityEngine.dll(新版本Unity可能是UnityEngine.CoreModule.dll),从中获取UnityEngine.Input类的元数据。
  3. 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的输入系统源码有一定了解,一般前两个方案足够覆盖大部分需求。


关键注意事项

  1. Unity版本兼容性:不同版本的Unity可能拆分了程序集(比如新版把UnityEngine.dll拆成了多个CoreModule、UI模块等),要根据目标游戏的Unity版本调整程序集名称。
  2. Hook时机:必须在Mono运行时完全初始化后再执行Hook操作,否则会导致崩溃或Hook失败。可以通过在DLL里创建线程,循环检测Mono相关API是否可用。
  3. 权限问题:注入DLL需要足够的权限,确保你的程序以管理员身份运行,或者目标游戏没有反注入保护。

内容的提问来源于stack exchange,提问作者BullyWiiPlaza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 21:13:11