КаталогИндекс раздела
НазадОглавлениеВперед


7. Клавиатура

7.1. Прерывание от клавиатуры и скан-коды

Работа клавиатуры организована на базе собственного микропроцессора. При нажатии или отпускании любой клавиши генерируется код (скан-код), который записывается в собственную память (аппаратный буфер) клавиатуры. При этом в центральный процессор выдается прерывание 9, сигнализирующее о появлении очередного скан-кода. Программа обработки этого прерывания может теперь прочитать код из клавиатуры. Если она этого не сделает, то сканкоды будут накапливаться в аппаратном буфере и могут быть прочитаны позднее. Чтение скан-кода производится из однобайтного порта 0x60. При выполнении только чтения скан-код не удаляется из аппаратного буфера, и при следующем обращении к порту 0x60 будет прочитан тот же скан-код. Для того, чтобы удалить скан-код из аппаратного буфера, необходимо послать в клавиатуру сигнал подтверждения: на короткое время выставить единицу в старшем разряде однобайтного порта 0 x61 (не разрушая при этом остальных разрядов этого порта). Каждая клавиша имеет собственный уникальный скан-код, в том числе и на 101-клавишной клавиатуре AT, где некоторые клавиши дублируются - например, левая и правая клавиши Shift имеют разные скан-коды. Распределение значений скан-кодов примерно соответствует расположению клавиш на панели клавиатуры. Скан-коды не совпадают с кодировкой внутримашинного представления символов, для которой применяется код ASCII. Преобразование скан-кодов в коды ASCII происходит программными путем. В некоторых источниках имеются расплывчатые формулировки, которые можно трактовать таким образом, что некоторые клавиши или комбинации клавиш (PrintScrn, Ctrl +Break, etc) не имеют скан-кодов - это ни в коем случае не так! Каждая клавиша имеет скан-код, который считывается в компьютер вышеописанным образом, а уж программа обработки этого кода может распорядиться им специальным образом.
Выше упоминалось, что скан-код генерируется и при нажатии, и при отпускании клавиши. Для 84-клавишной клавиатуры XT скан-код отпускания совпадает со скан-кодом нажатия, но содержит дополнительную единицу в старшем разряде. В источниках можно встретить информацию о том, что клавиатура AT генерирует двухбайтный скан-код при отпускании клавиши, в котором первый байт - 0xF0, а второй совпадает со скан-кодом нажатия. Это справедливо для 84-клавишной клавиатуры AT, но в настоящее время все AT комплектуется 101-клавишной клавиатурой, в которой код отпускания формируется по тем же прави- лам, что и в XT. Правда, в 101-клавишной клавиатуре некоторые дополнительные клавиши имеют скан-коды из 2 и более байт (и каждый байт сопровождается прерыванием 9).

Программный пример 7.1 позволяет определить скан-коды клавиш. Эта программа перехватывает прерывание 9. Обратите внимание на то, что обработчик прерывания представляет собой не дополнение к системному обработчику, а полностью его подменяет. При поступлении прерывания 9 обработчик читает скан-код из порта 0x60 и запоминает его в массиве SC, а затем посылает в клавиатуру сигнал подтверждения и сбрасывает контроллер прерываний. При поступлении скан-кода 1 (это клавиша Esc) обработчик взводит флаг окончания, по которому главная программа восстанавливает вектор и выводит на экран накопленные скан-коды.


/*==                    ПРИМЕР 7.1                     ==*/
/*======== Прерывание от клавиатуры и scan-коды =========*/
#include <dos.h>
#define byte unsigned char
void interrupt (*old9)(); /* Для сохр. старого вектора */
void interrupt new9();    /* Описание нового обработчика */
byte SC[100];          /* Массив - накопитель скан-кодов */
byte Nsc=0;            /* Счетчик скан-кодов */
byte eoj_flag;         /* Флаг окончания */
void *readvect(int in);
void writevect(int in, void *h);
union REGS rr;
struct SREGS sr;
main() {
  int i;
  /* Перехват вектора 9 */
  old9=readvect(9); writevect(9,new9);
  printf("\n\nНажимайте на калавиши \n");
  printf("Esc - конец работы\n");
  /* В этом цикле происходит обработка нажатых клавиш */
  for (eoj_flag=0; eoj_flag==0;);
  /* Восстановление вектора 9 */
  writevect(9,old9);
  /* Вывод на экран введенных скан-кодов */
  for (i=0; i<Nsc; printf("%x ",SC[i++]) );
  }
/*==== Обработчик прерывания 9 ====*/
void interrupt new9() {
  byte scan; /* Скан-код */
  byte c;    /* Состояние порта 61 */
  /* Чтение scan-кода. */
  scan=inportb(0x60);
  /* По Esc (скан - 1) устанавливается признак окончания */
  if (scan==1) eoj_flag=1;
  /* Запоминается скан-код */
  SC[Nsc++]=scan;
  /* Устанавл.признак окончания при заполнении массива */
  if (Nsc>99) eoj_flag=1;
  /* Подтверждение приема. В порт 61 на короткое время
     выставляется "1" по шине старшего разряда. */
  c=inportb(0x61);
  outportb(0x61,c|0x80);
  outportb(0x61,c);
  /* Сброс контроллера прерываний. */
  outportb(0x20,0x20);
  }
/*==== Получение старого вектора ====*/
void *readvect(int in) {
  rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr);
  return(MK_FP(sr.es,rr.x.bx));
}
/*==== Запись нового вектора ====*/
void writevect(int in, void *h) {
  rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(h);
  rr.x.dx=FP_OFF(h); intdosx(&rr,&rr,&sr);
}

Программный пример 7.2 иллюстрирует технику обработки "горячей клавиши", часто применяемую в резидентных программах. Такие программы перехватывают прерывание 9 и распознают код некоторой закрепленной за ними клавиши. При распознавании "горячей клавиши" программа выполняет какие-то свои действия, обработку всех остальных клавиш программа "отдает" системе. Для нашей программы "горячей" является клавиша "a", действия нашей программы по клавише "a" - пустые, что приведет к исчезновению буквы "a" из вводимого потока. Эта программа, как и предыдущая, также перехватывает прерывание 9. Обработчик прерывания читает скан-код, но не спешит посылать в клавиатуру подтверждение, а анализирует код. Если это скан-код клавиши "a", то обработчик удаляет его из клавиатуры и сбрасывает контроллер прерываний. В противном случае вызывается системный обработчик, который повторно прочитает тот же код из порта 0x60 и распорядится им по-своему. Вы можете убедиться в том, что в символьную строку, вводимую функцией scanf основной программы, не будут включаться буквы "a".


/*==                  ПРИМЕР 7.2                      ==*/
/*============ Прерывание от клавиатуры ================*/
#include <dos.h>
void interrupt (*old9)();  /* Для сохр. старого вектора */
void interrupt new9();   /* Описание нового обработчика */
unsigned char a_code=0x1e; /* Скан-код клавиши "a" */
union REGS rr;
struct SREGS sr;
void *readvect(int in);
void writevect(int in, void *h);
/*==== main ====*/
main()  {
  char string[80];
  /* Перехват вектора 9 */
  old9=readvect(9); writevect(9,new9);
  printf("\nВводите строку символов>");
  scanf("%s",string); /* Ввод строки */
  /* Восстановление вектора 9 */
  writevect(9,old9);
  printf("%s\n",string);
  }
/*==== Обработчик прерывания 9 ====*/
void interrupt new9() {
  unsigned char c;
  /* Чтение scan-кода и сравнение его с "горячим" */
  if (inportb(0x60)==a_code) {
    /* Если горячий код - подтверждение приема... */
    c=inportb(0x61);
    outportb(0x61,c|0x80);
    outportb(0x61,c);
    /* и сброс контроллера прерываний. */
    outportb(0x20,0x20);
    }
  else (*old9)(); /* Если нет - вызов сист.обработчика */
  }
/*==== Получение старого вектора ====*/
void *readvect(int in) {
  rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr);
  return(MK_FP(sr.es,rr.x.bx));
}
/*==== Запись нового вектора ====*/
void writevect(int in, void *h) {
  rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(h);
  rr.x.dx=FP_OFF(h); intdosx(&rr,&rr,&sr);
}

7.2. Коды ASCII и работа BIOS

Обработчиком прерывания 9 в BIOS скан-коды превращаются в коды ASCII. Имеется два типа ASCII-кодов: однобайтные и расширенные. Однобайтные коды - коды алфавитно-цифровых символов, символов псевдографики, некоторых управляющих символов (последние по-разному интерпретируются разными периферийными устройствами). Расширенные ASCII-коды - двухбайтные, первый байт всегда 0, второй содержит код. Такие коды связаны с клавишами, которые не имеют литерного отображения: клавиши функциональной клавиатуры, управления курсором и т.п. Обработчик прерывания 9 BIOS вырабатывает требуемые ASCII-коды и записывает в буфер клавиатуры. Далее программа, выполняющая ввод, обращается к BIOS или к DOS, и средства системы выбирают символы из буфера и передают программе.

Буфер клавиатуры находится в области данных BIOS. Этот буфер занимает память с адресами от 0040:001 по 0040:003C. Буфер организован как циклическая очередь, то есть при за- полнении указанной области памяти запись продолжается с ее начала. Два слова в области данных BIOS содержат адреса (смещения относительно начала области данных BIOS) начала и конца ("головы" и "хвоста") очереди. Адреса этих слов - 0040:001A и 0040:001C соответственно. Каждый код представлен в буфере BIOS двумя байтами. Для расширенных кодов ACSII первый байт содержит 0, а второй байт - ASCII-код. Для одно- байтных кодов первый байт содержит ASCII-код, а второй байт - скан-код клавиши, породившей этот ASCII-код. В буфере клавиатуры BIOS размещаются 15 слов, содержащих коды введенных клавиш, 16-е слово зарезервировано для размещения в нем признака конца 0x0D1C. Добавление нового кода в буфер состоит в записи кода по адресу "хвоста" и модификации указателя "хвоста". Удаление - в выборке кода по адресу "головы" и модификации указателя "головы" (модификация указателей должна учитывать циклическую природу очереди). Для очистка буфера достаточно приравнять указатель "хвоста" указателю "головы".
Программа примера 7.3 иллюстрирует работу буфера клавиатуры. При всяком изменении указателей очереди на экране отображается содержимое буфера и положение указателей в нем. Перехват прерывания 9 производится для распознавания управляющих клавиш программы. Клавиша "r" устанавливает признак чтения, по которому из очереди выбирается один код. Клавиша Esc устанавливает признак завершения программы.


/*==                      ПРИМЕР 7.3                    ==*/
/*========= Просмотр содержимого буфера клавиатуры =======*/
#include <dos.h>
#define byte unsigned char
#define word unsigned int
void interrupt (*old9)();  /* Для сохр. старого вектора */
void interrupt new9();     /* Описание нового обработчика */
byte end_flag;             /* Признак окончания */
byte read_flag;            /* Признак чтения */
union REGS rr;
struct SREGS sr;
void *readvect(int in);
void writevect(int in, void *h);
byte *ptr_0;       /* Указатель на начало области
                               в памяти BIOS */
byte *p;           /* Текущ.указатель в памяти BIOS */
word ptr_b, old_ptr_b; /* Смещения "головы" очереди */
word ptr_e, old_ptr_e; /* Смещения "хвоста" очереди */
char line[81];       /* Строка для отметки начала/конца */
int index_b, index_e;  /* Индексы в line */
int i;
main() {
  for(i=0; i<80; line[i++]=' '); line[i]='\0';
  ptr_0=(byte *)MK_FP(0x40,0x1e); /* Указатель на начало */
  /* Указатели на голову и хвост */
  old_ptr_b=peek(0x40,0x1a);
  old_ptr_e=peek(0x40,0x1c);
  /* Перехват вектора 9 */
  old9=readvect(9); writevect(9,new9);
  printf("\nНажимайте не клавиши\n");
  for (read_flag=end_flag=0; end_flag==0;) {
    if (read_flag) { /* Выборка символа из буфера */
      read_flag=0;
      /* Функция 0 прерывания 16 */
      rr.h.ah=0;
      int86(0x16,&rr,&rr);
      if (!rr.h.al)
        printf("Выбран расширенный ASCII-код - 00 %02x\n",
          rr.h.ah);
      else
        printf("Выбран символ - >%c< скан-код - %02x\n",
          rr.h.al,rr.h.ah);
      }
    /* Указатели на голову и хвост */
    ptr_b=peek(0x40,0x1a);  ptr_e=peek(0x40,0x1c);
    if ((ptr_b!=old_ptr_b)||(ptr_e!=old_ptr_e)) {
      /* Если указатели на голову или хвост изменились -
         отображаем состояние */
      old_ptr_b=ptr_b;  old_ptr_e=ptr_e;
      /* Буквы "н" и "к" размещаются в строке
         в вычисляемых местах */
      index_b=(ptr_b-0x1e)/2*5; index_e=(ptr_e-0x1e)/2*5;
      line[index_b]='н'; line[index_e]='к';
      printf("%s",line);
      line[index_b]=' '; line[index_e]=' ';
      /* Распечатка области буфера в памяти BIOS */
      for (p=ptr_0, i=0; i<16; p+=2, i++)
        printf("%02x%02x ",*p,*(p+1));
      printf("\n");
      }
    }
  /* Восстановление вектора 9 */  writevect(9,old9);
  /* Очистка буфера клавиатуры */
  poke(0x40,0x1a,peek(0x40,0x1c));
  }
/*==== Обработчик прерывания 9 ====*/
void interrupt new9() {
  byte c;
  /* Чтение scan-кода и сравнение его с "горячими" */
  if ((c=inportb(0x60))==0x13) { /* Клавиша "r" */
    /* Клавиша изымается из потока */
    c=inportb(0x61); outportb(0x61,c|0x80);
    outportb(0x61,c); outportb(0x20,0x20);
    read_flag=1; /* Признак чтения взводится */
    }
  else {
    if (c==1) /* Клавиша Esc */
      end_flag=1; /* Признак окончания взводится */
    (*old9)();  /* Вызов системного обработчика */
    }
  }
/*==== Получение старого вектора ====*/
void *readvect(int in) {
  rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr);
  return(MK_FP(sr.es,rr.x.bx));
}
/*==== Запись нового вектора ====*/
void writevect(int in, void *h) {
  rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(h);
  rr.x.dx=FP_OFF(h); intdosx(&rr,&rr,&sr);
}

Не все клавиши или комбинации клавиш порождают ASCII-коды. Непорождающие клавиши и комбинации могут быть разделены на две группы: клавиши-переключатели и клавиши (комбинации) специальных действий.
К клавишам-переключателям относятся: Shift, Ctrl, Alt, CapsLock, NumLock, ScrollLock, Insert (последняя, впрочем, наряду с переключением порождает и расширенный ASCII-код 0x0052). Нажатие клавиши-переключателя обрабатывается программами BIOS, в результате чего изменяются флаги состояния клавиатуры в области данных BIOS (два байта по адресам 0040: 0017, 0040:0018).
Интерпретация разрядов байта 0040:0017 следующая:

Интерпретация разрядов байта 0040:0018 следующая: Если клавиша нажата соответствующий разряд устанавливается в 1, при отпускании клавиши - сбрасывается в 0. Разряды режимов в исходном состоянии нулевые и переключаются при каждом нажатии соответствующей клавиши. Следующая программа, во многом подобная предыдущей отображает флаги клавиатуры при каждом изменении их состояния. Использование в этой программе прерывания 5 будет объяснено чуть ниже.

/*==                      ПРИМЕР 7.4                   ==*/
/*======== Просмотр состояния клавиш-переключателей =====*/
#include <dos.h>
void interrupt (*old5)(); /* Для сохр.старого вектора */
void interrupt new5();    /* Описание нового обработчика */
unsigned char end_flag;   /* Признак окончания */
union REGS rr;
struct SREGS sr;
void *readvect(int in);
void writevect(int in, void *h);
/*===== main ====*/
main() {
  unsigned int kb_state, old_kb_state;/* Флаги клавиатуры */
  int i;
  old_kb_state=peek(0x40,0x17);
  /* Перехват вектора 5 */
  old5=readvect(5); writevect(5,new5);
  printf("\nНажимайте не клавиши-переключатели\n");
  printf
    ("\nДля завершения нажимите не клавишу Print Scrn\n");
  for (end_flag=0; end_flag==0;) {
    kb_state=peek(0x40,0x17);
    if (kb_state!=old_kb_state) {
      /* Если флаги изменились - отображаем состояние */
      old_kb_state=kb_state;
    for(i=15; i>=0; i--)
      if ((kb_state>>i)&1) printf("1");
      else printf("0");
    printf("\n");
    }
  }
  /* Восстановление вектора 5 */  writevect(5,old5);
}
/*==== Обработчик прерывания 5 ====*/
/* Это прерывание выполняется из обработчика прерывания 9
   при нажатии клавиши Print Scrn */
void interrupt new5() {
  end_flag=1; /* Признак окончания взводится */
  }
/*==== Получение старого вектора ====*/
void *readvect(int in) {
  rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr);
  return(MK_FP(sr.es,rr.x.bx));
}
/*==== Запись нового вектора ====*/
void writevect(int in, void *h) {
  rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(h);
  rr.x.dx=FP_OFF(h); intdosx(&rr,&rr,&sr);
}

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

Большинство действий, связанных с обработкой информации о клавиатуре в области данных BIOS обеспечивается прерыванием BIOS 0x16. Это прерывание имеет три функции (номер функции задается в регистре AH).

Пример 7.5 иллюстрирует работу прерывания 0x16.

/*==                ПРИМЕР 7.5               ==*/
/*============= Прерывание BIOS 16 ============*/
#include <dos.h>
main()
  {
  union REGS rr;
  int i;
  printf("\nПрерывание 0x16, функция 1 >");
  /* Ожидание нажатия клавиши */
  do {
    rr.h.ah=1;
    int86(0x16,&rr,&rr);
    } while((rr.x.flags&0x0040)!=0);
  printf("%02x %02x",rr.h.ah,rr.h.al);
  /* Прием клавиши */
  rr.h.ah=0;
  int86(0x16,&rr,&rr);
  /* Здесь выведется то же, что и в предыдущей строке, т.к.
     клавиша, код которой прочитан по функции 1 из клавиа-
     туры не удалена. */
  printf("   %02x %02x\n",rr.h.ah,rr.h.al);
  /* Прием новой клавиши с ожиданием */
  printf("\nПрерывание 0x16, функция 0 >");
  rr.h.ah=0;
  int86(0x16,&rr,&rr);
  printf("%02x %02x\n",rr.h.ah,rr.h.al);
  printf("\nПрерывание 0x16, функция 2 >");
  /* Теперь надо нажимать клавиши состояний и смотреть, что
     выводится */
  do {
    rr.h.ah=2;
    int86(0x16,&rr,&rr);
    printf("%02x  ",rr.h.al);
    delay(300);
    /* Проверка нажатия любой (кроме переключателя) клавиши.
       Цикл продолжается до нажатия клавиши "пробел" */
    rr.h.ah=1;
    int86(0x16,&rr,&rr);
    } while(rr.h.al!=13);
  /* Очистка буфера клавиатуры */
  poke(0x40,0x1a,peek(0x40,0x1c));
  }

7.3. Функции стандартного ввода DOS

В DOS нет функций, ориентированных непосредственно на клавиатуру. Функции ввода DOS работают с файлом стандартного ввода, который по умолчанию связан с клавиатурой. Но при переназначении стандартного ввода функции DOS будут успешно работать и с другим источником.

Коротко перечислим функции DOS.


/*==                    ПРИМЕР 7.6                      ==*/
/*====== Функции обслуживания стандартного ввода DOS =====*/
/* ВНИМАНИЕ! Для проверки реакции на Ctrl+Break программу
   следует запускать вне Турбо-среды */
#include <dos.h>
main()
  {
  union REGS in, rr;     /* Регистры для вызовов DOS */
  struct SREGS sr;       /* Регистры для вызовов DOS */
  int x,y;    /* Коорд.курсора (1,6,7,8) или счетчик (A) */
  unsigned char dosnum;  /* Номер функции */
  unsigned long d;       /* Счетчик опросов (6) */
  char string[13];       /* Буфер (A) */
  char *s;               /* Указатель в буфере (A) */
L0: clrscr();
  printf("Укажите # функции DOS (1,6,7,8,B,A) >");
  scanf("%x",&dosnum);
  printf("DOS-функция %02X.\n",dosnum);
  switch (dosnum) {
    case 1: printf("Ввод символа с эхо; ");
      printf("реагирует на Ctrl-Break.\n");
      goto F;
    case 7: printf("Ввод символа без эхо; ");
      printf("не реагирует на Ctrl-Break.\n");
      goto F;
    case 8: printf("Ввод символа без эхо; ");
      printf("реагирует на Ctrl-Break.\n");
      goto F;
    case 6: printf("Ввод символа без эхо и без ожидания; ");
      printf("не реагирует на Ctrl-Break.\n");
      printf("Окончание по Esc\n");
      printf("Вводите >");
      for(; ; ) { /* Цикл по клавишам */
        for (d=0 ; ;d++ ) {
          /* Цикл ожидания нажатия клавиши */
          rr.h.ah=6;
          rr.h.dl=0xff;  /* FF в DL - запрос на ввод. */
          intdos(&rr,&rr);
          /* Если символ готов, флаг ZF=0, а в AL - код
             символа, в противном случае ZF=1. */
          if ((rr.x.flags&0x0040)==0) break;
          }
        x=wherex(); y=wherey();
        gotoxy(1,7); clreol();
        printf("Код==>%02x<== Символ==>%c<==",
          rr.h.al,rr.h.al);
        printf("  Циклов опроса - %lu\n",d);
        gotoxy(x,y);
        /* Окончание по Esc */
        if (rr.h.al==27) break;
        }
      break;
    case 0xb: printf("Проверка статуса клавиатуры.\n");
      printf("Статус будет повторяться до ");
      printf("первого нажатия клавиши\n");
      for ( ; ; ) {
        rr.h.ah=0x0b;
        intdos(&rr,&rr);
        /* В AL - FF, если символ есть;
                  0 - если символа нет. */
        printf("AL==>%d\n",rr.h.al);
        if (rr.h.al==0xff) break;
        delay(500);
        }
      break;
    case 0xa: printf("Буферизованный ввод строки; ");
      printf("реагирует на Ctrl-Break.\n");
      string[0]=11;  /* Макс.длина - 10 (без Enter) */
      for(x=1; x<13; string[x++]=0xff); /* Очистка */
      printf("\nБуфер до ввода   >");
      for(x=0; x<13; printf("%02x ",string[x++]));
      printf("\nВводите строку (не более 10 символов>");
      rr.h.ah=0x0a;
      sr.ds=FP_SEG(string);    /* DS:DX - адрес буфера */
      rr.x.dx=FP_OFF(string);
      intdosx(&rr,&rr,&sr);
      printf("\nБуфер после ввода>");
      for(x=0; x<13; printf("%02x ",string[x++]));
      printf("\nВведенная строка>");
      for(s=string+2; *s!=13; printf("%c",*(s++)));
      printf("\n");
      break;
    default: goto L0;
    }
  exit();
F: /* Общий алгоритм применения для функций 1,7,8 */
  printf("Окончание по Esc\nВводите >");
  for (; ; ) {
    rr.h.ah=dosnum;
    intdos(&rr,&rr);
    /* В AL - код символа. */
    x=wherex(); y=wherey();
    gotoxy(1,7); clreol();
    printf("   Код==>%02x<== Символ==>%c<==\n",
      rr.h.al,rr.h.al);
    gotoxy(x,y);
    /* Окончание по Esc */
    if (rr.h.al==27) break;
    }
  }

И еще две возможности предоставляет DOS.

Функция 0x0C. При ее вызове в регистре AL должен быть номер одной из функций ввода (1, 6, 7, 8, 0x0A). Функция очищает буфер клавиатуры в памяти BIOS, а затем вызывает указанную в AL функцию.

Кроме того, поскольку стандартный ввод представляет собой файл DOS, с ним можно работать при помощи функции файлового ввода 0x3F, задавая дескриптор файла 1. Файловые функции будут рассмотрены позднее.

7.4. Комбинация клавиш Ctrl+Break

Если нажата эта комбинация клавиш, то при первом же системном вызове она будет обработана. Обработка заключается обычно в немедленном завершении текущей программы. Обратите внимание на то, что комбинация обрабатывается не немедленно, а при вызове DOS. Начиная с самого нижнего уровня, порядок обработки этой комбинации следующий. Комбинация Ctrl+Break распознается BIOS при вводе. BIOS в этом случае вызывает прерывание 0x1B. Исходный обработчик этого прерывания состоит из одной команды IRET. DOS переустанавливает этот вектор на другой обработчик, который взводит признак нажатия Ctrl+Break в области данных BIOS (40:71). Функции DOS проверяют этот флаг и если он взведен (и если это разрешено статусом обработки) вызывают прерывание 0x23, а системный обработчик этого прерывания завершает программу.

В следующем примере дважды повторяются похожие действия: в программном цикле часть экрана заполняется символами '*'. Но в первом таком цикле нет системных вызовов. Поэтому если во время выполнения этого цикла будет нажата комбинация Ctrl+Break, прерывания программы не произойдет. Во втором цикле есть вызов функции DOS 0x0B, поэтому в этом цикле программу можно прервать.


/*==                     ПРИМЕР 7.7                     ==*/
/*============ Действие комбинации Ctrl+Break ============*/
/* ВНИМАНИЕ! Для проверки реакции на Ctrl+Break программу
   следует запускать вне Турбо-среды */
#include <dos.h>
void main() {
  union REGS rr;
  int i,k,o,m;
  clrscr();
  printf
    ("При выполнении этого цикла Ctrl+Break не сработает");
  for (o=160,i=0,k=0; i<30000; i++)
    /* Просто цикл */
    if (++k==100) {
      /* Через каждые 100 итераций на экран выводится '*' */
      pokeb(0xb800,o++,'*');pokeb(0xb800,o++,0x21);
      k=0; for(m=0;m<32000;m++); /* Задержка */
      }
  getch();
  clrscr();
  printf("При выполнении этого цикла Ctrl+Break сработает");
  for (o=160,i=0,k=0; i<30000; i++)
    if (++k==100) {
    /* Выполнение операции со стандартным вводом обработает
       Ctrl+Break */
    rr.h.ah=0x0b;
    intdos(&rr,&rr);
    pokeb(0xb800,o++,'*');pokeb(0xb800,o++,0x21);
    k=0; for(m=0;m<32000;m++);
    }
  }

В системе имеется так называемый статус обработки Ctrl+ Break. Если этот статус 1 (включен), то комбинация обрабатывается при любом системном вызове, если 0 (отключен) - только при вызовах, связанных со стандартным вводом и выводом. Прочитать или установить этот статус позволяет функция DOS 0 x33. Если при вызове функции в регистре AL - 1, то регистр DL должен содержать устанавливаемое значение статуса, если в AL - 0, статус читается. В обоих случаях в DL возвращается текущее значение статуса. В примере 7.8 предлагается испытать действие Ctrl+Break при разных значениях статуса обработки. В теле циклов применяется ни на что не влияющий системный вызов "получение версии DOS" (функция 0x30). Но если заменить этот вызов на вызов, например, функции 0x0B, то и первый цикл (выполняемый при статусе 0) будет прерываемым, так как для функций стандартного ввода обработка Ctrl+ Break не отключается.


/*==                     ПРИМЕР 7.8                     ==*/
/*============ Статус обработки Ctrl+Break ===============*/
/* ВНИМАНИЕ! Для проверки реакции на Ctrl+Break программу
   следует запускать вне Турбо-среды */
#include <dos.h>
void main() {
  union REGS rr;
  int i,k,o,m;
  clrscr();
  /* Отключение Ctrl+Break */
  rr.h.ah=0x33;
  rr.h.al=1;   /* Подфункция установка Ctrl+Break */
  rr.h.dl=0;   /* OFF */
  intdos(&rr,&rr);
  printf("Статус Ctrl+Break = %d\n",rr.h.dl);
  /* Этот цикл будет непрерываемым */
  for (o=160,i=0,k=0; i<30000; i++)
    if (++k==100) {
      /* Системный вызов (любой) */
      rr.h.ah=0x30; intdos(&rr,&rr);
    pokeb(0xb800,o++,'*');pokeb(0xb800,o++,0x21);
    k=0; for(m=0;m<32000;m++);
    }
  getch();
  clrscr();
  /* Включение Ctrl+Break */
  rr.h.ah=0x33;
  rr.h.al=1;  /* Подфункция установка Ctrl+Break */
  rr.h.dl=1;  /* ON */
  intdos(&rr,&rr);
  printf("Статус Ctrl+Break = %d\n",rr.h.dl);
  /* Этот цикл (точно такой же) можно будет прервать */
  /* То  же самое */
  for (o=160,i=0,k=0; i<30000; i++)
    if (++k==100) {
      rr.h.ah=0x30;
      intdos(&rr,&rr);
      pokeb(0xb800,o++,'*');pokeb(0xb800,o++,0x21);
      k=0; for(m=0;m<32000;m++);
      }
  }

Мы можем перехватить вектор 0x23 и производить собственную обработку Ctrl+Break. Это демонстрирует пример 7.9, в котором обработка комбинации заключается в смене цвета выводимого на экран символа.


/*==                     ПРИМЕР 7.9                    ==*/
/*========= Пользовательская обработка Ctrl+Break =======*/
/* ВНИМАНИЕ! Для проверки реакции на Ctrl+Break программу
   следует запускать вне Турбо-среды */
#include <dos.h>
void interrupt (*old23)(); /* Адрес старого обработчика */
void interrupt new23();    /* Описание нового обработчика */
unsigned char col;         /* Цвет вывода */
union REGS rr;
struct SREGS sr;
void *readvect(int in);
void writevect(int in, void *h);
void main() {
  union REGS rr;
  int i,k,o,m;
  col=0x21;    /* Исходный цвет */
  /* Включение Ctrl+Break */
  rr.h.ah=0x33;
  rr.h.al=1;   /* Подфункция установка Ctrl+Break */
  rr.h.dl=1;   /* ON */
  intdos(&rr,&rr);
  /* Перехват вектора */
  old23=readvect(0x23);  writevect(0x23,new23);
  for (o=0,i=0,k=0; i<30000; i++)
    if (++k==100) {
      /* Системный вызов */
      rr.h.ah=0x30; intdos(&rr,&rr); /* Опр.версию DOS */
      pokeb(0xb800,o++,'*');pokeb(0xb800,o++,col);
      k=0; for(m=0;m<32000;m++);
      }
  /* Восстановление вектора */
  writevect(0x23,old23);
  }
/*==== Обработчик прерывания 23, т.е. Ctrl+Break ====*/
void interrupt new23() {
  /* Переключение цвета */
  if (col==0x21) col=0x24; else col=0x21;
  }
/*==== Получение старого вектора ====*/
void *readvect(int in) {
  rr.h.ah=0x35; rr.h.al=in; intdosx(&rr,&rr,&sr);
  return(MK_FP(sr.es,rr.x.bx));
}
/*==== Запись нового вектора ====*/
void writevect(int in, void *h) {
  rr.h.ah=0x25; rr.h.al=in; sr.ds=FP_SEG(h);
  rr.x.dx=FP_OFF(h); intdosx(&rr,&rr,&sr);
}

Восстановление вектора 0x23, произведенное нами в конце программы - действие необязательное, так как при завершении программы система сама восстанавливает тот вектор этого прерывания, который был установлен до ее запуска.

7.5. Драйвер ANSI.SYS и переопределения клавиатуры

В состав DOS входит драйвер ANSI.SYS, обеспечивающий расширенные средства управления консолью. Чтобы иметь доступ к средствам, предоставляемым этим драйвером, в файл CONFIG.SYS должна быть включена команда: DEVICE=ANSI.SYS Функции драйвера ANSI.SYS вызываются путем вывода в стандартный вывод специально форматированных последовательностей символов. Эти последовательности начинаются символом с кодом 27 (0x1B, 8-ричный код -33), отсюда их название - Esc-последовательности. Второй символ Esc-последовательности "[" - код 91 (0x5B). Последующие символы варьируются. Для вызова ANSI-функций программа может использовать функции символьного вывода DOS или соответствующие средства языков высокого уровня, которые выдают символы на консоль. Большинство ANSI-функций управляют выводом на терминал и будут нами рассмотрены в соответствующем месте. Применительно к клавиатуре драйвер ANSI.SYS обеспечивает переназначение клавиатуры, присваивая заданной клавише новое значение ASCII -кода или целой последовательности кодов. Формат Esc-последовательности для переопределения клавиши следующий: <Esc>[<исходный код>;<заменитель>p, где "[", ";" и "p" - символы, кодируемые как показано; <Esc> - символ с кодом 27; <исходный код> - десятичный цифровой ASCII-код переназначаемой клавиши, для клавиш, имеющих расширенные ASCII-коды представляется как: 0;<код>; <заменитель> - это может быть другой код или коды, заданные либо в виде десятичных чисел, либо в виде строковых (взятых в кавычки) констант. Составляющие заменителя разделяются символами ; (точка с запятой). Для восстановления исходного значения клавиши следует выдать последовательность: <Esc>[<исходный код>;<исходный код>p Довольно громоздкий способ кодирования переопределений, возможно, станет более понятным при анализе следующего примера.


/*==                    ПРИМЕР 7.10                    ==*/
/*= Использование Esc-последовательностей драйвера ANSI =*/
main() {
 char string[81];
 /* 1). Клавише с ASCII-кодом 97 (буква a) назначается
    целая строка "клавиша a" */
 printf("\33[97;\"клавиша a\"p");
 /* 2). Клавише с ASCII-кодом 98 (буква b) назначается код
    66 (буква B) */
 printf("\33[98;66p");
 /* 3). Клавише с ASCII-кодом 99 (буква c) назначается
    последовательность кодов 65,66,67 (буквы ABC) */
 printf("\33[99;65;66;67p");
 /* 4). Клавише с ASCII-кодом 100 (буква c) назначается
    строка "перевод строки" и еще символ с кодом 10 */
 printf("\33[100;\"перевод строки\";10p");
 /* 5). Клавише с расширенным ASCII-кодом 0,59 (F1) назначается строка "клавиша F1"
    6). Клавише с расширенным ASCII-кодом 0,60 (F2) назначается код 98 (буква b) - 
        при обработке этого назначения не будет учитываться ранее сделанное нзначение для кода 98 */
 printf("\33[0;59;\"клавиша F1\"p\33[0;60;98p");
 /* При вводе этой строки убедимс, что назначение сработало */
 gets(string);
 /* Восстановление кодов */
 printf("\33[97;97p\33[98;98p\33[99;99p");
 printf("\33[100;100p\33[0;59;0;59p\33[0;60;0;60p");
 /* При вводе этой строки убедимся, что восстановление произошло */
 gets(string);
 }


0- правая клавиша Shift нажата;
1- левая клавиша Shift нажата;
2- клавиша Ctrl (левая или правая) нажата;
3- клавиша Alt (левая или правая) нажата;
4- режим ScrollLock;
5- режим NumLock;
6- режим CapsLock;
7- режим Insert.
0- левая клавиша Ctrl нажата;
1- левая клавиша Alt нажата;
2- клавиша SysReq нажата;
3- пауза;
4- клавиша ScrollLock нажата;
5- клавиша NumLock нажата;
6- клавиша CapsLock нажата;
7- клавиша Insert нажата.
КаталогИндекс раздела
НазадОглавлениеВперед