C# User32.dll Нажать и отпустить клавишу в неактивном окне
Есть игра в которой пытаюсь имитировать действия игрока, тоесть бег назад бег вперед и т.п Но если я использую данный код:
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, uint uMsg, int wParam, string lParam);
int WM_KEYDOWN = 0x0100;
int WM_KEYUP = 0x0101;
int WM_CHAR = 0x0102;
private void StartAFK()
{
while (true)
{
Thread.Sleep(15000);
if (enabled == true)
{
int code = 0;
switch (code)
{
case (int)CodeName.MoweToL_W_R:
{
SendMessage(HWIDL,WM_KEYDOWN,(int)Keys.W,0);
Thread.Sleep(199);
SendMessage(HWIDL,WM_KEYUP,(int)Keys.W,0);
}
break;
case (int)CodeName.Attack_Start:
{
}
break;
case (int)CodeName.go_to_Jump:
{
}
break;
case (int)CodeName.sit_and_go_mowe:
{
}
break;
}
}
else
{
break;
}
}
}
После данного кода, в игре просто зажимается клавиша W и персонаж бегает постоянно вперед без остановок. Где проблемма? Именно данное решение бы подошло чтобы окно оставалось не активным и в трее.
Ответы (1 шт):
Message Loop штука тонкая и морозить UI поток - значит негативно влиять на обработку Windows сообщений. Многие Windows сообщения обрабатываются через цепочнку приложений, и зависание UI потока в вашем приложении может просто приморозить чужое приложение. Да, такое бывает, особенно в старых приложениях. Поэтому давайте асинхронно.
Так же для отправки клавиш стоит использовать PostMessage.
[DllImport("user32.dll")]
static extern uint MapVirtualKey(Keys uCode, uint uMapType);
[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, uint msg, uint wParam, uint lParam);
Получится такой метод:
const uint WM_KEYDOWN = 0x0100;
const uint WM_KEYUP = 0x0101;
// https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-keydown
// https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-keyup
public static void WMSendKey(IntPtr hwnd, Keys keyCode, bool isDown)
{
uint scanCode = MapVirtualKey(keyCode, 0);
uint lParam = 0x00000001 | (scanCode << 16);
if (!isDown)
lParam |= 0xC0000000;
PostMessage(hwnd, isDown ? WM_KEYDOWN : WM_KEYUP, (uint)keyCode, lParam);
}
Как видите, правильное формирование сообщений WM_KEYDOWN и WM_KEYUP немного сложнее, чем у вас реализовано. На самом деле, я уже публиковал здесь этот метод ранее, вы просто не нашли.
Итого получится
private CancellationTokenSource _cts;
private async void StartAFK()
{
try
{
using (_cts = new CancellationTokenSource())
{
while (true)
{
await Task.Delay(15000, _cts.Token);
CodeName code = (CodeName)0;
switch (code)
{
case CodeName.MoweToL_W_R:
WMSendKey(HWIDL, Keys.W, true);
await Task.Delay(199);
WMSendKey(HWIDL, Keys.W, false);
break;
case CodeName.Attack_Start:
break;
case CodeName.go_to_Jump:
break;
case CodeName.sit_and_go_mowe:
break;
}
}
}
}
catch (OperationCanceledException) { }
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
_cts = null;
}
private void StopAFK()
{
_cts?.Cancel(); // это ведёт к практически немедленной остановке цикла, даже 15 секунд ждать не надо
}
Асинхронный подход дает еще один ощутимый плюс: интерфейс приложения не зависает.