在特定时间戳记下的WinAPI调用代码

时间:2018-11-20 08:11:25

标签: c++ windows winapi

使用WinAPI中提供的功能,是否可以确保根据毫秒精确的时间戳调用特定功能?如果可以的话,正确的实现方法是什么?

我正在尝试编写工具辅助的Speedrun软件。这种类型的软件会在脚本启动后的非常确切的时刻发送用户输入命令,以执行人为无法实现的输入,从而更快地完成视频游戏。一个典型的序列如下所示:

  1. 在0毫秒发送右击事件
  2. 在5450毫秒内发送右键向上键和上键向下键事件
  3. 在5460毫秒发送左键按下事件
  4. 等。

以下列出了我到目前为止尝试过的内容。由于我对高精度计时器的低级细节没有经验,所以我得到了一些结果,但是却不了解它们为什么会这样:

  • 在输入之间将睡眠 timeBeginPeriod 设置为1结合使用会产生最差的结果。在20个执行中,有0个满足计时要求。我相信睡眠文档Note that a ready thread is not guaranteed to run immediately. Consequently, the thread may not run until some time after the sleep interval elapses.中对此进行了很好的解释,我的理解是睡眠不足以完成此任务。
  • 使用繁忙的等待循环检查 GetTickCount64 ,并将 timeBeginPeriod 设置为1,会产生更好的结果。在20个处决中,有2个满足了时间要求,但这显然是幸运的情况。我查看了有关此计时功能的一些信息,但我怀疑它的更新频率不足以达到1毫秒的精度。
  • GetTickCount64 替换为 QueryPerformanceCounter 可以稍微改善这种情况。在20个处决中,有8个成功。我编写了一个记录器,该记录器将在发送每个输入之前存储QPC时间戳,并在序列完成后将值转储到文件中。我什至竭尽全力为代码中的所有变量分配空间,以确保不会浪费时间在不必要的显式内存分配上。日志值与我为程序提供的时间戳相差1到40毫秒。通用编程可以解决这个问题,但在我的情况下,游戏的一帧时间为16.7毫秒,因此在最坏的情况下,如此类延迟,我可能会延迟3帧,从而破坏了整个实验的目的。 li>
  • 将流程优先级设置为高没有任何区别。

目前,我不确定下一步要去哪里。我的两个猜测是,可能需要花很多时间来迭代繁忙的循环并使用(QPCNow-QPCStart)/ QPF来检查时间,以至于引入上述延迟,或者该过程被OS Scheduler中断了循环执行过程中,控制权返回为时已晚。

游戏具有100%确定性,并锁定在60 fps。我坚信,如果我设法使输入的时间准确,结果总是20分之20,但是在这一点上,我开始怀疑这不可能。

编辑:根据要求,这里是简化的测试版本。第二次调用ExecuteAtTime之后的断点,并查看TimeBeforeInput变量。对我来说,它读取1029和6017(我省略了小数点),这意味着代码应该在执行29和17毫秒后执行。 免责声明:该代码不是为了证明良好的编程习惯而编写的。

#include "stdafx.h"
#include <windows.h>

__int64 g_TimeStart = 0;
double g_Frequency = 0.0;

double g_TimeBeforeFirstInput = 0.0;
double g_TimeBeforeSecondInput = 0.0;

double GetMSSinceStart(double& debugOutput)
{
    LARGE_INTEGER now;
    QueryPerformanceCounter(&now);
    debugOutput = double(now.QuadPart - g_TimeStart) / g_Frequency;
    return debugOutput;
}

void ExecuteAtTime(double ms, INPUT* keys, double& debugOutput)
{
    while(GetMSSinceStart(debugOutput) < ms)
    {

    }
    SendInput(2, keys, sizeof(INPUT));
}

INPUT* InitKeys()
{
    INPUT* result = new INPUT[2];
    ZeroMemory(result, 2*sizeof(INPUT));

    INPUT winKey;
    winKey.type = INPUT_KEYBOARD;
    winKey.ki.wScan = 0;
    winKey.ki.time = 0;
    winKey.ki.dwExtraInfo = 0;
    winKey.ki.wVk = VK_LWIN;
    winKey.ki.dwFlags = 0;
    result[0] = winKey;

    winKey.ki.dwFlags = KEYEVENTF_KEYUP;
    result[1] = winKey;

    return result;
}

int _tmain(int argc, _TCHAR* argv[])
{
    INPUT* keys = InitKeys();

    LARGE_INTEGER qpf;
    QueryPerformanceFrequency(&qpf);
    g_Frequency = double(qpf.QuadPart) / 1000.0;

    LARGE_INTEGER qpcStart;
    QueryPerformanceCounter(&qpcStart);
    g_TimeStart = qpcStart.QuadPart;

    //Opens windows start panel one second after launch
    ExecuteAtTime(1000.0, keys, g_TimeBeforeFirstInput);

    //Closes windows start panel 5 seconds later
    ExecuteAtTime(6000.0, keys, g_TimeBeforeSecondInput);
    delete[] keys;

    Sleep(1000);
    return 0;
}

0 个答案:

没有答案