Вот и подошёл к концу наш конкурс! Поздравляем победителей!!! Подробнее.

Проект Programmers.kz и школа hotPen3D2D предлагает Вам курсы по веб-дизайну, веб-программированию и компьютерной графике. Подробности здесь.

 
Информация к новости
 (голосов: 1)

Электронное пианино на Delphi c использованием хука на клавиатуру (hook)

Категория: Статьи и исходники, Эксклюзив на Programmers.KZ (Программирование)

Электронное пианино на Delphi c использованием хука на клавиатуру (hook)

Автор: Мурашкин Александр (AlexanderMS)

Специально для programmers.kz

О том, как написать простейшее электронное пианино на Delphi. Или о том, как сделать звуки нажатия клавиш мелодичными. В конце концов, в этой статье рассказывается о том, что такое хук (hook) и как его использовать.

Наверное, многим из нас приходила в голову мысль об аналогии между клавиатурой компьютера и клавиатурой фортепиано. В этой статье мы напишем программу, которая заставит вас почувствовать себя пианистом-программистом, сидящим за компьютером. :)

Идея такова: при нажатии клавиш должны издаваться звуки определённой частоты (у каждой из семи нот есть своя определённая частота). Звуки будет издавать встроенный в компьютер динамик, который есть практически у всех, и звук которого вы слышите при загрузке компьютера. При этом мы сделаем так, что эти звуки будут издаваться независимо от того, активно ли наше приложение или нет. То есть если наше приложение свёрнуто, а вы набираете текст в Ворде, то динамик всё равно будет издавать звуки. Такого эффекта можно добиться благодаря использованию механизма, который называется хук (hook).

Хук (Hook, дословно "ловушка") - механизм, позволяющий перехватывать сообщения, отправленные системой, до того, как они дойдут до адресата.

Когда мы нажимаем клавишу, система генерирует специальное сообщение (вернее, несколько сообщений). Эти сообщения может получить любая программа. Например, когда Microsoft Word получит сообщение о нажатии клавиши, он отобразит новый символ на экране, даст соответствующую команду модулю проверки правописания и совершит ещё несколько нужных действий.

Но дело в том, что сообщения от клавиатуры получает только активная программа. Если Word свёрнут, то какие бы клавиши мы не нажимали, новый символ в Ворде не напечатается. А нам нужно, чтобы независимо от свёрнутости нашей программы она смогла получить и обработать сообщения о нажатии клавиши. Для этого и нужны хуки (точнее, глобальные хуки). Мы просто перехватим информацию о нажатии клавиши, совершим нужные действия, а только потом эта информация дойдёт до адресата, например, до Ворда.

Этап 1. Создание DLL.

Сначала нужно создать библиотеку DLL, в которой будут процедура установки и выключения хука, а также обработка нажатий клавиши. Мы делаем глобальный хук, который будет работать во всей системе, поэтому в данной ситуации нужно использовать DLL. Выполняем команду File - New - Other и выбираем DLL Wizard. Проект сохраним в отдельной папке и назовём KeyHook.dpr.

В разделе uses прописываем Windows (там объявлена функция установки хука) и SysUtils (нам пригодтся функция UpperCase).

alt

Прописываем частоты, соответствующие нотам:

const
  { частоты нот }
  Du = 262; // до
  Re = 294; // ре
  Mi = 330; // ми
  Fa = 349; // фа
  So = 392; // соль
  La = 440; // ля
  Si = 494; // си

Добавляем в константы следующую переменную-строку и массив соответствия нот:

  Chars = 'ZXCVBNM,./';
  Freqs : array[1..Length(Chars)] of word =
  (
  du,re,mi,fa,so,la,si,du,re,mi
  );

Это будет означать, что клавише Z соответствует нота До, X - нота Ре и так далее.

Добавляем также константу key_down = $40000000;. Она будет использоваться для проверки, что произошло именно нажатие (опускание) клавиши, а не какое-нибудь другое событие с клавишой.

В разделе var у нас будет всего лишь одна переменная: SysHook : HHook = 0; - идентификатор нашего хука.

Теперь пришла пора записать процедуру устрановки и снятия хука. Она у нас будет одна для обоих действий (так удобнее). А её единственный булевый параметр будет показывать, что именно сделать: true - установить, false - снять хук. Вот её код:

{ процедура запуска и остановки }
procedure RunStopHook(State : Boolean) export; stdcall;
begin
  //Если State = true, то ...
  if State
then
      begin
        // запускаем хук
        SysHook := SetWindowsHookEx(WH_KeyBoard, @SysMsgProc, HInstance, 0);
end
    else
      begin
        // отключаем хук
        UnhookWindowsHookEx(SysHook);
        SysHook := 0;
end
end;

Хук устанавливается процедурой SetWindowsHookEx. Параметры таковы:

  1. Тип хука. WH_Keyboard - хук на клавиатуру.
  2. Указатель на обработчик сообщений. Здесь мы указали процедуру SysMsgProc, нам предстоит её создать.
  3. Хэндл экземпляра приложения. Здесь мы указываем HInstance.
  4. Идентификатор потока (thread). 0 означает, что это - глобальный хук.
Функция возвращает переменную-идентификатор хука. Мы её запоминаем. А чтобы снять хук, мы используем процедуру UnhookWindowsHookEx, единственным параметром является идентификатор хука, который мы получили с помощью функции создания хука.

 

Теперь осталось последнее - написать процедуру-обработчик сообщений, о которой мы говорили. Комментарии поясняют этот код:

function SysMsgProc(code : integer; wParam : word;
  lParam : longint) : longint; stdcall;
var
  N : integer;
begin
  Result := CallNextHookEx(SysHook, Code, wParam, lParam);
// вызываем следующий хук в системе. Таково правило, если этого не делать,
  // то система зависнет
  { Проверяем сообщение }
  if code = HC_ACTION then
    if lParam or key_down = lParam // если клавиша нажата
      then
        begin            
          N := Pos(Uppercase(chr(WParam)), Chars); // проверяем её наличие в нашем списке
          if N <> 0 // если нашли
            then Windows.Beep(Freqs[N], 40); // то делаем звуковой сигнал
        end;
end;

Процедура Windows.Beep нужна для того, чтобы системный динамик издал звук. Первый параметр - это частота звука (в герцах), а второй - его длительность в миллисекундах. Частота в данном случае - это частота ноты, соответствующей клавише. А длительность можно поставить по своему усмотрению.

Не буду комментировать параметры функции CallNextHookEx - они описаны в справке. (Help - Windows SDK). Эта функция даёт команду системе вызвать следующий хук, если он есть. А если его нет - то сообщение благополучно доходит до адресата, не подозревающего, что это сообщение было кем-то перехвачено :)

Осталось добавить строку в код нашей библиотеки:

exports RunStopHook name 'RunStopHook';

Эта строка означает, что наша процедура идёт на экспорт, то есть её могут вызывать приложения, использующие эту DLL.

Теперь я публикую полный код библиотеки:

library MouseHook;
uses
  Windows,
  SysUtils;
const
  { частоты нот }
  Du = 262; // до
  Re = 294; // ре
  Mi = 330; // ми
  Fa = 349; // фа
  So = 392; // соль
  La = 440; // ля
  Si = 494; // си
  {
   соответствие нот определённым клавишам. Я сделал так,
   но можно назначить и другие ноты клавишам
  }
  
  Chars = 'ZXCVBNM,./';
  Freqs : array[1..Length(Chars)] of word =
  (
  du,re,mi,fa,so,la,si,du,re, mi
  );
  key_down = $40000000; // константа для проверки, что клавиша нажата.
var
  SysHook : HHook = 0; // идентификатор нашего хука
function SysMsgProc(code : integer; wParam : word;
  lParam : longint) : longint; stdcall;
var
  N : integer;
begin
  Result := CallNextHookEx(SysHook, Code, wParam, lParam);
// вызываем следующий хук в системе. Таково правило, если этого не делать,
  // то система зависнет
  { Проверяем сообщение }
  if code = HC_ACTION then
    if lParam or key_down = lParam // если клавиша нажата
      then
        begin            
          N := Pos(Uppercase(chr(WParam)), Chars); // проверяем её наличие в нашем списке
          if N <> 0 // если нашли
            then Windows.Beep(Freqs[N], 40); // то делаем звуковой сигнал
        end;
end;
{ процедура запуска и остановки }
procedure RunStopHook(State : Boolean) export; stdcall;
begin
  //Если State = true, то ...
  if State
then
      begin
        // запускаем хук
        SysHook := SetWindowsHookEx(WH_KeyBoard, @SysMsgProc, HInstance, 0);
end
    else
      begin
        // отключаем хук
        UnhookWindowsHookEx(SysHook);
        SysHook := 0;
end
end;
exports RunStopHook name 'RunStopHook'; // помещаем нашу процедуру на экспорт,
// чтобы её могло использовать стороннее приложение
end.

Что ж, DLL написана, её нужно сохранить. Запустить её без главного приложения не удастся, и нам надо теперь написать небольшое приложение, которое будет вызывать эту библиотеку.

Этап 2. Создание исполняемой программы, использующей DLL.

Создаём новый проект. Лучше сохранить его в папку с нашей DLL. Проект я назвал Piano, а главному модулю дал имя Main. Форму назвал PianoForm, хотя от этих имён работоспособность программы не зависит. :)

В главном модуле (Main) у формы создаём обработчик события OnCreate. Там прописываем команду RunStopHook(true);. Создаём также обработчик события OnDestroy. В нём прописываем RunStopHook(false);. И последний штрих - нужно добавить в секцию interface строку:

procedure RunStopHook(State : Boolean); stdcall; external 'KeyHook.dll' name 'RunStopHook';

Таким образом мы объявляем функцию, которая содержится в нашей DLL, и даём указание подключить эту DLL к нашей программе.

Всё, приложение готово. Форма может оставаться пустой - нас она не волнует.

Ниже я привожу весь код модуля. От имён модуля и формы здесь ничего не зависит, главное, чтобы наша DLL называлась именно так, как мы здесь укажем.

unit Main;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
type
  TPianoForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
end;
var
  PianoForm: TPianoForm;
procedure RunStopHook(State : Boolean); stdcall; external 'KeyHook.dll' name 'RunStopHook';
implementation
{$R *.dfm}
procedure TPianoForm.FormCreate(Sender: TObject);
begin
  RunStopHook(true);
end;
procedure TPianoForm.FormDestroy(Sender: TObject);
begin
  RunStopHook(false);
end;
end.

Теперь нужно убедиться, что оба проекта скомпилированы, и в нашей папке есть и DLL, и сама программа:

alt

Теперь можно запускать нашу программу.

alt

Попробуйте понажимать на клавиши Z, X, C и так далее. Динамик должен издавать звуки соответствующих нот.

Что ж, теперь можно сыграть "Жили у бабуси" или что-то посовершеннее. Обратите внимание, что приложение работает даже в свёрнутом виде.

Скачать программу вместе с исходниками можно здесь.

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Введите два слова, показанных на изображении: *