Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Модемы и факс-модемы. Программирование для MS-DOS и Windows.

© Александр Фролов, Григорий Фролов
Том 16, М.: Диалог-МИФИ, 1993.

[Назад] [Содеожание] [Дальше]

7.3. Приложение 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 и нажмите клавишу . В ответ на введенную команду модем загрузит конфигурацию, принятую по умолчанию и вернет компьютеру ответ OK (см. рис. 7.2).

Чтобы приложение EASYTTY могло автоматически отвечать на вызов по телефонной линии, передайте модему команду ATS0=1 и нажмите клавишу . Поэкспериментируйте с приложением EASYTTY, передавая модему различные команды и наблюдая на экране ответные сообщения.

Рис. 7.2. Приложение EASYTTY

Вы можете передать модему команду набора номера удаленного абонента. Чтобы набрать номер 777-77-77, достаточно ввести команду ATDP 777-77-77 и нажать клавишу .

После установления связи с удаленным модемом вы можете обменяться с ним текстовыми сообщениями. Набирайте передаваемый текст на клавиатуре и просматривайте ответ от удаленного компьютера в главном окне приложения.

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

Чтобы перевести модем из режима передачи данных в командный режим, подождите 2-3 секунды, наберите на клавиатуре три знака '+' и дождитесь от модема ответа OK.

Главный файл приложения EASYTTY приведен в листинге 7.7.

Листинг 7.7. Файл EASYTTY.CPP

#include	
#include	
#include	
#include	

// Определение констант
#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( "Для завершения программы нажмите клавишу " );

	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();

		// Если нажата клавиша , закрываем 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( "Для завершения приложения нажмите клавишу " );

Она выводит в окне приложения строку "Для завершения приложения нажмите клавишу ". Затем следует цикл 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-порта.

Если вы нажмете клавишу , функция 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();

Проверяем, нажата ли клавиша . Если нажата клавиша , закрываем COM-порт с помощью функции CloseCommPort и возвращаем значение FALSE:

V// Закрываем 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
[Назад] [Содеожание] [Дальше]