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

       

Приложение 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


Содержание раздела