Новые книги

Эрминия Ибарра, профессор с мировым именем, преподаватель INSEAD, бросает вызов общепринятому мнению о лидерстве. Согласно ее концепции «восприятия извне», чтобы научиться думать как лидер, вам необходимо начать действовать как лидер. При помощи практических инструментов из этой книги вы сможете стать лидером, которому доверяют, ради которого не боятся рисковать, которого уважают и которым восхищаются.
Windows XP – это одна из самых популярных операционных систем. Дома, на работе, в Интернет-кафе вы не сможете работать на компьютере, не умея работать с этой операционной системой.

С помощью этой книги вы быстро освоите все необходимые навыки для работы с Windows XP. Прочитав ее, вы узнаете, как установить, настроить и управлять этой операционной системой.

Экранные библиотеки и работа с видеопамятью.

8. Экранные библиотеки и работа с видеопамятью.

Терминал в UNIX с точки зрения программ - это файл. Он представляет собой два устройства: при записи write() в этот файл осуществляется вывод на экран; при чтении read()-ом из этого файла - читается информация с клавиатуры.

Современные терминалы в определенном смысле являются устройствами прямого доступа:

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

Традиционные терминалы являются самостоятельными устройствами, общающимися с компьютером через линию связи. Протокол* - общения образует систему команд терминала и может быть различен для терминалов разных моделей. Поэтому библиотека работы с традиционным терминалом должна решать следующие проблемы:

  • настройка на систему команд данного устройства, чтобы одна и та же программа работала на разных типах терминалов.
  • эмуляция недостающих в системе команд; максимальное использование предоставленных терминалом возможностей.
  • мимнимизация передачи данных через линию связи (для ускорения работы).
  • было бы полезно, чтобы библиотека предоставляла пользователю некоторые логические абстракции, вроде ОКОН - прямоугольных областей на экране, ведущих себя подобно маленьким терминалам.

В UNIX эти задачи решает стандартная библиотека curses (а только первую задачу более простая библиотека termcap). Для настройки на систему команд конкретного дисплея эти библиотеки считывают описание системы команд, хранящееся в файле /etc/termcap. Кроме них бывают и другие экранные библиотеки, а также существуют иные способы работы с экраном (через видеопамять, см. ниже).

В задачах данного раздела вам придется пользоваться библиотекой curses. При компиляции программ эта библиотека подключается при помощи указания ключа -lcurses, как в следующем примере:

    cc progr.c -Ox -o progr -lcurses -lm
Здесь подключаются две библиотеки:
  • /usr/lib/libcurses.a (работа с экраном) и
  • /usr/lib/libm.a (математические функции, вроде sin, fabs). Ключи для подключения библиотек должны быть записаны в команде САМЫМИ ПОСЛЕДНИМИ. Заметим, что стандартная библиотека языка Си (содержащая системные вызовы, библиотеку stdio (функции printf, scanf, fread, fseek, ...), разные часто употребляемые функции (strlen, strcat, sleep, malloc, rand, ...)) /lib/libc.a подключается автоматически и не требует указания ключа -lc.

В начале своей программы вы должны написать директиву

    #include <curses.h>
подключающую файл /usr/include/curses.h, в котором описаны форматы данных, используемых библиотекой curses, некоторые предопределенные константы и.т.п. (это надо, чтобы ваша программа пользовалась именно этими стандартными соглашениями). Посмотрите в этот файл!

Когда вы пользуетесь curses-ом, вы НЕ должны пользоваться функциями стандартной библиотеки stdio для непосредственного вывода на экран; так вы не должны пользоваться функциями printf, putchar. Это происходит потому, что curses хранит в памяти процесса копию содержимого экрана, и если вы выводите что-либо на экран терминала обходя функции библиотеки curses, то реальное содержимое экрана и позиция курсора на нем перестают соответствовать хранимым в памяти, и библиотека curses начнет выводить неправильное изображение.

       ПРОГРАММА
        |  |
        | CURSES---копия экрана
        | printw,addch,move
        |  |
        V  V
     библиотека STDIO --printf,putchar----> экран
Таким образом, curses является дополнительным "слоем" между вашей программой и стандартным выводом и игнорировать этот слой не следует.

Напомним, что изображение, создаваемое при помощи библиотеки curses, сначала формируется в памяти программы без выполнения каких-либо операций с экраном дисплея (т.е. все функции wmove, waddch, waddstr, wprintw изменяют только ОБРАЗЫ окон в памяти, а на экране ничего не происходит!). И лишь только ПОСЛЕ того, как вы вызовете функцию refresh() ("обновить"), все изменения происшедшие в окнах будут отображены на экране дисплея (такое одновременное обновление всех изменившихся частей экрана позволяет провести ряд оптимизаций). Если вы забудете сделать refresh - экран останется неизменным. Обычно эту функцию вызывают перед тем, как запросить у пользователя какой-либо ввод с клавиатуры, чтобы пользователь увидел текущую "свежую" картинку. Хранение содержимого окон в памяти программы позволяет ей считывать содержимое окон, тогда как большинство обычных терминалов не способны выдать в компьютер содержимое какой-либо области экрана.

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

Выбор между "традиционной" работой с экраном и прямым доступом (фактически - между мобильностью и скоростью) - вопрос принципиальный, тем не менее принятие решения зависит только от вас. Видеопамять IBM PC в текстовом режиме 80x25 16 цветов имеет следующую структуру:

       struct symbol{       /* IBM PC family           */
            char chr;       /* код символа             */
            char attr;      /* атрибуты символа (цвет) */
       } mem[ 25 ] [ 80 ];  /* 25 строк по 80 символов */
Структура байта атрибутов:
     ------------------------------------------
     | 7   | 6 | 5 | 4 | 3         | 2 | 1 | 0 | # бита
     ------------------|-----------------------
     |blink| R | G | B | intensity | r | g | b | цвет
     ------------------|-----------------------
    background (фон)  | foreground (цвет букв)
R - red (красный) G - green (зеленый) B - blue (синий)
blink - мерцание букв (не фона!)
intensity - повышенная яркость

Координатная система на экране: верхний левый угол экрана имеет координаты (0,0), ось X горизонтальна, ось Y вертикальна и направлена сверху вниз.

Цвет символа получается смешиванием 3х цветов: красного, зеленого и синего (электронно-лучевая трубка дисплея имеет 3 электронные пушки, отвечающие этим цветам). Кроме того, допустимы более яркие цвета. 4 бита задают комбинацию 3х основных цветов и повышенной яркости. Образуется 2**4=16 цветов:

                 I R G B   номер цвета
    BLACK        0 0 0 0    0    черный
    BLUE         0 0 0 1    1    синий
    GREEN        0 0 1 0    2    зеленый
    CYAN         0 0 1 1    3    циановый (серо-голубой)
    RED          0 1 0 0    4    красный
    MAGENTA      0 1 0 1    5    малиновый
    BROWN        0 1 1 0    6    коричневый
    LIGHTGRAY    0 1 1 1    7    светло-серый (темно-белый)
    DARKGRAY     1 0 0 0    8    темно-серый
    LIGHTBLUE    1 0 0 1    9    светло-синий
    LIGHTGREEN   1 0 1 0   10    светло-зеленый
    LIGHTCYAN    1 0 1 1   11    светло-циановый
    LIGHTRED     1 1 0 0   12    ярко-красный
    LIGHTMAGENTA 1 1 0 1   13    ярко-малиновый
    YELLOW       1 1 1 0   14    желтый
    WHITE        1 1 1 1   15    (ярко)-белый

Физический адрес видеопамяти IBM PC в цветном алфавитно-цифровом режиме (80x25, 16 цветов) равен 0xB800:0x0000. В MS DOS указатель на эту память можно получить при помощи макроса make far pointer: MK_FP (это должен быть far или huge указатель!). В XENIX** - указатель получается при помощи системного вызова ioctl, причем система предоставит вам виртуальный адрес, ибо привелегия работы с физическими адресами в UNIX принадлежит только системе. Работу с экраном в XENIX вы можете увидеть в примере "осыпающиеся буквы".

8.1.

    /*#! /bin/cc  fall.c -o fall -lx
     *      "Осыпающиеся буквы".
     *      Использование видеопамяти IBM PC в ОС XENIX.
     *      Данная программа иллюстрирует доступ к экрану
     *      персонального компьютера как к массиву байт;
     *      все изменения в массиве немедленно отображаются на экране.
     *          Функция nap() находится в библиотеке -lx
     *      Показана также работа с портами IBM PC при помощи ioctl().
     */
    #include <stdio.h>
    #include <fcntl.h>         /* O_RDWR */
    #include <signal.h>
    #include <ctype.h>
    #include <sys/types.h>
    #include <sys/at_ansi.h>
    #include <sys/kd.h>        /* for System V/4 and Interactive UNIX only */
    /*#include <sys/machdep.h>    for XENIX and SCO UNIX only */
    #include <sys/sysmacros.h>
    #ifdef M_I386
    # define far    /* на 32-битной машине far не требуется */
    #endif
    char far *screen;   /* видеопамять как массив байт */
                        /* far - "длинный" (32-битный) адрес(segment,offset) */
    int     segm;       /* сегмент с видеопамятью */
    #define COLS  80        /* число колонок на экране */
    #define LINES 25        /* число строк */
    #define DELAY 20        /* задержка (миллисекунд) */
    int  ega;               /* дескриптор для доступа к драйверу EGA */
    /* структура для обмена с портами */
    static struct port_io_struct    PORT[ 4 /* не более 4 за раз */] = {
           /* операция       номер порта   данные */
           /*   .dir           .port        .data */
     /* Переустановить flip/flop:
      * заставить порт 0x3C0 ожидать пары адрес/значение
      * при последовательной записи байтов в этот порт.
      */
        {   IN_ON_PORT,     0x3DA,       -1               },
           /* IN-чтение */
     /* Теперь 3c0 ожидает пары адрес/значение */
        {   OUT_ON_PORT,    0x3C0,       -1 /* адрес */   },
        {   OUT_ON_PORT,    0x3C0,       -1 /* значение*/ },
           /* OUT-запись */
     /* переинициализировать дисплей, установив бит #5 порта 3c0 */
        {   OUT_ON_PORT,    0x3C0,       0x20             }
    };
    void closescr(nsig){             /* конец работы */
            setbgcolor(0);  /* установить черный фон экрана */
            exit(0);
    }
    /* получение доступа к видеопамяти адаптера VGA/EGA/CGA */
    void openscr () {
        static struct videodev {
            char *dev; int mapmode;
        } vd[] = {
            { "/dev/vga", MAPVGA },
            { "/dev/ega", MAPEGA },
            { "/dev/cga", MAPCGA },
            { NULL,       -1     }
        }, *v; /* устройство для доступа к видеоадаптеру */
        for(v=vd; v->dev;v++ )
            if((ega = open (v->dev, O_RDWR)) >= 0 ) goto ok;
        fprintf( stderr, "Can't open video adapter\n" );
        exit(1);
    ok:
        /* fprintf(stderr, "Adapter:%s\n", v->dev); */
        /* получить адрес видеопамяти и доступ к ней */
    #ifdef M_I386
        screen = (char *) ioctl (ega, v->mapmode, 0);
    #else
        segm = ioctl (ega, v->mapmode, 0);
        screen = sotofar (segm, 0); /* (segment,offset) to far pointer */
    #endif
        signal( SIGINT, closescr );
    }
    /* макросы для доступа к байтам "символ" и "атрибуты"
     * в координатах (x,y) экрана.
     */
    #define GET(x,y)        screen[ ((x) + (y) * COLS ) * 2 ]
    #define PUT(x,y, c)     screen[ ((x) + (y) * COLS ) * 2 ]  = (c)
    #define GETATTR(x,y)        screen[ ((x) + (y) * COLS ) * 2 + 1 ]
    #define PUTATTR(x,y, a)     screen[ ((x) + (y) * COLS ) * 2 + 1 ] = (a)
    /* символ изображается как черный пробел ? */
    #define white(c,a) ((isspace(c) || c==0) && (attr & 0160)==0)
    /* установить цвет фона экрана */
    void setbgcolor( color ){
         PORT[1].data = 0;  /* регистр номер 0 палитры содержит цвет фона */
         /* всего в палитре 16 регистров (0x00...0xFF) */
         PORT[2].data = color ;
         /* новое значение цвета, составленное как битовая маска
          *  RGBrgb  (r- красный, g- зеленый, b- синий, RGB- дополнительные
          *  тусклые цвета)
          */
         /* выполнить обмены с портами */
         if( ioctl( ega, EGAIO, PORT ) < 0 ){
               fprintf( stderr, "Can't out port\n" );
               perror( "out" );
         }
    }
    void main(ac, av) char **av;{
            void fall();
            openscr();
            if( ac == 1 ){
                setbgcolor(020);    /* темно-зеленый фон экрана */
                fall();             /* осыпание букв */
            } else {
                if(*av[1] == 'g')
    /* Установить режим адаптера graphics 640x350 16-colors */
                     ioctl( ega, SW_CG640x350, NULL);
    /* Если вы хотите получить адрес видеопамяти в графическом режиме,
     * вы должны СНАЧАЛА включить этот режим,
     * ЗАТЕМ сделать screen=ioctl(ega, v->mapmode, NULL);
     * и ЕЩЕ РАЗ сделать включение графического режима.
     */
    /* Установить режим адаптера text 80x25 16-colors       */
                else ioctl( ega, SW_ENHC80x25, NULL);
            }
            closescr(0);
    }
    /* осыпать буквы вниз */
    void fall(){
            register i, j;
            int rest;
            int nextcol;
            int n;
            int changed = 1;        /* не 0, если еще не все буквы опали */
            char mask [ COLS ];
            while( changed ){
               changed = 0;
               for( i = 0 ; i < COLS ; i++ )
                    mask[ i ] = 0;
               for( i = 0 ; i < COLS ; i++ ){
                    rest = COLS - i;   /* осталось осыпать колонок */
                    nextcol = rand() % rest;
                    j = 0;  /* индекс в mask */
                    n = 0;  /* счетчик */
                    for(;;){
                            if( mask[j] == 0 ){
                                    if( n == nextcol ) break;
                                    n++;
                            } j++;
                    }
                    changed += fallColumn( j );
                    mask[j] = 1;
               }
            }
    }
    /* осыпать буквы в одном столбце */
    int fallColumn( x ){
            register int y;
            char ch, attr;
            int firstspace = (-1);
            int firstnospace = (-1);
    Again:
            /* find the falled array */
            for( y=LINES-1; y >= 0 ; y-- ){
                    ch = GET( x, y );
                    attr = GETATTR( x,y );
                    if( white(ch, attr)){
                        firstspace = y;
                        goto FindNoSpace;
                    }
            }
    AllNoSpaces:
            return 0;       /* ничего не изменилось */
    FindNoSpace:            /* найти не пробел */
            for( ; y >= 0 ; y-- ){
                    ch = GET( x, y );
                    attr = GETATTR( x, y );
                    if( !white(ch, attr)){
                            firstnospace = y;
                            goto Fall;
                    }
            }
    AllSpaces:       /* в данном столбце все упало */
            return 0;
    Fall:
            /* "уронить" букву */
            for( y = firstnospace ; y < firstspace ; y++ ){
                    /* переместить символ на экране на одну позицию вниз */
                    ch   = GET( x, y );
                    attr = GETATTR( x, y );
                    PUT( x, y, 0 );
                    PUTATTR( x, y, 0 );
                    PUT( x, y+1 , ch );
                    PUTATTR( x, y+1, attr );
                    nap( DELAY );   /* подождать DELAY миллисекунд */
            }
            return 1;               /* что-то изменилось */
    }

* - Под протоколом в программировании подразумевают ряд соглашений двух сторон (сервера и клиентов; двух машин в сети (кстати, термин для обозначения машины в сети "host" или "site")) о формате (правилах оформления) и смысле данных в передаваемых друг другу сообщениях. Аналогия из жизни - человеческие речь и язык. Речь всех людей состоит из одних и тех же звуков и может быть записана одними и теми же буквами (а данные - байтами). Но если два человека говорят на разных языках - т.е. поразному конструируют фразы и интерпретируют звуки - они не поймут друг друга!

** - XENIX - (произносится "зиникс") версия UNIX для IBM PC, первоначально разработанная фирмой Microsoft и поставляемая фирмой Santa Cruz Operation (SCO).

© Copyright А. Богатырев, 1992-95
Си в UNIX

Назад | Содержание | Вперед