Приложение EASYTTY
Наше первое телекоммуникационное приложение EASYTTY демонстрирует использование функций Windows, предназначенных для работы с портами асинхронного последовательного адаптера и модемами.
Приложение EASYTTY выполняет все основные функции, которые должна поддерживать любая телекоммуникационная программа. EASYTTY позволяет передавать модему AT-команды, принимать от него ответ и отображать его на экране.
Перед тем как запускать приложение EASYTTY на вашем компьютере, следует создать в каталоге Windows файл EASYTTY.INI (см. листинг 7.6). Если вместе с книгой вы приобрели дискету, то скопируйте файл EASYTTY.INI из каталога WIN\EASYTTY в каталог Windows.
Листинг 7.6. Файл EASYTTY.INI
[Port]
Mode=COM2:9600,N,8,1
Файл EASYTTY.INI должен состоять из одного раздела [Port], содержащего единственную строку Mode. В этой строке определяется номер COM-порта, к которому подключен модем, скорость обмена и формат передаваемых данных. Формат строки Mode совпадает с форматом команды MODE операционной системы MS-DOS.
Запустите EASYTTY. Набирая на клавиатуре AT-команды модема, можно перевести его в любой режим. Например, можно сбросить текущую конфигурацию. Для этого введите команду ATZ и нажмите клавишу <Enter>. В ответ на введенную команду модем загрузит конфигурацию, принятую по умолчанию и вернет компьютеру ответ OK (см. рис. 7.2).
Чтобы приложение EASYTTY могло автоматически отвечать на вызов по телефонной линии, передайте модему команду ATS0=1 и нажмите клавишу <Enter>. Поэкспериментируйте с приложением EASYTTY, передавая модему различные команды и наблюдая на экране ответные сообщения.

Рис. 7.2. Приложение EASYTTY
Вы можете передать модему команду набора номера удаленного абонента. Чтобы набрать номер 777-77-77, достаточно ввести команду ATDP 777-77-77 и нажать клавишу <Enter>.
После установления связи с удаленным модемом вы можете обменяться с ним текстовыми сообщениями. Набирайте передаваемый текст на клавиатуре и просматривайте ответ от удаленного компьютера в главном окне приложения.
Когда вы закончите сеанс связи с удаленным компьютером, переведите модем в командный режим и передайте ему команду ATH0. Модем повесит трубку и отключится от телефонной линии.
Чтобы перевести модем из режима передачи данных в командный режим, подождите 2-3 секунды, наберите на клавиатуре три знака '+' и дождитесь от модема ответа OK.
Главный файл приложения EASYTTY приведен в листинге 7.7.
Листинг 7.7. Файл EASYTTY.CPP
#include <windows.h>
#include <string.h>
#include <conio.h>
#include <stdio.h>
// Определение констант
#define QUEUE_SIZE 1024
#define CTRL_Q 17
#define CTRL_S 19
#define ESC 27
// Прототипы функций
int InitCommPort(void);
int CloseCommPort(int idCommPort);
int ProcessExchange(int idCommPort);
// ============================================================
// Функция WinMain
// ============================================================
#pragma argsused
int PASCAL
WinMain( HANDLE hInstance,
HANDLE hPrevInstance,
LPSTR lpszCmdLine,
int cmdShow )
{
int idCommPort; // идентификатор COM-порта
// Позволяем одновременно запустить только
// одну копию приложения
if( hPrevInstance )
return( FALSE );
// Инициализация интерфейса EasyWin
_InitEasyWin();
// Открываем COM-порт и устанавливаем новый режим работы
idCommPort = InitCommPort();
// В случае возникновения ошибки при инициализации COM-порта
// завершаем работу приложения
if(idCommPort < 0)
return FALSE;
puts( "Для завершения программы нажмите клавишу <ESC>" );
while( TRUE )
{
MSG msg;
// Организуем цикл обработки сообщений
if( PeekMessage( ( LPMSG )&msg, NULL, 0, 0, PM_REMOVE ) )
{
// При получении сообщения WM_QUIT завершаем приложение
if ( msg.message == WM_QUIT )
return( msg.wParam );
TranslateMessage( &msg );
DispatchMessage ( &msg );
}
// Если в очереди приложения нет сообщений, начинаем обмен
// данными через COM-порт
else
{
if( !ProcessExchange( idCommPort ))
{
// Закрываем главное окно приложения
DestroyWindow(GetFocus());
}
}
}
}
// ============================================================
// Функция InitCommPort
// ============================================================
int InitCommPort()
{
DCB dcbCommPort; // структура DCB
int idCommPort; // идентификатор COM-порта
char szMsg[144]; // временный буфер
char szCommSettings[20]; // режим работы COM-порта
char szPortName[6]; // имя COM-порта
// Получаем из раздела [Port] файла PHONE.INI строку Mode,
// определяющую режим работы COM-порта и записываем ее в
// буфер szCommSettings
GetPrivateProfileString("Port", "Mode", "COM1:9600,n,8,1",
szCommSettings, sizeof(szCommSettings) - 1, "easytty.ini");
// Выделяем из полученной строки первые четыре символа,
// задающие номер COM-порта, для последующей передачи его
// функции OpenComm
lstrcpyn(szPortName, szCommSettings, 5);
szPortName[4] = '\0';
// Открываем COM-порт szPortName
if (( idCommPort =
OpenComm( szPortName, QUEUE_SIZE, QUEUE_SIZE )) < 0 )
{
// В случае ошибки отображаем сообщение и
// завершаем работу приложения
wsprintf( szMsg,
"Ошибка открытия порта.\nФункция OpenComm возвратила %d",
idCommPort );
MessageBox( NULL, szMsg,
"Ошибка", MB_OK | MB_ICONEXCLAMATION );
return( idCommPort );
}
// Удаляем все символы из выходной очереди
FlushComm( idCommPort, 0 );
// Удаляем все символы из входной очереди
FlushComm( idCommPort, 1 );
// Заполняем структуру DCB в соответствии с командной строкой
// Mode из раздела [Port] файла PHONE.INI
if( BuildCommDCB( szCommSettings, &dcbCommPort ) != 0 )
{
// В случае ошибки отображаем сообщение и
// завершаем работу приложения
MessageBox( NULL, "Ошибка при заполнении структуры DCB",
"Ошибка", MB_OK | MB_ICONEXCLAMATION );
return( -1 );
}
// Устанавливаем новый режим COM-порта в соответствии с
// подготовленной структурой DCB
if ( SetCommState( &dcbCommPort ) != 0 )
{
// В случае ошибки отображаем сообщение и
// завершаем работу приложения
MessageBox( NULL, "Ошибка установки режима COM-порта",
"Ошибка", MB_OK | MB_ICONEXCLAMATION );
return( -1 );
}
// Возвращаем идентификатор открытого COM-порта
return idCommPort;
}
// ============================================================
// Функция ProcessExchange
// ============================================================
int ProcessExchange( int idCommPort )
{
int nCharWaiting, nCharWriting;
COMSTAT ComStat;
char inBuff[ QUEUE_SIZE ];
// Определяем текущее состояние открытого COM-порта
GetCommError( idCommPort, &ComStat );
// Если во входной очереди уже есть данные, считываем их и
// выводим на экран
if ((nCharWaiting = ComStat.cbInQue ) > 0 )
{
// Считываем данные из входной очереди и
// помещаем их в буффер inBuff
nCharWaiting = ReadComm( idCommPort, inBuff,
( nCharWaiting > QUEUE_SIZE ?
QUEUE_SIZE : nCharWaiting ));
// Отображаем полученные символы на экране
if ( nCharWaiting > 0 )
for( int i = 0; i < nCharWaiting; i++ )
putch(inBuff[i]);
else
return( FALSE );
}
// Узнаем, нажата ли какая-нибудь клавиша на клавиатуре
else if ( kbhit() )
{
// Если клавиша нажата, определяем ее код
char keyHit = ( char )getch();
if ( !keyHit )
keyHit = ( char )getch();
// Если нажата клавиша <Esc>, закрываем COM-порт и завершаем
// работу приложения
if ( keyHit == ESC )
{
CloseCommPort( idCommPort );
return( FALSE );
}
// Записывем код нажатой клавиши в выходную очередь
// COM-порта
else
{
nCharWriting =
WriteComm( idCommPort, ( LPSTR )&keyHit, 1 );
// При возникновении ошибки завершаем приложение
if( nCharWriting < 0 )
return( FALSE );
}
}
return( TRUE );
}
// ============================================================
// Функция CloseCommPort
// ============================================================
int CloseCommPort( int idCommPort )
{
// Удаляем все символы из входной и выходной очереди
// COM-порта
FlushComm( idCommPort, 0 );
FlushComm( idCommPort, 1 );
// Закрываем COM-порт
CloseComm( idCommPort );
return 0;
}
Особенностью приложения EASYTTY является использование интерфейса EasyWin, предоставляемого средой разработки Borland C++ for Windows версии 3.1. Интерфейс EasyWin позволяет сократить до минимума код, требуемый для создания окна, вывода в него принимаемых данных и получения ввода с клавиатуры.
После запуска приложения EASYTTY, функция WinMain выполняет инициализацию интерфейса EasyWin. Для этого вызывается функция _InitEasyWin, описанная во включаемом файле STDIO.H:
_InitEasyWin();
После вызова этой функции появляется главное окно приложения. Теперь можно вызывать стандартные функции консольного ввода/вывода - puts, kbhit, getch, putch.
Затем функция WinMain вызывает функцию InitCommPort, определенную в приложении. Эта функция считывает из раздела [Port] файла PHONE.INI строку Mode, которая определяет номер COM-порта, к которому подключен модем и его режим работы. Потом InitCommPort открывает соответствующий порт и устанавливает его режим. Затем функция завершает свою работу и возвращает идентификатор открытого COM-порта.
Если COM-порт не открыт, то идентификатор открытого COM-порта равен нулю и приложение сразу завершает работу.
После того как порт открыт, вызывается функция puts:
puts( "Для завершения приложения нажмите клавишу <Esc>" );
Она выводит в окне приложения строку "Для завершения приложения нажмите клавишу <Esc>". Затем следует цикл while в котором вызывается функция PeekMessage и функция ProcessExchange, определенная в нашем приложении:
while( TRUE )
{
MSG msg;
// Организуем цикл обработки сообщений
if( PeekMessage( ( LPMSG )&msg, NULL, 0, 0, PM_REMOVE ) )
{
// При получении сообщения WM_QUIT завершаем приложение
if ( msg.message == WM_QUIT )
return( msg.wParam );
TranslateMessage( &msg );
DispatchMessage ( &msg );
}
// Если в очереди приложения нет сообщений, начинаем обмен
// данными через COM-порт
else
{
if( !ProcessExchange( idCommPort ))
{
// Закрываем главное окно приложения
DestroyWindow(GetFocus());
}
}
}
Функция PeekMessage образует цикл обработки сообщений, благодаря которому одновременно могут работать и другие приложения Windows.
Функция ProcessExchange является сердцем приложения EASYTTY. Она организует весь диалог пользователя с приложением. Для этого она считывает данные из входного буфера COM-порта, поступающие в него от модема, и отображает их в окне приложения. Если вы нажимаете на клавиши, функция ProcessExchange передает код клавиши в выходной буфер COM-порта.
Если вы нажмете клавишу <Esc>, функция ProcessExchange вызывает функцию CloseCommPort, определенную в приложении, а затем закрывает главное окно приложения, вызывая функцию DestroyWindow.
Теперь рассмотрим более подробно функции InitCommPort, ProcessExchange и CloseCommPort, определенные в приложении.
Функция InitCommPort считывает из раздела [Port] файла EASYTTY.INI строку Mode, определяющую режим работы COM-порта и записывает ее в буфер szCommSettings. Если файл EASYTTY.INI не обнаружен в каталоге Windows или в нем не определена строка Mode, в буфер szCommSettings записывается строка "COM1:9600,n,8,1".
Затем из строки в буфере szCommSettings выделяются первые четыре символа, задающие номер COM-порта для последующей передачи его функции OpenComm. Функция OpenComm открывает этот COM-порт.
if((idCommPort =
OpenComm(szPortName, QUEUE_SIZE, QUEUE_SIZE)) < 0 )
{
...
}
Если COM-порт не открыт, OpenComm возвращает отрицательное значение. Функция отображает предупреждающее сообщение "Ошибка открытия порта..." и завершает работу.
Если COM-порт успешно открыт, удаляем все символы из входной и выходной очередей:
// Удаляем все символы из выходной очереди
FlushComm( idCommPort, 0 );
// Удаляем все символы из входной очереди
FlushComm( idCommPort, 1 );
Затем заполняем структуру dcbCommPort типа DCB в соответствии с командной строкой Mode из раздела [Port] файла EASYTTY.INI. Для этого используем функцию BuildCommDCB, передав ей строку szCommSettings:
if( BuildCommDCB( szCommSettings, &dcbCommPort ) != 0 )
{
// В случае ошибки отображаем сообщение и
// завершаем работу приложения
MessageBox( NULL, "Ошибка при заполнении структуры DCB",
"Ошибка", MB_OK | MB_ICONEXCLAMATION );
return( -1 );
}
Если BuildCommDCB возвращает значение, не равное нулю, значит произошла ошибка. В этом случае выводим сообщение "Ошибка при заполнении структуры DCB" и завершаем функцию, возвращая число -1.
В случае успешного выполнения функции BuildCommDCB устанавливаем новый режим COM-порта в соответствии с подготовленной структурой DCB:
if( SetCommState( &dcbCommPort ) != 0 )
{
// В случае ошибки отображаем сообщение и
// завершаем работу приложения
MessageBox( NULL, "Ошибка установки режима COM-порта",
"Ошибка", MB_OK | MB_ICONEXCLAMATION );
return( -1 );
}
Если SetCommState возвращает ненулевое значение, значит произошла ошибка. В этом случае выводим сообщение "Ошибка установки режима COM-порта" и завершаем функцию InitCommPort, возвращая число -1.
Если функция SetCommState завершилась успешно, функция InitCommPort завершает работу и возвращает идентификатор открытого COM-порта. Позже полученный идентификатор COM-порта передается функциям ProcessExchange и CloseCommPort.
Функция ProcessExchange вызывает GetCommError, заполняющую структуру ComStat типа COMSTAT:
COMSTAT ComStat;
// Определяем текущее состояние открытого COM-порта
GetCommError( idCommPort, &ComStat );
Поле cbInQue структуры ComStat будет определять количество символов во входной очереди используемого нами COM-порта. Если во входной очереди есть данные, считываем их и выводим их в окно приложения:
nCharWaiting = ReadComm( idCommPort, inBuff,
( nCharWaiting > QUEUE_SIZE ? QUEUE_SIZE :
nCharWaiting ));
// Отображаем полученные символы на экране
if ( nCharWaiting > 0 )
for( int i = 0; i < nCharWaiting; i++ ) putch(inBuff[i]);
else
return( FALSE );
Если входная очередь COM-порта пуста, с помощью стандартной функции консольного ввода/вывода kbhit проверяем, нажата ли какая-нибудь клавиша на клавиатуре.
В случае, если клавиша нажата, определяем ее код:
// Если клавиша нажата, определяем ее код
char keyHit = ( char )getch();
if ( !keyHit )
keyHit = ( char )getch();
Проверяем, нажата ли клавиша <Esc>. Если нажата клавиша <Esc>, закрываем COM-порт с помощью функции CloseCommPort и возвращаем значение FALSE:
// Закрываем COM-порт и возвращаем значение FALSE
CloseCommPort( idCommPort );
return( FALSE );
Если пользователь нажал любую другую клавишу, записываем ее код в выходную очередь COM-порта:
nCharWriting = WriteComm( idCommPort, ( LPSTR )&keyHit, 1 );
На этом работа функции завершена, и мы переходим к рассмотрению функции CloseCommPort.
Функция CloseCommPort наиболее простая из функций приложения EASYTTY. Она удаляет все символы из входной и выходной очереди COM-порта, а затем закрывает COM-порт и возвращает управление:
FlushComm( idCommPort, 0 );
FlushComm( idCommPort, 1 );
CloseComm( idCommPort );
return 0;
Файл определения модуля для приложения EASYTTY приведен в листинге 7.8.
Листинг 7.8. Файл EASYTTY.DEF
; ==========================================================
; Файл определения модуля
; ==========================================================
NAME EASYTTY
DESCRIPTION 'Приложение EASYTTY, (C) 1994, Frolov G.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 16384
HEAPSIZE 16384
CODE preload moveable discardable
DATA preload moveable multiple