Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Системное программирование и WinAPI > Как перехватить нажатие клавиши


Автор: Iamme 1.7.2009, 20:01
Добрый день!

Мы вообще то пишем на java, но программа должна выполнять действия по комбинации клавиш (на java не реализуемо), в связи с этим я пытаюсь пока написать простейший код на С++ с использованием WinApi, который выводит сообщение по нажатию клавиши (пока любой).

Получилось вот что: 
Код


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

// функция вхождений программы WinMain
int KeyboardProc(int nCode, DWORD wParam, DWORD lParam)
{

 switch(wParam)
  {
  case WM_KEYDOWN:
    MessageBox(NULL, NULL, NULL, 0);
   break;
  default:
      break;
  }
 
 return 0;
}

int APIENTRY WinMain(HINSTANCE hInstance,
             HINSTANCE         hPrevInstance,
             LPSTR             lpCmdLine,
             int               nCmdShow)
{

  SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)&KeyboardProc, GetModuleHandle(NULL), 0);
  // цикл сообщений приложения
 
  MSG msg = {0};    // структура сообщения
  int iGetOk = 0;   // переменная состояния
  while ((iGetOk = GetMessage(&msg, NULL, 0, 0 )) != 0) // цикл сообщений
  {
    if (iGetOk == -1) return 3;  // если GetMessage вернул ошибку - выход
    TranslateMessage(&msg);    
    DispatchMessage(&msg);
  }
  return 0;  // возвращаем код завершения программы
}

Сообщение вроде выводится по нажатию любой кнопки в любом окне, но сразу же возникает исключение "ошибка чтения"

Подскажите, в чем дело?

Также буду благодарна, если мне подскажут, как перехватывать не любую клавишу, а комбинацию Ctrl+C+C.

Спасибо.
Использую среду Visual C++.

Автор: GremlinProg 1.7.2009, 20:06
у KeyboardProc соглашение вызова должно быть stdcall
поправь так:
Код

int CALLBACK KeyboardProc(int nCode, DWORD wParam, DWORD lParam)


Добавлено через 3 минуты и 58 секунд
три клавиши...
вообще, на 3 разные клавиши можно обойтись GetKeyState
но раз некоторые повторяются, причем в определенной последовательности,
то можно завести стек последних трех клавиш (вернее очередь)

Автор: jonie 1.7.2009, 20:34
1.
Код

int __stdcall KeyboardProc(int nCode, DWORD wParam, DWORD lParam)


2.
Код

    default:
        break;
    }

    return CallNextHookEx(hhk, nCode, wParam, lParam);

...
hhk = SetWindowsHookEx
где hhk имеет тип HHOOK и является глобальной по отношению к функциям.

проверить на "ctrl" нажата можно так:
Код
if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) нажата



Автор: Iamme 3.7.2009, 16:24
решение с перехватом пока такое

Код

#include "stdafx.h"
#include "rrr.h"
#include <stdio.h>
#include <windows.h>
#define VK_C    0x043

HHOOK hHook;
// функция вхождений программы WinMain
int a=0;
int b=0;
int CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    

  if (HC_ACTION==nCode)
    {  if (wParam==WM_KEYDOWN)
        {
             a=b;
             b=0;
            KBDLLHOOKSTRUCT * p = (KBDLLHOOKSTRUCT*)lParam;
            if ((p->vkCode == VK_C) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0))
            { 
               b=1;
                if ((a==1) && (b==1) ) MessageBox(NULL, NULL,NULL,0);
            }
        }
    }
  return CallNextHookEx(hHook, nCode, wParam, lParam);
}


int APIENTRY WinMain(HINSTANCE hInstance,
             HINSTANCE         hPrevInstance,
             LPSTR             lpCmdLine,
             int               nCmdShow)
{
  
  hHook=SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)&KeyboardProc, GetModuleHandle(NULL), 0);
  // цикл сообщений приложения
 
 
  MSG msg = {0};    // структура сообщения
  int iGetOk = 0;   // переменная состояния
  while ((iGetOk = GetMessage(&msg, NULL, 0, 0 )) != 0) // цикл сообщений
  {
    if (iGetOk == -1) return 3;  // если GetMessage вернул ошибку - выход
    TranslateMessage(&msg);    
    DispatchMessage(&msg);
  }
  return 0;  // возвращаем код завершения программы
}


Вроде перехватывает по Ctrl CC, но мне непонятно следующее

1)в документации сказано, что wPAram содержит код виртуальной клавиши.
Сколько раз не жмешь кнопку, значение wParam не меняется (WM_KEYDOWN), а код реально брать вот так надо   

  KBDLLHOOKSTRUCT * p = (KBDLLHOOKSTRUCT*)lParam;
          p->vkCode
Почему wParam не меняется? вроде код написала, но так и не поняла пока

2) Почему  мне написали 
  return CallNextHookEx(hhk, nCode, wParam, lParam); а не return 0?
3) dll теперь надо

Автор: jonie 3.7.2009, 20:17
Цитата
2) Почему  мне написали 
  return CallNextHookEx(hhk, nCode, wParam, lParam); а не return 0?

ваш хук может быть не единственным в системе. Почитайте Remarks к SetWindowsHookEx функции в MSDN:
Цитата

Calling the CallNextHookEx function to chain to the next hook procedure is optional, but it is highly recommendedotherwise, other applications that have installed hooks will not receive hook notifications and may behave incorrectly as a result. You should call CallNextHookEx unless you absolutely need to prevent the notification from being seen by other applications. 

Цитата

3) dll теперь надо
а что мешает сделать Длл? Можно сделать COM компонент, ведь наверно Java-е проще общаться с COM ?

Цитата
1)в документации сказано, что wPAram содержит код виртуальной клавиши.

там это несказано:
Цитата

wParam
[in] Specifies the identifier of the keyboard message. This parameter can be one of the following messages: WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, or WM_SYSKEYUP. 

т.е. wParam будет просто одним из сообщением (DWORD-ом, DWORD это типа беззнаковое целое размером в 32бита на x86 системе).

Вам еще нужно не забыть что остановка хуков нужна! Вы обязаны сделать UnhookWindowsHookEx(hHook) после того как вам хук не нужен будет в противном случае возможно вы будете являться причиной деградирования системы...

Скорее всего вас устроит длл (или COM) которая будет содержать функции установки хука, его снятия. При установке создаст глобальный объект синхронизации, например Mutex, и в цикле обработки сообщений сделаете проверку на состояние мьютекса...

В общем-то у меня есть готовая длл-ка, на assembler x86 правда (впрочем не особо суть разница), реализующая не низкоуровневые, а обычные хуки, параметром которой является HWND (хендл окна). Если Java умеет обрабатывать оконные сообщения то мб вам она подойдет...

Автор: Iamme 3.7.2009, 22:07
Цитата(jonie @ 3.7.2009,  20:17)
Скорее всего вас устроит длл (или COM) которая будет содержать функции установки хука, его снятия. При установке создаст глобальный объект синхронизации, например Mutex, и в цикле обработки сообщений сделаете проверку на состояние мьютекса...


Делаю DLL, так и планирую - 2 метода  - установки (start) и снятия  (stop) хука. 

Код
// dll.cpp: определяет экспортированные функции для приложения DLL.


#include "stdafx.h"
#define VK_C    0x043
HHOOK hHook;
int a=0;
int b=0;
int CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{    

  if (HC_ACTION==nCode)
    {  if (wParam==WM_KEYDOWN)
        {
             a=b;
             b=0;
            KBDLLHOOKSTRUCT * p = (KBDLLHOOKSTRUCT*)lParam;
            if ((p->vkCode == VK_C) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0))
            { 
               b=1;
                if ((a==1) && (b==1) ) MessageBox(NULL, NULL,NULL,0);
            }
        }
    }
  return CallNextHookEx(hHook, nCode, wParam, lParam);
}
__declspec(dllexport)  int start()
{
  
  hHook=SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)&KeyboardProc, GetModuleHandle(NULL), 0);
  // цикл сообщений приложения
 
 
  MSG msg = {0};    // структура сообщения
  int iGetOk = 0;   // переменная состояния
  while ((iGetOk = GetMessage(&msg, NULL, 0, 0 )) != 0) // цикл сообщений
  {
    if (iGetOk == -1) return 3;  // если GetMessage вернул ошибку - выход
    TranslateMessage(&msg);    
    DispatchMessage(&msg);
  }
  return 0;  // возвращаем код завершения программы
}
__declspec(dllexport)  int stop()
{
    UnhookWindowsHookEx(hHook);
    return 0; 
}



Для начала пытаюсь эту dll из с++ вызвать, два метода подряд для теста , вот вызов

Код

// dllmain.cpp: определяет точку входа для приложения DLL.
#include "stdafx.h"

typedef int (__stdcall *MYPROC)(); 
int APIENTRY WinMain(HINSTANCE hInstance,
             HINSTANCE         hPrevInstance,
             LPSTR             lpCmdLine,
             int               nCmdShow)
{
    MYPROC start, stop;
    HINSTANCE dll = LoadLibraryA("simpledll.dll"); 
    if (dll != NULL) 
    { 
        start = (MYPROC) GetProcAddress(dll, "start"); 
        stop = (MYPROC) GetProcAddress(dll, "stop"); 
    } 
    if (start!=NULL) (start)();
    if (stop!=NULL) (stop)();
    
    FreeLibrary(dll);
    return 0;
}


Но адреса  start и stop почему-то нулевые получаются. В чем может быть причина?

PS 
И зачем еще поток Mutex создавать?

Автор: jonie 4.7.2009, 07:58
Цитата

Но адреса  start и stop почему-то нулевые получаются. В чем может быть причина?
потому что в dll вашей они незываются не start и stop - работает изменение симоволов в стиле c++.
Включите в длл файд def примерно такого содержимого: 
Код


LIBRARY      "MyMegaHookLibName"

EXPORTS
    start        PRIVATE
    stop            PRIVATE

и уберите __declspec(dllexport)
Также пропишите в опциях компилятора (в нстройках есть) Module Definition File на только что добавленый def файл.

Цитата


И зачем еще поток Mutex создавать?
у вас блокирующий поток start. Вам придется в вызывающем делать поток..... а я лично сделал бы неблокирующий потокобезопасный start и stop...но то дело ваше.

Автор: Iamme 12.7.2009, 09:23
Цитата(jonie @ 4.7.2009,  07:58)
у вас блокирующий поток start. Вам придется в вызывающем делать поток..... а я лично сделал бы неблокирующий потокобезопасный start и stop...но то дело ваше.

А что значит "неблокирующий потокобезопасный start и stop"? Можно командами?

все это нужно для создания процесса? (в данный момент при использованиии Dll цикл сообщений сразу заканчивается, никакого ожидания нажатий не происходит)

Автор: jonie 12.7.2009, 11:34
Цитата
А что значит "неблокирующий потокобезопасный start и stop"? Можно командами?

пример в аттаче. Хотя и его нужно допиливать до полной безопасности.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)