This article first appeared online at ClarionOnline ( http://www.clariononline.com ) in volume 2 issue 5 (December 1998)
32-разрядная ДОС? Не совсем, но близко
В эру разработки под Windows, большинство
программистов уже забыли что не все программы
должны использовать графический интерфейс.
Фактически имеется два типа 32-разрядных
приложений, которые могут работать под Windows -
обычные GUI приложения, которые мы все знаем и
любим, и консольные приложения.
В соответствии со своим наименованием,
консольные приложения "предоставляют консоли,
которые управляют вводом и выводом (I/O) в
символьном режиме". Не часто (почти никогда)
нам нужно написать консольное приложение на Clarion,
только из-за того, что так легко писать
GUI-программы - для этого надо только нажать Ctrl-F,
нарисовать экран и написать слово ACCEPT.
Однако, всегда остается вероятность что однажды,
по некоторым причинам, вам потребуется написать
консольное приложение. Если вы думаете о них как
32-разрядных приложениях ДОС, что действительно
очень близко к истине, то, я уверен, вы найдете им
немного болше применений. Хотя обработка
консольного экрана не так легка, но после того
как вы напишите пару функций wrapper-ов, вы сможете
быстро писать символьные приложения, которые не
имеют досовской сегментации памяти segment:offset, а
используют всю линейную память машины.
Итак, в этой статье, я собираюсь показать как
писать консольное приложение. Оно ничего не
делает кроме приглашения пользователю нажать
клавишу, и когда он нажмет "X" - программа
завершается. Вы, конечно, можете сделать гораздо
больше, например, устанавливать позицию курсора
и цвета символов и фона, но для начала мы оставим
программу простой.
Имеется 42 функции API, относящиеся к консольным
приложениям, но сегодня мы будем использовать
только некоторые из них:
GetStdHandle
SetStdHandle
AllocConsole
FreeConsole
CreateConsoleScreenBuffer
SetConsoleActiveScreenBuffer
WriteConsole
ReadConsole
SetConsoleTitle
SetConsoleMode
GetConsoleMode
Предположив, что мы прототипировали функции
правильно (используя истиный MSDN), первое, что нам
нужно слелать, это создать консоль. GUI приложения
не имеют консоли - на то они и GUI приложения!
Консоль может иметь один входной поток и много
потоков вывода, один из которых должен быть
активным. В терминах Микрософт они называются
"console input buffer " и "console screen buffer"
Для создания консоли, мы вызываем AllocConsole() (мы
можем только вызвать AllocConsole() из GUI приложения - в
других средах, которые могут создавать истиные
консольные приложения, вызов AllocConsole() вызовет
ошибку). Это создаст новую консоль для
использования в GUI приложении, и установит
стандартный ввод, вывод и обработку ошибок.
If ~AllocConsole()
Halt(1,'Unable to allocate console (' &GetLastError() & ')')
End
Второе, что мы должны сделать, это создать первый
(и возможно следующие) буферы консольного экрана.
Мы делаем это вызовом функции CreateConsoleScreenBuffer().
Если нужно больше экранных буферов, то вызваем
эту функцию столько раз, сколько нужно (может
потребоваться подготовить экран для фона и
выводить его когда нужно). Как и для многих других
вызовов API, вы должны передать права доступа,
которые вы хотите иметь для объекта. Я не
представляю, зачем может потребоваться консоль
без прав записи и чтения, но это возможно.
В следующем примере, 0 означает, что буфер не
может быть поделен (shared) для доступа чтения и
записи, а CONSOLE_TEXTMODE_BUFFER это только тип создаваемой
консоли.
Loc:OutputHandle =
CreateConsoleScreenBuffer(GENERIC_READ+GENERIC_WRITE,0,NULL,CONSOLE_TEXTMODE_BUFFER,NULL)
If Loc:OutputHandle = INVALID_HANDLE_VALUE
Halt(2,'Unable to create console (' &GetLastError() & ')')
End
После того как буфер экрана создан, нужно сделать
его активным, и только из-за того что это демо
программа, я также устанавливаю заголовок
консоли. Вы выполните это вызовом
SetConsoleActiveScreenBuffer() и SetConsoleTitle(). Убедитесь, что
SetConsoleTitle() работает в текущем окне консоли, вот
почему мы не передали ей хендл текущего буфера
экрана - мы можем иметь несколько буферов экрана,
но они все разделяют одно общее физическое окно.
If SetConsoleActiveScreenBuffer(Loc:OutputHandle) = 0 Then
Halt(3,'Unable to set active console (' &GetLastError() & ')')
End
Loc:Windowtitle = 'Clarion 5 32-bit Console Application'
If SetConsoleTitle(Address(Loc:Windowtitle)) = 0 Then
Message('Unable to set console title (' &GetLastError() & ')')
End
Если вы собираетесь читать с консоли (то есть
чтобы пользователь нажимал на клавиши в ответ на
приглашение), то нужно получить также хендл
потока ввода. Я нашел, что Win2000 и Win95/98 здесь слегка
отличаются. Win2000 позволяет вам читать прямо из
STD_INPUT_HANDLE, а под Win95/98 нужно получить сначала
стандартный хендл и использовать возвращенную
копию.
Loc:InputHandle = GetStdHandle(STD_INPUT_HANDLE)
If Loc:InputHandle = INVALID_HANDLE_VALUE
Halt(4,'Unable to get console input handle (' &GetLastError() & ')')
End
Есть несколько разных опций, которые можно
установить в зависимости от ввода с консоли. Это:
Значене
Смысл
----------------------------------------------------------------------
ENABLE_LINE_INPUT Любые данные, читаемые с консоли,
должны сопровождаться символом возвратом
каретки.
ENABLE_ECHO_INPUT Символы пишутся в активных буфер
экрана как они читаются. Этот режим может
использоваться только если также установлен
режим ENABLE_LINE_INPUT.
ENABLE_PROCESSED_INPUT Ctrl+C обрабатывается системой и не
помещается в буфер ввода. Если также включен
режим ENABLE_LINE_INPUT, символы backspace, carriage return, и linefeed
обрабатываются системой.
ENABLE_WINDOW_INPUT Действия пользователя, которые
изменяют размер буфера экрана консоли, are reported in
the console's input buffer.
ENABLE_MOUSE_INPUT Если указатель мыши находится внутри
бордюра консольного окна и окно имеет
клавиатурный фокус, события мыши, генерируемые
при перемещении мыши и нажатия на клавиши мыши
помещаются в буфер ввода.
Когда консоль создана, все режимы ввода, кроме
ENABLE_WINDOW_INPUT включены по умолчанию, но в моем
случае, я не хочу, чтобы пользователь нажимал ENTER
для завершения ввода, поэтому я хочу запретить
ENABLE_LINE_INPUT. Я хочу оставить ENABLE_ECHO_INPUT, но он может
использоваться только вместе с ENABLE_LINE_INPUT. Снова,
из-за того что это простое приложение, я не
забочусь о мышке или событиях изменения размера
окна, поэтому я заканчиваю делая ENABLE_PROCESSED_INPUT:
If ~SetConsoleMode(Loc:InputHandle,ENABLE_PROCESSED_INPUT )
Halt(5,'Unable to set console mode (' &GetLastError() & ')')
End
Если вы достигли этой точки, ваша консоль
настроена так, что вы можете читать и писать с
консоли при помощи ReadConsole() и WriteConsole(). Любые
нажимаемые пользователем клавиши не повторяются
на экрана, но консоль автоматически обрабатывает
backspace и enter. В этом примере, я только приглашаю
пользователя нажать клавишу, и произвожу выход
из программы при нажатии клавиши 'X'.
Loop
Loc:OutBuffer = 'press any key to continue, X to quit<10><13>'
x# =
WriteConsole(Loc:OutputHandle,Address(Loc:OutBuffer),Len(Loc:OutBuffer),Address(Loc:BytesWritten),NULL)
Clear(Loc:InBuffer)
Loop
IF
ReadConsole(Loc:InputHandle,Address(Loc:InBuffer),100,Address(Loc:BytesRead),NULL) = 0
THEN
Halt(5,'Error on read console (' & GetLastError()&
')')
Break
End
Until Loc:BytesRead > 0
If Upper(Loc:Inbuffer[1]) = 'X' Then
Break
End
End
Все, что теперь осталось - не забыть освободить
консоль, когда приложение завершает свою работу.
If ~FreeConsole()
Halt(6,'Unable to free console (' &GetLastError() & ')' )
End
И виола! У вас теперь есть простое 32-разрядное
приложение символьного режима. На первый взгляд,
его применение довольно ограничено - даже MSDN
только использует пример обработки GUI создания
консоли, когда возникает ошибка, что
предотвращает его от использования обычного
графического интерфейса. Если достаточно
поработать, я не вижу причины, почему обычные
приложении CDD не могут быть конвертированы, but that's
almost certainly beyond the scope of all but the simplest DOS applications.
Итак, возвращаясь к основному вопросу - зачем? Да,
есть некоторое количество приложений Windows,
которые легче писать в символьном режиме - что
либо для отладки, это первое, что приходит в
голову (для тех из вас, кто читает журнал Др.Добса,
мой порт в CW к символьному отладчику в ноябрьском
номере, может быть примером).
So, back to the original question - why? Well, there are a number of Windows applications
that are easier to write if they're character based - anything to do with debugging is the
first example that springs to mind (for those of you that read Dr Dobbs, my CW port of the
character-based debugger in November's issue is progressing, although not as fast as I'd
like).
Выводы
I'll be the first to admit that in the grand scheme of things it isn't much, but you never
know when it may come in useful.
Загрузить исходный код
Вернуться на домашнюю страницу.
Или послать мне письмо на paula@attglobal.net
(c) 1999 Paul Attryde