7. Модемы и операционная система WindowsДанная глава книги будет посвящена программированию асинхронных последовательных адаптеров и модемов в среде операционной системы Windows. В ней вы найдете подробное описание функций программного интерфейса Windows, сообщений и структур данных, предназначенных для взаимодействия с устройствами (модемами, факс-модемами), подключенными к асинхронному последовательному адаптеру. В этой главе нами приведены исходные тексты небольших телекоммуникационных программ, предназначенных для работы в среде операционной системы Windows. 7.1. Драйвер асинхронного последовательного адаптераДрайвер асинхронного последовательного адаптера представляет собой специальную библиотеку динамической компоновки. Он экспортирует набор функций, которые Windows может использовать для того, чтобы открыть порт, установить новый режим его работы, передать и принять данные, определить состояние порта и причину возникновения ошибок. Однако приложения не обращаются непосредственно к этим функциям. Вместо этого они обращаются к функциям, экспортируемым из модуля USER, операционной системы Windows - OpenComm, CloseComm и т. д. Подробное описание данных функций представлено в следующем разделе книги. Функции из модуля USER обращаются уже непосредственно к драйверу асинхронного последовательного адаптера. В состав операционных систем Windows 3.1 и Windows for Worksgroups 3.11 входит драйвер асинхронного последовательного адаптера COMM.DRV. Этот драйвер позволяет получить доступ к портам COM1, COM2, COM3 и COM4. При этом драйвер использует для COM-портов адреса, записанные в области переменных (данных) BIOS. Если область переменных BIOS не содержит информации о COM-портах, то базовые адреса и номера линии IRQ пользователь должен установить самостоятельно при помощи приложения Control Panel. Введенная вами информация записывается в директивах COMxBase и COMxIRQ из раздела [386Enh] файла SYSTEM.INI. Данные из этих директив используются только в случае, когда в области переменных BIOS нет данных. Драйвер асинхронного последовательного адаптера определяет тип микросхемы UART, на основе которой построен асинхронный последовательный адаптер. Если он обнаруживает UART 16550A, то включает аппаратный буфер FIFO, расположенный на нем. В этом случае Windows устойчиво работает на скоростях 9600 бит/с и выше. Многие компьютеры, на которых не установлена эта микросхема, теряют информацию во время передачи данных и не могут установить связь на скорости выше 9600 бит/с. 7.2. Телекоммуникационные функцииПрактически все функции, которые мы будем описывать в разделе "Телекоммуникационные функции", могут быть использованы для работы с параллельным адаптером. Однако из-за ограниченного объема книги мы не приводим сведений об использовании параллельных портов компьютера. 7.2.1. Функция OpenCommПеред тем как приложение сможет начать работу с портом асинхронного последовательного адаптера, оно должно открыть этот порт при помощи функции OpenComm. int OpenComm(LPCSTR lpszDevControl, UINT cbInQueue, UINT cbOutQueue); Первый параметр функции lpszDevControl определяет открываемый порт и является указателем на строку, закрытую двоичным нулем, содержащую имя порта. Строка должна иметь формат "COMn" для асинхронного последовательного адаптера или "LPTn" для параллельного адаптера. Вместо символа n следует указать номер открываемого порта. Как вы уже знаете из предыдущих разделов, COM-порт работает под управлением специального драйвера асинхронного последовательного адаптера. Драйвер принимает данные из порта и записывает их во входную очередь. Затем приложение, по мере надобности может прочитать данные из входной очереди. Когда приложение передает данные в COM-порт, они сначала попадают в выходную очередь драйвера, после чего драйвер передает их непосредственно асинхронному адаптеру. При вызове функции OpenComm вы должны сами определить размер входной и выходной очереди драйвера асинхронного последовательного адаптера. Параметр cbInQueue задает размер входной очереди COM-порта в байтах, а параметр cbOutQueue - размер выходной очереди в байтах. В случае успешного выполнения функция возвращает число, определяющее открытый COM-порт. В дальнейшем оно будет использоваться практически всеми телекоммуникационными функциями. Мы будем называть его идентификатором COM-порта. Если по какой-либо причине COM-порт не открыт, функция OpenComm возвращает отрицательное значение - код ошибки. В следующей таблице перечислены возможные значения, возвращаемые функцией в случае возникновения ошибки:
Функцию OpenComm можно использовать для того, чтобы узнать открыт ли данный COM-порт. Если перед вызовом функции OpenComm присвоить параметрам cbInQueue и cbOutQueue нулевые значения, то функция возвращает константу IE_OPEN, в случае, когда порт уже открыт или IE_MEMORY в противном случае. В операционных системах Windows 3.1 и Windows for Worksgroups 3.11 можно использовать COM-порты с номерами от 1 до 9 (COM1-COM9) и параллельные порты от 1 до 3 (LPT1-LPT3). Если вы укажете номер порта, не поддерживаемый драйвером, функция OpenComm вернет код ошибки. Сразу после открытия порта для него устанавливаются режим, принятый по умолчанию (скорость передачи информации, формат данных и т. д.). Чтобы изменить этот режим, необходимо воспользоваться функцией SetCommState. Ниже мы приводим исходный текст функции OpenComPort, который вы можете использовать для открывания COM-порта. Чтобы открыть порт, достаточно передать этой функции номер порта. //========================================================== // Функция OpenCommPort //========================================================== int OpenCommPort(int nNumPort) { // Временный буфер для создания имени порта char szTmpNamePort[10]; wsprintf(szTmpNamePort, "COM%d", nNumPort); // Открываем COM-порт return OpenComm(szTmpNamePort, 8192, 8192); } Функция OpenCommPort самостоятельно формирует строку с текстовым именем открываемого COM-порта. Строка формируется с помощью функции wsprintf во временном буфере szTmpNamePort. Размер входной и выходной очереди COM-порта задается равным 8192 байтам, чего вполне достаточно для простых приложений, не поддерживающих протоколы обмена файлами. Затем определенная нами функция возвращает идентификатор открытого порта или отрицательное число в случае ошибки. 7.2.2. Функция CloseCommCOM-порт является аппаратным ресурсом компьютера. Когда приложение открывает COM-порт, он становится недоступен для других приложений. Поэтому после использования порта, его следует освободить. Для этой цели нужно воспользоваться функцией CloseComm. Функция CloseComm имеет следующий прототип: int CloseComm(int idComDev); Единственный параметр idComDev должен содержать идентификатор порта асинхронного последовательного адаптера, который будет закрыт. Идентификатор порта асинхронного последовательного адаптера возвращает описанная выше функция OpenComm. Функция возвращает ноль в случае успешного завершения или число, меньшее нуля, в случае ошибки. Одной из причин возникновения ошибки может быть неправильное значение параметра idComDev (т. е. если вы пытаетесь закрыть порт, который не был открыт. Функция CloseComm закрывает определенный порт асинхронного последовательного адаптера и освобождает всю оперативную память, выделенную для входной и выходной очереди данных. Перед тем как порт будет закрыт, все символы из выходной очереди будут переданы в COM-порт. Чтобы закрыть порт, не дожидаясь передачи всех оставшихся в выходной очереди символов, можно воспользоваться функцией FlushComm, сбрасывающей содержимое очередей без передачи. FlushComm(nPortID,0); // Сбросить входную очередь FlushComm(nPortID,1); // Закрыть COM-порт имеющий идентификатор nPortID CloseComm(nPortID); 7.2.3. Первая программаТеперь, когда мы рассмотрели первые две функции, предназначенные для управления портами асинхронного последовательного адаптера, приведем исходный текст приложения OPENCOMM, использующего эти функции. Приложение OPENCOMM последовательно открывает и закрывает все порты асинхронного адаптера, начиная с COM1 до COM8. При этом оно определяет номера доступных портов и выводит на экран их список. Главный файл приложения OPENCOMM представлен в листинге 7.1. Листинг 7.1. Файл OPENCOMM.CPP // ============================================================ // Определение доступных COM-портов // ============================================================ #define STRICT #include После запуска приложения OPENCOMM, функция WinMain вызывает функцию FindCOMPorts, определяющую список доступных COM-портов. Функция FindCOMPorts содержит цикл, в котором открывается очередной COM-порт: idComDev = OpenComm(szCommPattern, 1024, 1024); Если OpenComm возвращает значение большее или равное нулю, значит COM-порт успешно открыт. В этом случае мы устанавливаем в байте bFindPort бит с номером, соответствующим номеру открытого порта. После этого мы закрываем только что открытый порт: CloseComm(idComDev); Затем мы переходим к проверке следующего порта. Если функция OpenComm возвращает значение, меньшее нуля, мы считаем, что порт недоступен. Проверив в цикле COM-порты от COM1 до COM8, функция FindCOMPorts возвращает байт bFindPort. Каждый бит этого байта отвечает за свой COM-порт.
Получив байт bFindPort, WinMain формирует в строке szMsg список доступных портов и выводит его на экран с помощью функции MessageBox (см. рис. 7.1).
Рис 7.1. Список доступных COM-портов Файл определения модуля приложения OPENCOMM приведен в листинге 7.2. Листинг 7.2. Файл OPENCOMM.DEF ; ============================================================= ; Файл определения модуля ; ============================================================= NAME OPENCOMM DESCRIPTION 'Приложение OPENCOMM, (C) 1994, Frolov G.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple 7.2.4. Определение доступных портов Драйвер асинхронного последовательного адаптера VCD предоставляет приложениям Windows программный интерфейс через точку входа, адрес которой можно определить, вызвав мультиплексное прерывание INT 2Fh. Реализация этих функций зависит от конкретного драйвера VCD. Если у вас установлен драйвер, не совместимый с драйвером VCD из DDK, то эти функции могут не работать. В листинге 7.3. мы приводим исходный текст приложения FINDPORT, которое определяет порты асинхронного последовательного адаптера, доступные приложениям Windows, посредством вызова функции драйвера VCD. Листинг 7.3. Файл FINDPORT.CPP #define STRICT #include После запуска приложения, WinMain вызывает функцию GetWinFlags для определения режима работы Windows. Если Windows работает в стандартном режиме, приложение выводит соответствующее сообщение и завершает свою работу. Если Windows работает в расширенном режиме, выполняется вызов функции FindCOMPorts, определяющей список доступных COM-портов. Затем полученный список доступных портов отображается на экране. Функция FindCOMPorts возвращает байт, каждый бит которого отвечает за свой COM-порт. Младший бит соответствует порту COM1, а старший COM8. Исходный текст функции FindCOMPorts представлен в листинге 7.4. Листинг 7.4. Файл COMM_DRV.CPP #include В исходном тексте функции FindCOMPorts нет ни вызова функции OpenComm, ни вызовов других телекоммуникационных функций, она содержит только десяток строк на языке ассемблера. Рассмотрим функцию FindCOMPorts подробней. Сначала вызывается функция 1684h мультиплексного прерывания INT 2Fh. Для нее в регистре BX передается константа VCD_Device_ID, означающая, что необходимо получить точку входа для драйвера VCD. mov ax, 1684h mov bx, VCD_Device_ID xor di, di mov es, di int 2Fh Если после вызова мультиплексного прерывания в регистрах ES:DI сохраняются нулевые значения, значит драйвер VCD не поддерживает программный интерфейс и, следовательно, мы не можем получить к нему доступ. В этом случае возвращаем нулевое значение. Если содержимое регистров ES:DI не равно нулю, значит драйвер VCD доступен. Сохраняем адрес точки входа его интерфейса в переменной lpCOMM: mov word ptr lpCOMM, di mov word ptr lpCOMM+2, es Затем вызываем функцию из драйвера VCD, предварительно записав в регистр DX значение 1. mov dx, 1 call dword ptr lpCOMM После возвращения из этой функции по содержимому регистра AL можно определить, какие из портов асинхронного последовательного адаптера доступны для использования. Затем мы завершаем выполнение функции FindCOMPorts и возвращаем значение регистра AL. Файл определения модуля приложения FINDPORT приведен в листинге 7.5. Листинг 7.5. Файл FINDPORT.DEF ; ============================================================= ; Файл определения модуля ; ============================================================= NAME OPENCOMM DESCRIPTION 'Приложение FINDPORT, (C) 1994, Frolov G.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple 7.2.5. Функция SetCommStateИтак, мы научились открывать и закрывать COM-порт. Однако при вызове функции OpenComm мы указали только его имя и размеры входной и выходной очереди. Ни одной характеристики COM-порта - скорости передачи или формата данных мы не указывали. Дело в том, что функция OpenComm устанавливает для открываемого порта скорость обмена, формат данных и другие характеристики, принятые по умолчанию. Чтобы изменить эти характеристики, необходимо воспользоваться функцией SetCommState. Функция SetCommState устанавливает новый режим COM-порта, задаваемый полями структуры DCB, передаваемой ей в качестве одного из параметров. int SetCommState(const DCB FAR* lpdcb); Параметр lpdcb является дальним указателем на структуру DCB, содержащую описание устанавливаемого режима COM-порта. Подробное описание полей этой структуры мы привели чуть ниже. Если функция SetCommState успешно установила новый режим работы COM-порта, она возвращает нулевое значение. В противном случае функция возвращает отрицательное значение. Функция проводит повторную инициализацию всего аппаратного обеспечения COM-порта, но не сбрасывает входную и выходную очереди. Поэтому символы, расположенные во входной и выходной очередях, не будут удалены. В том случае, если желательно удалить эти символы и очистить очереди, следует воспользоваться функцией FlushComm. 7.2.6. Структура DCBПерейдем к подробному описанию структуры DCB (Device Control Block). Структура DCB содержит информацию, определяющую различные характеристики портов последовательного асинхронного адаптера. В структуре DCB определяется скорость передачи данных, количество бит данных и стоповых бит в передаваемых символах, устанавливается контроль четности и режим управления потоком. Структура DCB определена в файле WINDOWS.H следующим образом: typedef struct tagDCB { BYTE Id; UINT BaudRate; BYTE ByteSize; BYTE Parity; BYTE StopBits; UINT RlsTimeout; UINT CtsTimeout; UINT DsrTimeout; UINT fBinary :1; UINT fRtsDisable :1; UINT fParity :1; UINT fOutxCtsFlow :1; UINT fOutxDsrFlow :1; UINT fDummy :2; UINT fDtrDisable :1; UINT fOutX :1; UINT fInX :1; UINT fPeChar :1; UINT fNull :1; UINT fChEvt :1; UINT fDtrflow :1; UINT fRtsflow :1; UINT fDummy2 :1; char XonChar; char XoffChar; UINT XonLim; UINT XoffLim; char PeChar; char EofChar; char EvtChar; UINT TxDelay; } DCB; В файле WINDOWS.H определен также тип LPDCB - дальний указатель на структуру типа DCB: typedef DCB FAR* LPDCB; Опишем назначение отдельных полей структуры DCB. Поле IdСодержит идентификатор COM-порта. Значение поля устанавливается функцией BuildCommDCB, описанной ниже, и содержит то же самое значение, которое возвращает функция OpenComm при открытии этого порта. Если старший бит поля Id установлен, данная структура DCB описывает порт параллельного адаптера. Поле BaudRate Поле BaudRate определяет скорость передачи, с которой работает COM-порт. Скорость можно задать двумя способами. Вы можете записать в это поле либо одну из констант CBR_, определенных в файле WINDOWS.H, либо абсолютное значение скорости передачи информации. Список констант CBR_ и соответствующие им значения скорости перечислены в представленной ниже таблице.
Как видите, старший байт для констант CBR_ содержит значение 0xFF. Поэтому, если вы желаете записать в поле BaudRate абсолютное значение скорости, указывайте числа, меньшие, чем значение константы CBR_110 (0xFF10), и большие 2. Если вы запишите в поле BaudRate неправильное значение, функция SetCommState завершится с ошибкой IE_BAUDRATE. Поле ByteSizeВ поле ByteSize определяется количество бит в символах, передаваемых и принимаемых через COM-порт. Это поле может содержать любое значение от 4 до 8. Если вы запишете в него другое значение, то функция SetCommState завершится с ошибкой IE_BYTESIZE. Поле ParityПоле Parity управляет контролем четности и может содержать одну из пяти констант, перечисленных ниже:
Если записать в это поле другое значение, функция SetCommState вернет код ошибки IE_DEFAULT. Поле StopBitsПоле StopBits задает количество стоповых бит. Поле может содержать одну из констант, перечисленных ниже:
Если записать в это поле другое значение, то функция SetCommState вернет код ошибки IE_DEFAULT. Поле RlsTimeoutПоле RlsTimeout определяет максимальный интервал времени (тайм-аут), в течение которого функция WriteComm будет ожидать появления сигнала RLSD (ранее обозначался нами как DCD) перед тем, как записать данные в очередь передатчика. Интервал времени задается в миллисекундах. Вы можете записать в эти поля константы INFINITE или IGNORE, определенные в файле WINDOWS.H как 0xFFFF и 0x0, соответственно. INFINITE можно перевести как бесконечный, однако это не означает, что функция WriteComm будет бесконечно долго ждать сигнал RLSD. На самом деле эта константа задает интервал времени примерно 65 секунд. IGNORE означает, что WriteComm не ожидает сигнал RLSD. Если интервал ожидания истечет, а сигнал RLSD так и не установится, функция WriteComm вернет нулевое значение. Чтобы узнать причину ошибки, вызовите функцию GetCommError. Если функция WriteComm возвратила управление, после того как истек тайм-аут, GetCommError возвратит значение CE_RLSDTO. Поле CtsTimeoutПоле CtsTimeout определяет максимальный интервал времени (тайм-аут), в течение которого функция WriteComm будет ожидать появления сигнала CTS перед тем, как записать данные в очередь передатчика. Интервал времени задается в миллисекундах. Вы можете записать в эти поля константы INFINITE или IGNORE. Назначение этих констант уже было рассмотрено нами при описании поля RlsTimeout. Если интервал ожидания истечет, а сигнал CTS не установится, функция WriteComm вернет нулевое значение. Чтобы узнать причину ошибки, вызовите функцию GetCommError. Если функция WriteComm возвратила управление, после того как истек тайм-аут, GetCommError возвратит значение CE_CTSTO. Поле DsrTimeoutПоле DsrTimeout определяет максимальный интервал времени (тайм-аут), в течение которого функция WriteComm будет ожидать появления сигнала DSR перед тем, как записать данные в очередь передатчика. Интервал времени задается в миллисекундах. Вы можете записать в эти поля константы INFINITE или IGNORE. Назначение этих констант было уже рассмотрено при описании поля RlsTimeout. Если интервал ожидания истечет, а сигнал DSR не установится, функция WriteComm вернет нулевое значение. Чтобы узнать причину ошибки, вызовите функцию GetCommError. Если функция WriteComm возвратила управление, после того как истек тайм-аут, GetCommError возвратит значение CE_DSRTO. Поле fBinaryПоле fBinary, которое используется как флаг, устанавливает режим передачи двоичной информации. Если это поле содержит значение FALSE (ноль), принятый символ EOF распознается как сигнал для окончания приема данных. Символ EOF определяется полем EofChar структуры DCB. После того как функция ReadComm прочтет символ EOF из входной очереди, она возвращает управление, не прочитав остальные данные. Если во входном буфере остались непрочитанные данные, они распознаются как переполнение. В этом случае функция GetCommError вернет код ошибки CE_RXOVER. При получении символа EOF в структуре COMSTAT устанавливается флаг CSTF_EOF. Если в поле fBinary записано значение TRUE (единица), символ EOF обрабатывается так же, как и остальные символы. Это позволяет беспрепятственно передавать через COM-порт не только текстовые, но также и двоичные данные. Поле fRtsDisableПоле fRtsDisable определяет, будет ли использоваться сигнал RTS. Если это флаг установлен, сигнал RTS не используется и все время остается равен нулю. Если поле fRtsDisable содержит нулевое значение (FALSE), сигнал RTS устанавливается, когда COM-порт открывается и сбрасывается, когда порт закрывается. Чтобы использовать сигнал RTS для управления потоком, необходимо записать в поле fRtsDisable нулевое значение, а в поле fRtsFlow - единицу. Другие комбинации значений fRtsDisable и fRtsFlow блокируют управление потоком по линии RTS. Ниже перечислены различные комбинации полей fRtsDisable и fRtsFlow:
Если сигналы RTS и DTR не используются для управления потоком, их можно устанавливать и сбрасывать с помощью функции EscapeCommFuntion. Поле fParityПоле fParity определяет, будет ли выполняться проверка принимаемой и передаваемой информации на четность. Если в этом поле записано значение TRUE, проверка на четность выполняется. Если функция ReadComm обнаружит ошибку четности, она возвращает отрицательную величину. В этом случае вы должны воспользоваться функцией GetCommError, чтобы определить причину и сбросить флаг ошибки CE_RXPARITY. Чтобы при обнаружении ошибки по четности в функцию окна поступало сообщение EV_ERR, воспользуйтесь функцией SetCommEventMask. Поле fOutxCtsFlowПоле fOutxCtsFlow определяет, что сигнал CTS используется для управления выходным потоком данных. Если это поле содержит значение TRUE и сигнал CTS выключен, передача информации приостанавливается до тех пор, пока сигнал CTS не установится снова. Если поле CtsTimeout содержит величину, отличную от нуля, функция WriteComm будет ожидать появления сигнала CTS вне зависимости от значения поля fOutxCtsFlow. Поле fOutxDsrFlowПоле fOutxDsrFlow определяет, что сигнал DSR используется для управления выходным потоком. Если это поле содержит значение TRUE и сигнал DSR выключен, передача информации приостанавливается до тех пор, пока сигнал CTS снова не установится. Если поле DsrTimeout содержит величину, отличную от нуля, функция WriteComm будет ожидать появления сигнала DSR вне зависимости от значения поля fOutxDsrFlow. Поле fDummyПоле fDummy зарезервировано. Поле fDtrDisableПоле fDtrDisable определяет, будет ли использоваться сигнал DTR. Если это флаг установлен, сигнал DTR не используется и остается равен нулю. Если поле fDtrDisable содержит нулевое значение (FALSE), сигнал DTR устанавливается, когда COM-порт открыт, и сбрасывается, когда порт закрывается. Обычно поле fDtrDisable используют совместно с fDtrFlow. Их использование аналогично полям fRtsDisable и fRtsFlow. Поле fOutXПоле fOutX включает протокол управления потоком XON/XOFF. Если это поле установлено, передача информации приостанавливается, когда принимается символ XoffChar, и возобновляется при получении сигнала XonChar. Если передача данных прерывается в результате приема символа XOFF, в структуре COMSTAT устанавливается флаг CSTF_XOFFHOLD. Поле fInXПоле fInX устанавливает протокол управления потоком XON/XOFF при приеме данных. Если это поле установлено, то символ XoffChar передается, когда в очереди приемника становится больше, чем XoffLim символов и символ XonChar, когда в очереди приемника становится меньше, чем XonLim символов. Если передача данных прерывается в результате передачи символа XOFF, в структуре COMSTAT устанавливается флаг CSTF_XOFFSENT. Поле fPeCharЕсли поле fPeChar содержит значение TRUE, каждый символ, полученный с ошибкой по четности, будет заменен на символ, заданный полем PeChar. Поле fNullЕсли поле fNull содержит значение TRUE, все принятые из COM-порта символы, имеющие нулевое значение, будут пропускаться. Поле fChEvtПри установке поля fChEvt, получение символа EvtChar отмечается как событие EV_RXFLAG. В действительности поле fChEvt не используется. Необходимо вызвать функцию SetCommEventMask, чтобы разрешить это событие. Поле fDtrflowПоле fDtrflow определяет, что сигнал DTR используется для протокола управления потоком (при приеме данных). Если этот флаг установлен, сигнал DTR выключается, когда в приемной очереди становится больше чем XoffLim символов, и включается снова, когда в приемной очереди становится меньше, чем XonLim символов. Такой механизм позволяет предотвратить переполнение входной очереди COM-порта, которое может вызвать потерю принятых данных. Поле fRtsflowПоле fRtsflow определяет, что сигнал RTS используется для управления потоком (при приеме данных). Если это поле установлено, сигнал RTS выключается, когда в приемной очереди становится больше чем XoffLim символов и включается, когда в приемной очереди становится меньше, чем XonLim символов. Поле fDummy2Поле fDummy2 зарезервировано. Поле XonCharПоле XonChar содержит значение символа XON для передачи и для приема. Смотри описание полей fInX и fOutX. Функция BuildCommDCB записывает в это поле значение 0x11 ( Поле XoffCharПоле XoffChar определяет значение символа XOFF для передачи и для приема. Смотри описание полей fInX и fOutX. Функция BuildCommDCB, описанная ниже, записывает в это поле значение 0x13 ( Если вы решили изменить принятые по умолчанию значения полей XonChar и XoffChar, следует обратить внимание на то, что они должны содержать различные величины. В противном случае управление потоком при помощи символов XON/XOFF будет работать неправильно и обмен данными может прекратиться. Поле XonLimПоле XonLim определяет минимально допустимое число символов в приемной очереди, при принижении которого передается символ XON (если включен протокол XON/XOFF) и устанавливается сигнал DTR (если включен протокол DTR). Функция BuildCommDCB записывает в это поле значение 0x10. Поле XoffLimПоле XoffLim определяет максимально допустимое число символов в приемной очереди, при превышении которого передается символ XOFF (если включен протокол) и сбрасывается сигнал DTR (если включен протокол). Чтобы определить максимально допустимое количество символов в очереди, надо вычесть из размера очереди приемника значение поля XoffLim. Функция BuildCommDCB записывает в это поле значение 0x10. Для изменения значения, записанного в поле, можно воспользоваться функцией SetCommState. Поле PeCharПоле PeChar задает значение символа, используемого для замещения символов, принятых с ошибкой по четности. Функция BuildCommDCB записывает в это поле значение 0x0. Смотри поле fPeChar и fParity. Поле EofCharПоле EofChar определяет символ EOF, используемый при передаче сигнала "конец данных". Функция BuildCommDCB записывает в это поле значение 0x0. Смотри поле fBinary. Поле EvtCharПоле EvtChar определяет символ, используемый для передачи сигнала событие (EV_RXFLAG). Когда принимается символ EvtChar, генерируется событие EV_RXFLAG. В действительности флаг fChEvt не используется. Необходимо вызвать функцию SetCommEventMask, чтобы разрешить это событие. Функция BuildCommDCB записывает в это поле значение 0x0. Поле TxDelayПоле TxDelay не используется в Windows 3.1 и Windows for Workgroups 3.11. Как заполнить или модифицировать структуру DCBНа этом мы заканчиваем описание структуры DCB. В заключение приведем несколько советов по модификации полей этой структуры. Структура DCB заполняется двумя функциями - OpenComm и BuildCommDCB. Однако существует много полей в структуре DCB, которые нельзя изменить с помощью этих функций. Для этого надо непосредственно записать новые значения в поля структуры, а затем воспользоваться функцией SetCommState, чтобы внесенные изменения отразились на работе COM-порта. Если вам нужно изменить какое-либо поле этой структуры, следует сначала узнать текущее значение полей структуры DCB, с помощью функции GetCommState. Затем внесите все необходимые изменения в поля структуры и вызовите функцию SetCommState, чтобы соответственно изменился режим COM-порта. Такой алгоритм гарантирует, что остальные поля структуры DCB перед вызовом SetCommState будут содержать правильные значения. В качестве альтернативного способа изменения структуры DCB можно воспользоваться функцией BuildCommDCB. Этой функции передается в качестве параметра строка, имеющая формат команды MODE операционной системы MS-DOS. Данная строка может содержать значения скорости передачи данных, количества бит данных и стоповых бит в слове. Функция BuildCommDCB заполняет структуру DCB, но не изменяет состояния COM-порта. Для этого следует воспользоваться функцией SetCommState. В начале работы с COM-портом приложение должно открыть его с помощью функции OpenComm. Функция OpenComm с помощью функции BuildCommDCB заполняет временный, недоступный программисту, блок DCB значениями по умолчанию. Скорость обмена устанавливается равной 9600 бит/с., проводится проверка на четность, передаваемые символы содержат 7 информационных и один стоповый бит. Затем функция OpenComm выполняет внутренний вызов функции SetCommState с подготовленной структурой DCB, устанавливая начальный режим COM-порта. Затем можно создать собственную структуру DCB и установить новый режим COM-порта, вызвав функцию SetCommState. Функция SetCommState принимает в качестве параметра указатель на заполненную структуру DCB и устанавливает режим работы COM-порта в соответствии с данными из этой структуры. Вы также можете узнать текущее состояние COM-порта с помощью функции GetCommState. 7.2.7. Функция BuildCommDCBФункция BuildCommDCB заполняет структуру DCB в соответствии с переданной ей строкой параметров. Структура DCB содержит управляющую информацию, необходимую для установки режима работы портов последовательного асинхронного адаптера (см. раздел "Структура DCB"). int BuildCommDCB(LPCSTR lpszDef, DCB FAR* lpdcb); Параметр lpszDef является дальним указателем на строку символов, закрытую двоичным нулем, которая должна содержать команды установки режима COM-порта. Формат этой строки полностью соответствует параметрам команды MODE операционной системы MS-DOS. Например, параметр lpszDef может указывать на строку "COM1:9600,n,8,1". В этом случае после вызова функции BuildCommDCB заполненная структура DCB позволит установить порт COM1 в режим обмена данными со скоростью 9600 бит/с, без проверки на четность с форматом передаваемых символов из 8 бит данных и одним стоповым битом. Параметр lpdcb является дальним указателем на структуру типа DCB. В нее будет записана информация, полученная после интерпретации строки lpszDef. Функция BuildCommDCB возвращает ноль, если команды, указанные в параметре lpszDef, успешно интерпретированы и структура DCB заполнена. В случае возникновения ошибки, например, в формате команд, функция возвращает -1. Функция BuildCommDCB только заполняет поля структуры DCB. Чтобы установить режим порта последовательного асинхронного адаптера в соответствии с заполненной структурой DCB, необходимо воспользоваться функцией SetCommState. С помощью функции BuildCommDCB вы можете управлять далеко не всеми характеристиками COM-порта, определяемыми структурой DCB. Так как формат команд строки lpszDef соответствует команде MODE MS-DOS, то с помощью BuildCommDCB можно изменить только скорость передачи информации, режим проверки на четность и формат передаваемых данных (число стоповых бит и бит данных в передаваемых символах). Остальные поля структуры DCB функция BuildCommDCB заполняет по собственному усмотрению (см. описание полей структуры DCB в разделе "Структура DCB"). Так, BuildCommDCB запрещает управление потоком данных между модемом и компьютером на аппаратном уровне (сигналы CTS и RTS) и на программном уровне (с помощью символов XON и XOFF). Кроме того, функция BuildCommDCB позволяет определить только символы длиной 7 или 8 бит. Другие значения вызывают ошибку. Таким образом, функция BuildCommDCB может быть полезна только в самых простых случаях или для начального заполнения структуры DCB. В большинстве случаев вам потребуется самостоятельно заполнять поля этой структуры. 7.2.8. Функция SetCommBreakФункция SetCommBreak прекращает передачу данных и переводит COM-порт в состояние BREAK. COM-порт находится в состоянии BREAK до тех пор, пока приложение не вызовет функцию ClearCommBreak. int SetCommBreak(int idComDev); Параметр idComDev должен содержать идентификатор COM-порта, переводимого в состояние BREAK. В качестве параметра idComDev необходимо использовать величину, возвращаемую функцией OpenComm. При успешном завершении функция возвращает ноль. Если функция возвращает значение меньшее нуля, произошла ошибка. 7.2.9. Функция ClearCommBreakФункция ClearCommBreak восстанавливает обмен данными через порт асинхронного последовательного адаптера, прерванный ранее вызовом функции SetCommBreak. int ClearCommBreak(int idComDev); Единственный параметр функции idComDev должен содержать идентификатор порта асинхронного последовательного адаптера, для которого вызывается функция. Функция возвращает ноль в случае успешного завершения или -1, если параметр idComDev задан неправильно, то есть не соответствует ни одному открытому порту асинхронного последовательного адаптера. 7.2.10. Функция EnableCommNotificationФункция EnableCommNotification разрешает или запрещает передачу сообщения WM_COMMNOTIFY в функцию окна приложения. Более подробную информацию о сообщении WM_COMMNOTIFY можно получить, прочитав раздел "Сообщение WM_COMMNOTIFY". Прототип функции EnableCommNotification представлен ниже. BOOL EnableCommNotification(int idComDev, HWND hwnd, int cbWriteNotify, int cbOutQueue); Параметр idComDev является идентификатором порта, который будет передавать сообщения WM_COMMNOTIFY в функцию окна. Для параметра idComDev необходимо использовать значение, полученное от функции OpenComm. Параметр hwnd определяет окно, функции которого будет передаваться сообщение WM_COMMNOTIFY. Если этот параметр содержит значение NULL, функция EnableCommNotification запрещает передачу сообщения WM_COMMNOTIFY в функцию текущего окна. Параметр cbWriteNotify определяет количество байт, которое драйвер асинхронного последовательного адаптера должен записать во входную очередь перед тем, как будет передано сообщение WM_COMMNOTIFY, у которого в младшем слове параметра lParam записан код извещения CN_RECEIVE . Данное сообщение служит сигналом приложению, что пора прочитать данные из входной очереди COM-порта. Последний параметр cbOutQueue задает минимальное количество символов (байт) в очереди передатчика. Если в очереди передатчика становится меньше байт, чем определено параметром cbOutQueue, драйвер асинхронного последовательного адаптера передает приложению сообщение WM_COMMNOTIFY, у которого в младшем слове параметра lParam записан код извещения CN_TRANSMIT. Это сообщение означает, что выходная очередь практически пустая и можно поместить в нее очередную порцию данных. В случае успешного завершения, функция возвращает значение отличное от нуля. Если функция завершилась с ошибками, она возвращает ноль. Причиной возникновения ошибки может послужить вызов функции EnableCommNotification с неправильным идентификатором COM-порта (COM-порт не открыт) или то, что драйвер асинхронного последовательного адаптера не поддерживает функцию EnableCommNotification. Так, например, драйвер асинхронного последовательного адаптера COMM.DRV операционной системы Windows версии 3.0 не поддерживает эту функцию. Вы можете запретить передачу функции окна сообщения WM_COMMNOTIFY с кодами извещения CN_RECEIVE и CN_TRANSMIT. Если задать для параметра cbWriteNotify значение -1, драйвер не будет передавать функции окна сообщение WM_COMMNOTIFY с кодом извещения CN_RECEIVE. Аналогично, если задать для параметра cbOutQueue значение -1, драйвер не будет передавать функции окна сообщение WM_COMMNOTIFY с кодом извещения CN_TRANSMIT. Может возникнуть резонный вопрос: что произойдет, если разрешить генерацию сообщения WM_COMMNOTIFY с кодом извещения CN_RECEIVE, задать пороговое значение для передачи сообщения 20 байт, а из COM-порта поступит только 15 байт? Будет ли драйвер ожидать прихода остальных 5 байт и как долго? Не прекратится ли обмен данными? Драйвер асинхронного последовательного адаптера периодически, с интервалом около 100 миллисекунд, опрашивает все очереди COM-портов. Если при очередном опросе входной очереди в ней будет находится меньше чем cbWriteNotify байт, функции окна все равно передается сообщение WM_COMMNOTIFY с кодом извещения CN_RECEIVE. Таким образом, если во входной очереди находится меньше байт, чем надо для генерации сообщения, функция окна все равно получит сообщение WM_COMMNOTIFY по истечении 100 миллисекунд. 7.2.11. Функция FlushCommФункция FlushComm удаляет все символы из входной или выходной очереди COM-порта. Прототип функции имеет следующий вид: int FlushComm(int idComDev, int fnQueue); Параметр idComDev является идентификатором COM-порта, очередь которого необходимо очистить (сбросить). Параметр fnQueue определяет очередь, из которой будут удалены все символы. Если значение этого параметра равно нулю, очищается выходная очередь. Если же значение параметра равно единице, удаляются все символы из приемной очереди. Функция возвращает ноль в случае нормального завершения. Если значение, возвращаемое функцией, меньше нуля, параметр idComDev определяет неоткрытый COM-порт или параметр fnQueue задает несуществующую очередь. Возвращаемое функцией число принимает положительное значение, если возникла ошибка COM-порта. Подробный список возможных ошибок приведен в описании функции GetCommError. 7.2.12. Функция GetCommErrorФункция GetCommError позволяет определить текущее состояние COM-порта и причину ошибки при предыдущем вызове коммуникационной функции. Когда случается ошибка при передаче или приеме данных, Windows блокирует COM-порт до тех пор, пока не будет вызвана функция GetCommError. int GetCommError(int idComDev, COMSTAT FAR* lpStat); Параметр idComDev должен содержать идентификатор порта, который будет проверяться. Через параметр lpStat передается дальний указатель на структуру типа COMSTAT. В поля этой структуры будет записано состояние COM-порта. В случае если вы вызовете GetCommError с параметром lpStat, равным NULL, функция вернет только значение ошибки. Структура COMSTAT определена в файле WINDOWS.H следующим образом: #if (defined(STRICT) || (WINVER >= 0x030a)) // Основное определение структуры COMSTAT typedef struct tagCOMSTAT { BYTE status; UINT cbInQue; UINT cbOutQue; } COMSTAT; // Определение вспомогательных констант #define CSTF_CTSHOLD 0x01 #define CSTF_DSRHOLD 0x02 #define CSTF_RLSDHOLD 0x04 #define CSTF_XOFFHOLD 0x08 #define CSTF_XOFFSENT 0x10 #define CSTF_EOF 0x20 #define CSTF_TXIM 0x40 #else /* (STRICT || WINVER >= 0x030a) */ // Альтернативное определение структуры COMSTAT typedef struct tagCOMSTAT { BYTE fCtsHold :1; BYTE fDsrHold :1; BYTE fRlsdHold :1; BYTE fXoffHold :1; BYTE fXoffSent :1; BYTE fEof :1; BYTE fTxim :1; UINT cbInQue; UINT cbOutQue; } COMSTAT; #endif /* !(STRICT || WINVER >= 0x030a */ Из приведенного выше листинга видно, что структура COMSTAT определяется по-разному в зависимости от следующего условия: #if(defined(STRICT) || (WINVER >= 0x030a)) // Основное определение структуры COMSTAT // .... #else /* (STRICT || WINVER >= 0x030a) */ // Альтернативное определение структуры COMSTAT // .... #endif Если вы создаете загрузочный модуль для операционной системы Windows 3.1 или устанавливаете жесткий режим контроля синтаксиса программы, определив константу STRICT, используется основное определение структуры COMSTAT. Опишем поля структуры COMSTAT при использовании основного определения:
Функция GetCommError возвращает значение, являющееся комбинацией флагов. Флаги определяют ошибки, возникшие при последнем вызове коммуникационной функции. Возвращаемое значение может быть комбинацией из следующих флагов:
7.2.13. Функция SetCommEventMaskДля каждого COM-порта Windows поддерживает слово событий. Оно состоит из набора бит, которые устанавливаются при изменении состояния COM-порта (например, при изменении состояния сигнала CTS). Чтобы при изменении состояния COM-порта соответствующим образом менялось слово состояния, необходимо сначала разрешить регистрацию этих изменений. Функция SetCommEventMask разрешает регистрацию изменений состояния COM-порта в слове событий данного COM-порта: UINT FAR* SetCommEventMask(int idComDev, UINT fuEvtMask); Параметр idComDev является идентификатором COM-порта. Это значение возвращает функция OpenComm. Параметр fuEvtMask определяет, какие события будут разрешены. Этот параметр может быть комбинацией из любых констант, перечисленных в следующей таблице:
Функция SetCommEventMask возвращает указатель на слово событий данного COM-порта. Каждый бит этого слова отвечает за определенное событие. Если бит установлен, значит соответствующее событие произошло. В слово событий записываются только события, разрешенные функцией SetCommEventMask. Воспользовавшись функцией EnableCommNotification, можно разрешить передачу в функцию окна приложения сообщения WM_COMMNOTIFY с кодом извещения CN_EVENT. Это сообщение будет передаваться при изменении слова состояния COM-порта. 7.2.14. Функция GetCommEventMaskФункция GetCommEventMask получает и затем очищает слово события данного COM-порта. UINT GetCommEventMask(int idComDev, int fnEvtClear); Параметр idComDev является идентификатором COM-порта. Параметр fnEvtClear определяет события, которые будут сброшены в слове событий. Если функция завершается успешно, она возвращает значение слова событий для COM-порта, определенного параметром idComDev. Каждый бит этого слова отвечает за одно конкретное событие. Если данное событие произошло, соответствующий бит равен 1. Перед тем как функция GetCommEventMask может зарегистрировать возникновение события, приложение должно разрешить данное событие при помощи функции SetCommEventMask. При возникновении таких событий, как изменение состояния линии или ошибка принтера, вы должны вызвать функцию GetCommError. 7.2.15. Функция GetCommStateФункция GetCommState позволяет узнать режим работы порта асинхронного последовательного адаптера. Прототип функции представлен ниже: int GetCommState(int idComDev, DCB FAR* lpdcb); Параметр idComDev должен содержать идентификатор COM-порта. Это значение возвращает функция OpenComm. Параметр lpdcb является дальним указателем на структуру DCB, в которую будет записана информация о режиме работы COM-порта, указанного в параметре idComDev. В случае успешного завершения функция GetCommState возвращает нулевое значение. Если при работе функции возникла ошибка, возвращаемая величина меньше нуля. 7.2.16. Функция ReadCommФункция ReadComm позволяет прочитать данные из входной очереди COM-порта. Прототип функции представлен ниже: int ReadComm(int idComDev, void FAR* lpvBuf, int cbRead); Параметр idComDev является идентификатором COM-порта, из которого будут прочитаны данные. Параметр lpvBuf содержит дальний указатель на буфер, в который будут записаны прочитанные из COM-порта данные. Последний параметр cbRead задает количество символов, которое следует прочитать из входной очереди порта. Следите за тем, чтобы значение cbRead не было больше, чем размер буфера lpvBuf В случае успешного выполнения функции возвращаемое значение определяет количество символов, прочитанное из COM-порта. Если во время выполнения функции произошла ошибка, функция возвращает отрицательное значение. При этом абсолютное значение возвращенной величины определяет количество символов, успешно прочитанных из COM-порта. В случае возникновения ошибки ее причину можно выяснить с помощью функции GetCommError. Так как ошибка может произойти и в том случае, когда ни один символ из входной очереди не прочитан, то, если ReadComm возвращает нулевую величину, следует вызвать функцию GetCommError, чтобы удостоверится в отсутствии ошибок. Если величина, возвращаемая функцией меньше чем значение параметра cbRead, это означает, что на момент вызова функции во входной очереди COM-порта находилось символов меньше, чем определено параметром cbRead. Если величина, возвращаемая функцией равна значению параметра cbRead, то возможно, что в очереди приемника еще находятся данные. Поэтому можно сразу повторно вызвать функцию ReadComm. В качестве примера использования функции ReadComm мы приводим исходный текст функции ReadCommChar, считывающей из входной очереди один символ: //========================================================== // Функция ReadCommChar //========================================================== int ReadCommChar(int nPortID) { int iResult = 0; int iErr = 0; // Считываем из COM-порта один символ iErr = ReadComm(nPortID, (LPSTR)&iResult, 1); // Если символ не прочитан, обрабатываем ошибку if(iErr != 1) { iResult = -1; // Сбрасываем флаги ошибок GetCommError(nPortID, NULL); } return iResult; } В качестве параметра для функции ReadCommChar необходимо передать идентификатор COM-порта. Функция ReadCommChar вызывает ReadComm и пытается считать из входной очереди COM-порта один символ. Если ReadComm возвращает единицу, символ успешно прочитан и функция ReadCommChar возвращает код полученного символа. В противном случае для того, чтобы сбросить флаги ошибок, вызывается функция GetCommError. Затем функция ReadCommChar возвращает -1. 7.2.17. Функция TransmitCommCharФункция TransmitCommChar помещает символ, заданный параметром chTransmit, в начало выходной очереди COM-порта, определенного параметром idComDev. Прототип функции представлен ниже. int TransmitCommChar(int idComDev, char chTransmit); Функция возвращает нулевое значение в случае успешного завершения или отрицательное значение, если символ не записан в выходную очередь. Функцию нельзя вызывать повторно, если COM-порт не производит передачу данных. После того как функция TransmitCommChar записала символ в начало выходной очереди, необходимо, чтобы перед повторным вызовом функции этот символ был передан. Если предыдущий символ не был передан, перед повторным вызовом функции TransmitCommChar, функция завершается с ошибкой. В следующем примере функция TransmitCommChar используется для передачи символов, набранных на клавиатуре в COM-порт. case WM_CHAR: ch = (char)wParam; // Помещаем код нажатой клавиши в начало выходной очереди TransmitCommChar(idComDev, ch); // Добавляем символ перевода строки LF // для каждого символа возврата каретки if (ch == 0x0d) TransmitCommChar(idComDev, 0x0a); return TRUE; 7.2.18. Функция UngetCommCharФункция позволяет поместить символ в начало входной очереди. При выполнении последующей операции чтения функция ReadComm прочитает этот символ. Прототип функции UngetCommChar представлен ниже: int UngetCommChar(int idComDev, char chUnget); Первый параметр функции idComDev должен содержать идентификатор COM-порта. Параметр chUnget должен содержать символ, помещаемый во входную очередь COM-порта. Функция возвращает нулевое значение в случае успешного завершения или отрицательное значение при возникновении ошибки. Последовательные вызовы функции UngetCommChar не допускаются. Перед тем как вы сможете вызвать функцию UngetCommChar еще раз, символ, помещенный в очередь приемника, должен быть прочитан. 7.2.19. Функция WriteCommФункция WriteComm записывает символы в выходную очередь COM-порта. Прототип функции представлен ниже: int WriteComm( int idComDev, const void FAR* lpvBuf, int cbWrite); Параметр idComDev содержит идентификатор COM-порта, используемого для передачи данных. Параметр lpvBuf является дальним указателем на буфер, содержащий передаваемые данные. Последний параметр функции cbWrite определяет количество символов в буфере lpvBuf, предназначенных для передачи. Обратите внимание на то, что значение параметра cbWrite не должно быть больше, чем размер буфера lpvBuf В случае успешного выполнения функции возвращаемое ей значение определяет количество символов, записанное в выходную очередь COM-порта. Если во время выполнения функции произошла ошибка, функция возвращает отрицательное значение. Абсолютное значение возвращаемой величины определяет количество символов, успешно записанных в выходную очередь. В случае возникновения ошибки ее причину можно выяснить с помощью функции GetCommError. Если в очереди передатчика недостаточно места, функция WriteComm может удалить данные из этой очереди. Поэтому перед вызовом функции WriteComm приложение должно проверить наличие достаточного свободного пространства в очереди передатчика. Для этого следует воспользоваться функцией GetCommError. При открытии COM-порта функцией OpenComm следует указать размер выходной очереди не меньше, чем максимальный предполагаемый размер передаваемых строк. В качестве примера использования функции WriteComm мы приводим исходный текст функции WriteCommChar, записывающей в выходную очередь один символ: //========================================================== // Функция WriteCommChar //========================================================== BOOL WriteCommChar(int nPortID, int nChar) { int iErr = 0; iErr = WriteComm(nPortID, (LPSTR)&nChar, 1)) if(iErr != 1) { GetCommError(nPortID, NULL); return FALSE; } return TRUE; } В качестве первого параметра функции WriteCommChar передается идентификатор COM-порта, в который необходимо передать символ. Второй параметр должен содержать код передаваемого символа. Функция WriteCommChar вызывает функцию WriteComm и пытается записать в выходную очередь COM-порта один символ. Если WriteComm возвращает единицу, значит символ успешно записан и функция WriteCommChar возвращает значение TRUE. В противном случае для того чтобы сбросить флаги ошибок вызывается функция GetCommError и WriteCommChar возвращает FALSE. 7.2.20. Сообщение WM_COMMNOTIFYСообщение WM_COMMNOTIFY отправляется драйвером COM-порта в функцию окна и служит извещением об изменении состояния COM-порта. Такое сообщение может поступать в функцию окна при наполнении входной очереди до определенного порогового значения, изменении состояния сигналов DTR, RI, RTS и т. д. Параметр wParam сообщения WM_COMMNOTIFY определяет идентификатор COM-порта, вызвавшего отправку сообщения. Через младшее слово параметра lParam функция окна получает число, по которому можно судить о причине посылки сообщения WM_COMMNOTIFY. Это число может быть комбинацией следующих кодов извещения:
Если приложение обработало поступившее ему сообщение WM_COMMNOTIFY, оно должно возвратить нулевое значение. Сообщение WM_COMMNOTIFY с кодом извещения CN_EVENT передается только тогда, когда слово состояния событий изменяет свое значение. Приложение, которое принимает сообщение WM_COMMNOTIFY, должно каждый раз очищать слово состояния событий, для того чтобы гарантировать получение сообщений в дальнейшем. Использование функции EnableCommNotification для разрешения передачи сообщений WM_COMMNOTIFY с кодами извещения CN_RECEIVE и CN_TRANSMIT может вызвать генерацию ошибочных сообщений WM_COMMNOTIFY, для которых параметр NotifyStatus равен нулю. При обмене данными на высоких скоростях это может привести к аварийной ситуации и даже перезагрузке компьютера. Для решения этой проблемы можно использовать следующие методы: Событие CN_RECEIVE формируется в тех случаях, когда количество байт во входной очереди превышает пороговое значение cbWriteNotify, установленное функцией EnableCommNotification, или когда истекло время (тайм-аут). После формирования события CN_RECEIVE в случае превышения пороговой величины cbWriteNotify другие сообщения CN_RECEIVE не будут генерироваться до тех пор, пока количество байт во входной очереди не станет меньше значения cbWriteNotify и не превысит ее снова. Сообщение CN_TRANSMIT создается аналогично CN_RECEIVE. Порог устанавливается параметром cbOutQueue функции EnableCommNotify. Когда количество символов в выходной очереди становится меньше, чем cbOutQueue, формируется сообщение CN_TRANSMIT. Другие сообщения CN_TRANSMIT не будут посылаться до тех пор, пока в буфере не станет больше, чем cbOutQueue символов. Однако, если прерывания поступают достаточно быстро, дополнительные сообщения CN_RECEIVE (или CN_TRANSMIT) могут посылаться до того, как количество символов в выходной очереди станет больше, чем cbWriteNotify. Эти сообщения можно пропускать (игнорировать), однако они могут послужить причиной перезагрузки системы. Ниже приведен фрагмент обработчика сообщения WM_COMMNOTIFY: //========================================================== // Функция окна WndProc //========================================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch( message ) { case WM_COMMNOTIFY: { if(CN_EVENT & LOWORD( lParam ) == CN_EVENT) { GetCommEventMask(COMDEV( npTTYInfo ), EV_RXCHAR); return(TRUE); } else if(CN_RECEIVE & LOWORD( lParam ) == CN_RECEIVE) { return(TRUE); } else if(CN_TRANSMIT & LOWORD( lParam ) == CN_TRANSMIT) { return(TRUE); } else if(LOWORD( lParam ) == 0) return(TRUE); } default: return(DefWindowProc(hwnd, message, wParam, lParam)); } } При загрузке драйвера последовательного асинхронного адаптера он вызывает функцию CreateSystemTimer и создает таймер, посылающий сообщения драйверу каждые 100 миллисекунд. Обрабатывая сообщения таймера, драйвер просматривает состояние всех открытых COM-портов и проводит проверку тайм-аута. Период таймера изменить нельзя. 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 и нажмите клавишу Чтобы приложение EASYTTY могло автоматически отвечать на вызов по телефонной линии, передайте модему команду ATS0=1 и нажмите клавишу
Рис. 7.2. Приложение EASYTTY Вы можете передать модему команду набора номера удаленного абонента. Чтобы набрать номер 777-77-77, достаточно ввести команду ATDP 777-77-77 и нажать клавишу После установления связи с удаленным модемом вы можете обменяться с ним текстовыми сообщениями. Набирайте передаваемый текст на клавиатуре и просматривайте ответ от удаленного компьютера в главном окне приложения. Когда вы закончите сеанс связи с удаленным компьютером, переведите модем в командный режим и передайте ему команду ATH0. Модем повесит трубку и отключится от телефонной линии. Чтобы перевести модем из режима передачи данных в командный режим, подождите 2-3 секунды, наберите на клавиатуре три знака '+' и дождитесь от модема ответа OK. Главный файл приложения EASYTTY приведен в листинге 7.7. Листинг 7.7. Файл EASYTTY.CPP #include Особенностью приложения 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( 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-порта. Если вы нажмете клавишу Теперь рассмотрим более подробно функции 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(); Проверяем, нажата ли клавиша 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 7.4. Приложение PHONEТелекоммуникационное приложение EASYTTY демонстрирует основные приемы использования функций Windows, предназначенных для работы с портами асинхронного последовательного адаптера. Приложение PHONE создано специально для работы с модемами и демонстрирует приемы передачи модему AT-команд и автоматического реагирования на ответ модема. Главная диалоговая панель приложения представляет собой панель телефонного аппарата и показана на рисунке 7.3.
Рис. 7.3. Приложение PHONE Вы можете набрать на клавиатуре, расположенной в левой части диалоговой панели "Телефон", любой телефонный номер. Введенный номер отображается в специальном поле справа вверху. Если цифра номера введена неправильно, ее можно стереть, нажав на кнопку "<-". Чтобы модем приступил к набору номера, нажмите кнопку "Набор". Если после набора номера произошло соединение с удаленным модемом, вы можете разорвать связь и повесить трубку, нажав кнопку "Сброс". По окончании работы с приложением, его можно завершить, нажав на кнопку "Выход". Во время инициализации модема, набора номера и попытки установления связи с удаленным модемом завершение приложения не разрешается и кнопка "Выход" блокируется. Перед тем как запустить приложение PHONE на вашем компьютере, следует создать в каталоге Windows файл PHONE.INI (см. листинг 7.9). Листинг 7.9. Файл PHONE.INI [Port] Mode=COM2:19200,N,8,1 DsrDtrFlow=1 CtsRtsFlow=1 RtsDisable=0 DtrDisable=0 [Modem] Init=ATQ0V1X4&C1&D2M1 Dial=ATDP HangUp=ATH0 ModemTimeout=5000 DialTimeout=60000 HangUpTimeout=2000 Файл PHONE.INI должен содержать раздел [Port]. В этом разделе расположены строки, определяющие используемый модемом COM-порт, скорость обмена, формат передаваемых данных, режим управления потоком. Строка Mode определяет используемый COM-порт, скорость обмена и формат передаваемых данных. Формат строки Mode совпадает с форматом команды MODE операционной системы MS-DOS. Строки DsrDtrFlow, CtsRtsFlow, RtsDisable и DtrDisable используется для указания режима управления потоком. Числа, указанное вами в этих строках, будут записаны в соответствующие поля структуры DCB. Далее должен находиться раздел [Modem]. В нем расположены строки, содержащие AT-команды инициализации модема и определены интервалы времени, отведенные на выполнение различных операций.
Временные интервалы, определяемые строками ModemTimeout, DialTimeout и HangUpTimeout, должны быть указаны в миллисекундах. Исходный текст главного файла приложения PHONE приведен в листинге 7.10. Листинг 7.10. Файл PHONE.CPP #define STRICT #include Функция WinMain, приложения PHONE, содержит всего несколько строк. В ней нет привычного вызова функций регистрации класса окна и создания окна. Вместо этого WinMain сразу выводит на экран модальную диалоговую панель PHONE, определенную в файле ресурсов приложения PHONE.RC. Для создания модальной диалоговой панели мы воспользовались функцией DialogBox. Перед вызовом функции DialogBox вызывается функция MakeProcInstance. Она создает переходник для функции диалога DlgProc. Более подробную информацию о создании диалоговых панелей можно получить в 12 томе "Библиотеки системного программиста". #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { static DLGPROC lpfnDlgProc; hInst = hInstance; // Переходник для функции диалоговой панели lpfnDlgProc = (DLGPROC)MakeProcInstance((FARPROC)DlgProc, hInst); // Создаем модальную диалоговую панель DialogBox( hInstance, "PHONE", NULL, lpfnDlgProc ); return 0; } Функция диалоговой панели DlgProc обрабатывает сообщения WM_INITDIALOG, WM_CONNECT, WM_COMMAND. Обработчик сообщения WM_INITDIALOG посылает функции диалоговой панели DlgProc сообщение WM_CONNECT и возвращает значение TRUE: PostMessage( hdlg,WM_CONNECT,0,0L ); return TRUE; Сообщение WM_CONNECT определено во включаемом файле PHONE.H следующим образом: #define WM_CONNECT WM_USER Обработчик сообщения WM_CONNECT блокирует кнопку "Сброс" на время инициализации модема, вызывая функцию EnableWindow. После блокировки кнопки "Сброс" вызывается функция InitLine. // Блокируем кнопку "Сброс" EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE); // Открываем COM-порт и инициализируем модем iErr = InitLine(); // Разблокируем кнопку "Сброс" EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE); Функция InitLine, определенная в приложении, открывает COM-порт, устанавливает режим его работы. После этого InitLine передает модему команду инициализации и ждет ответ в течении некоторого времени. Если модем не возвратил ответ OK, функция InitLine возвращает отрицательную величину. В этом случае приложение сначала вызывает функцию ShowError, которая отображает сообщение об ошибке, а затем функцию EndDialog, которая закрывает диалоговую панель. // Если возникла ошибка отображаем сообщение // и закрываем диалоговую панель if( iErr < 0 ) { ShowError( iErr ); EndDialog( hdlg, 0 ); } Когда пользователь нажимает на любую кнопку диалоговой панели PHONE, вызывается обработчик сообщения WM_COMMAND. Параметр wParam сообщения WM_COMMAND содержит идентификатор нажатой кнопки. В зависимости от его значения обработчик WM_COMMAND выполняет различные действия. Если нажата одна из цифровых кнопок "1", "2", "3" ... "0", соответствующая цифра добавляется в конец буфера sBufNum. В этом буфере подготавливается строка с телефонным номером. Чтобы стереть последнюю цифру номера, можно нажать кнопку "<-". Эта кнопка имеет идентификатор ID_BACK. Обработчик сообщений WM_COMMAND, имеющий параметр wParam, равный ID_BACK, стирает последнюю цифру из буфер sBufNum. Если все символы из буфера sBufNum удалены, вызывается функция MessageBeep, подающая звуковой сигнал. Набор телефонного номера, записанного в буфере sBufNum, происходит после нажатия на кнопку "Набор". В этом случае в функцию окна приходит сообщение WM_COMMAND с параметром wParam, равным ID_DIAL. Обработчик этого сообщения блокирует кнопку "Выход" и вызывает функцию DialPhone, определенную в приложении, которая и производит набор номера. Если во время набора номера возникла ошибка, функция DialPhone возвращает отрицательное значение. В этом случае вызывается функция ShowError, которая отображает на экране диалоговую панель с кратким описанием возникшей ошибки. Перед окончанием обработки сообщения разблокируется кнопка "Выход". case ID_DIAL: { EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE); // Набираем номер iErr = DialPhone(); // Если возникла ошибка, отображаем сообщение if( iErr < 0 ) ShowError( iErr ); EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE); return TRUE; } После того как модем набрал телефонный номер и соединился с удаленным компьютером, вы можете разорвать это соединение, нажав кнопку "Сброс". При этом вызывается соответствующий обработчик: case ID_CLEAR: { EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE); // Вешаем трубку HangUpPhone(); EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE); return TRUE; } Он блокирует кнопку "Выход", а затем вызывает функцию HangUpPhone. Функция HangUpPhone вешает трубку и разрывает связь с удаленным модемом. Коды ошибок, идентификаторы диалоговой панели PHONE, а также два массива строк szOkString и szAnswer определены в файле PHONE.H (см. листинг 7.11). Листинг 7.11. Файл PHONE.H // Прототипы функций BOOL CALLBACK _export DlgProc(HWND, UINT, WPARAM, LPARAM); int InitLine(void); int DialPhone(void); int CloseLine(void); int HangUpPhone(void); void ShowError(int iErr); void Idle(void); int WaitModemAnswer(int idComDev, LPSTR *szWaitDest, LPSTR szModemAnswer, int nLength, DWORD dwTimeOut); // Определение констант #define WM_CONNECT WM_USER #define ERR_NO_OPEN (-1) #define ERR_DCB_BUILD (-2) #define ERR_DCB_SET (-3) #define ERR_WRITE (-4) #define ERR_TIMEOUT (-5) #define ERR_READ (-6) #define ID_0 100 #define ID_1 101 #define ID_2 102 #define ID_3 103 #define ID_4 104 #define ID_5 105 #define ID_6 106 #define ID_7 107 #define ID_8 108 #define ID_9 109 #define ID_REPEAT 111 #define ID_CLEAR 130 #define ID_BACK 123 #define ID_NUMBER 120 #define ID_DIAL 122 // Определение массивов строк szOkString и szAnswer char *szOkString[] = { "OK", NULL }; char *szAnswer[] = { "OK", "CONNECT", "RING", "NO CARRIER", "ERROR", "NO DIAL TONE", "BUSY", "NO ANSWER", NULL }; В листинге 7.12 представлен исходный текст файла PHONE.RC, содержащего описание ресурсов приложения. В нем описаны диалоговая панель PHONE, пиктограмма PHONE и таблица строк. Листинг 7.12. Файл PHONE.RC #include "phone.h" PHONE DIALOG 59, 29, 131, 79 STYLE WS_POPUP | WS_VISIBLE | WS_BORDER CLASS "BorDlg" CAPTION "Телефон" BEGIN PUSHBUTTON "1", ID_1, 8, 9, 14, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "2", ID_2, 24, 9, 14, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "3", ID_3, 40, 9, 14, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "4", ID_4, 8, 25, 14, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "5", ID_5, 24, 25, 14, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "6", ID_6, 40, 25, 14, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "7", ID_7, 8, 41, 14, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "8", ID_8, 24, 41, 14, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "9", ID_9, 40, 41, 14, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "0", ID_0, 24, 57, 30, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "#", ID_REPEAT, 8, 57, 14, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "Выход", IDCANCEL, 78, 57, 32, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "Сброс", ID_CLEAR, 66, 41, 28, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP PUSHBUTTON "Набор", ID_DIAL, 78, 25, 32, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP CONTROL "", ID_NUMBER, "EDIT", ES_LEFT | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER, 66, 10, 58, 12 PUSHBUTTON "<--", ID_BACK, 96, 41, 28, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP CONTROL "", 110, "BorShade", 1 | WS_CHILD | WS_VISIBLE, 4, 4, 54, 72 CONTROL "", 112, "BorShade", 1 | WS_CHILD | WS_VISIBLE, 62, 4, 66, 72 END PHONE ICON "phone.ico" STRINGTABLE BEGIN ERR_NO_OPEN, "COM-порт не открыт" ERR_DCB_BUILD, "Ошибка DCB" ERR_DCB_SET, "Ошибка при установке режимма COM-порта" ERR_WRITE, "Ошибка при записи в COM-порта" ERR_TIMEOUT, "Модем не отвечает" ERR_READ, "Ошибка чтения из COM-порта" END В листинге 7.13 приведено изображение пиктограммы, расположенной в файле PHONE.ICO, на который ссылается оператор ICON в файле PHONE.RC. Листинг 7.13. Файл PHONE.ICO
Файл определения модуля для приложения PHONE приведен в листинге 7.14. Листинг 7.14. Файл PHONE.DEF ; ========================================================== ; Файл определения модуля ; ========================================================== NAME PHONE DESCRIPTION 'Приложение PHONE, (C) 1994, Frolov G.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 16384 HEAPSIZE 16384 CODE preload moveable discardable DATA preload moveable multiple 7.5. Приложение TELETYPEПриложение TELETYPE демонстрирует использование телекоммуникационных функций Windows и сообщения WM_COMMNOTIFY для работы с портами асинхронного последовательного адаптера и модемами. Приложение TELETYPE выполняет все основные функции, которые должна поддерживать любая телекоммуникационная программа. TELETYPE позволяет передавать модему AT-команды, принимать от него ответ и отображать его на экране. Перед тем как запускать приложение TELETYPE на вашем компьютере, следует создать в каталоге Windows файл TELETYPE.INI (см. листинг 7.15). Листинг 7.15. Файл TELETYPE.INI [Port] Mode=COM2:9600,N,8,1 Файл TELETYPE.INI должен содержать раздел [Port], состоящий из единственной строки Mode. В этой строке определяется номер COM-порта, к которому подключен модем, скорость обмена и формат передаваемых данных. Формат строки Mode совпадает с форматом команды MODE операционной системы MS-DOS. Запустите TELETYPE. Набирая на клавиатуре AT-команды модема, можно перевести его в любой режим. Можно сбросить текущую конфигурацию. Для этого введите команду ATZ и нажмите клавишу Чтобы приложение TELETYPE могло автоматически отвечать на вызов по телефонной линии, передайте модему команду ATS0=1 и нажмите клавишу
Рис. 7.4. Приложение TELETYPE Вы можете передать модему команду набора номера удаленного абонента. Чтобы набрать номер 987-65-43 достаточно ввести команду ATDP 987-65-43 и нажать клавишу После установления связи с удаленным модемом вы можете обменяться с ним текстовыми сообщениями. Набирайте передаваемый текст на клавиатуре и просматривайте ответ от удаленного компьютера в главном окне приложения. Когда вы закончите сеанс связи с удаленным компьютером, переведите модем в командный режим и передайте ему команду ATH0. Модем повесит трубку и отключится от телефонной линии. Чтобы перевести модем из режима передачи данных в командный режим, подождите 2-3 секунды, наберите на клавиатуре три знака '+' и дождитесь от модема ответа OK. Внешний вид главного окна приложения TELETYPE представлен на рисунке 7.4. Главный файл приложения TELETYPE приведен в листинге 7.16. После запуска TELETYPE управление получает главная функция приложения WinMain. Если параметр hPrevInstance функции WinMain равен нулю, WinMain вызывает функцию инициализации приложения InitApp. В противном случае на экран выводится сообщение о невозможности запуска второй копии приложения. Функция InitApp регистрирует класс окна WMODEM и возвращает управление функции WinMain. На базе зарегистрированного класса окна создается и отображается главное окно приложения, после чего запускается обычный цикл обработки сообщений. При создании окна в функцию окна передается сообщение WM_CREATE. Получив это сообщение, функция окна вызывает функцию InitTTY, которая определяет различные параметры окна и сохраняет их в глобальных переменных. Затем вызывается функция PostMessage, которая отправляет сообщение WM_CONNECT функции главного окна приложения. Сообщение WM_CONNECT определено во включаемом файле TELETYPE.H следующим образом: #define WM_CONNECT WM_USER Константа WM_USER специально предназначена для определения приложениями своих собственных кодов сообщений. Обработчик сообщения WM_CONNECT вызывает функцию InitCommPort. Эта функция открывает и инициализирует COM-порт в соответствии с данными из файла TELETYPE.INI, разрешает генерацию драйвером COM-порта сообщений WM_COMMNOTIFY и устанавливает сигнала DTR. После обработки сообщения WM_CONNECT можно набирать на клавиатуре текст. Для получения ввода с клавиатуры наше приложение обрабатывает сообщение WM_CHAR, вызывая функцию UserChat, которая передает код введенного символа драйверу COM-порта и отображает его на экране. Меню приложения TELETYPE содержит две строки: "Информация" и "Выход". При выборе строки "Информация" на экране отображается короткая информация о приложении. Когда вы закончите работать с TELETYPE, завершите приложение, выбрав из меню строку "Выход". В этом случае вызывается функция PostMessage, передающая функции окна сообщение WM_CLOSE. Обработчик сообщения WM_CLOSE вызывает функцию CloseCommPort, которая закрывает COM-порт и завершает приложение. Листинг 7.16. Файл TELETYPE.CPP // Определяем константу MAIN_MODULE. Она используется // в файле TELE.H #define MAIN_MODULE #include В файле CONNECT.CPP (см. листинг 7.17) содержится определение функции UserChat. Эта функция предназначен для обработки сообщения WM_CHAR, поступающего функции окна в ответ на ввод с клавиатуры. Функция UserChat проверяет, нажал ли пользователь клавишу Строка, подготовленная в массиве szTemp, передается в COM-порт и далее модему. WriteComm( idOpenCommPort,(LPSTR)szTemp, lstrlen((LPCSTR)szTemp)); После вызова функции WriteComm приложение вызывает функцию GetCommError, которая сбрасывает код ошибки, если таковая случилась при передаче. В нашем приложении полученный код ошибки не обрабатывается. Переданная модему строка не отображается автоматически в окне приложения. Для этого строка, записанная в szTemp, передается функции WriteTTY, определенной в файле TTY.CPP. WriteTTY( szTemp, hwnd, DARK_BLUE ); Первый параметр функции WriteTTY должен содержать строку, которую надо отобразить на экране, второй параметр - идентификатор окна и третий - цвет отображаемой строки. Мы указали в качестве третьего параметра константу DARK_BLUE, соответствующую синему цвету. Константы DARK_BLUE и DARK_GREEN, используемые в нашем приложении определены в файле TELETYPE.H. Листинг 7.17. Файл CONNECT.CPP #include В файле COMMPORT.CPP (см. листинг 7.18) определены основные функции, непосредственно взаимодействующие с COM-портом - InitComPort, CloseComPort и ReadCommPort. Функция InitComPort выполняет все действия по инициализации COM-порта. Рассмотрим ее более подробно. Сначала InitComPort определяет режим работы COM-порта, для этого она считывает строку Mode из раздела Port файла TELETYPE.INI. Затем функция OpenComm открывает соответствующий COM-порт. Потом функция BuildCommDCB заполняет структуру DCB, которая передается функции SetCommState. Функция SetCommState устанавливает новый режим работы порта. Сразу после открытия COM-порта в его буферах могут остаться данные. Чтобы их удалить, мы использовали функцию FlushComm. В приложении TELETYPE мы обрабатываем сообщения WM_COMMNOTIFY, вырабатываемые драйвером COM-порта. Для того чтобы разрешить генерацию этих сообщений, предназначена функция EnableCommNotification. if(!EnableCommNotification(idCommPort,hwnd,32,-1)) return -1; После вызова EnableCommNotification функции окна будут поступать сообщения WM_COMMNOTIFY с кодом извещения CN_RECEIVE, если во входную очередь COM-порта поступило больше 32 символов или истек тайм-аут. Более подробную информацию о функции EnableCommNotification можно получить в разделах "Функция EnableCommNotification" и "Сообщение WM_COMMNOTIFY". Затем вызывается функция EscapeCommFunction, которая устанавливает сигнал DTR, сообщая модему, что компьютер готов к обмену данными. На этом функция InitCommPort заканчивает свою работу и возвращает вызывающей процедуре идентификатор открытого COM-порта. Самая простая функция нашего приложения, предназначенная для работы с COM-портами, называется CloseComPort. Она сбрасывает сигнал DTR и закрывает COM-порт. В файле COMMPORT.CPP также определена функция ReadComPort, предназначенная для чтения данных из выходной очереди COM-порта. Функция имеет три параметра. Первый параметр idComDev определяет идентификатор COM-порта, из которого будут прочитаны данные. Второй параметр szDest должен содержать адрес буфера, в который будет записана поступающая информация, а последний параметр nLength указывает размер этого буфера. Функция ReadCommPort содержит внутри себя цикл, в котором происходит чтение данных из выходной очереди COM-порта. Мы выполняем чтение из входной очереди в цикле, так как при больших скоростях передачи информации (больше 9600 бит/с) за время чтения данных из очереди, в нее могут поступить новые данные. В цикле сначала вызывается функция GetCommError. Она заполняет структуру ComStat типа COMSTAT. Нас интересует только поле cbInQue этой структуры. В нем записано, сколько байт находится во входной очереди COM-порта. Если в очереди есть данные, считываем их, вызывая функцию ReadComm. В противном случае выходим из цикла чтения и возвращаем вызывающей процедуре количество прочитанных байт. Листинг 7.18. Файл COMMPORT.CPP #include Предусмотрены четыре функции, предназначенные для работы с главным окном приложения TELETYPE. Их имена - InitTTY, WriteTTY, SetFocusTTY, KillFocusTTY. Эти функции определены в файле TTY.CPP (см. листинг 7.20). Так как указанные функции не содержат ничего, непосредственно относящегося к COM-портам, мы опишем их кратко. Функция InitTTY определяет размеры окна приложения в символах. Главное окно приложения не может изменять свой размер. Поэтому функцию InitTTY достаточно вызвать в начале работы приложения. Прототип функции представлен ниже: void InitTTY(HWND hwnd); Функция InitTTY имеет только один параметр hwnd, который должен содержать идентификатор главного окна приложения. Это значение возвращается функцией CreateWindow. Функция WriteTTY предназначена для отображения данных в главном окне приложения. Прототип функции: void WriteTTY(LPSTR lpOutString, HWND hwnd, COLORREF rgbColor); Первый параметр lpOutString должен содержать указатель на строку символов, закрытую двоичным нулем. Эта строка будет выведена на экран. Второй параметр hwnd должен содержать идентификатор главного окна приложения. Последний параметр функции WriteTTY - rgbColor. Он определяет цвет символов, которые будут отображаться на экране. В приложении TELETYPE символы, полученные от модема, отображаются зеленым цветом, а символы набираемые пользователем на клавиатуре и передаваемые модему - синим. Функции SetFocusTTY и KillFocusTTY управляют текстовым курсором, отображаемым в окне приложения. Функция SetFocusTTY отображает курсор, а функция KillFocusTTY - убирает его из окна приложения. Прототипы функций SetFocusTTY и KillFocusTTY аналогичны: void SetFocusTTY(HWND hwnd); void KillFocusTTY(HWND hwnd); Эти функции имеют единственный параметр hwnd, который должен содержать идентификатор главного окна приложения. Листинг 7.20. Файл TTY.CPP #include Включаемый файл TELE.H (см. листинг 7.21) содержит определения констант, идентификаторов и глобальных переменных, а также объявления функций, используемых в приложении. Листинг 7.21. Файл TELE.H #define DARK_BLUE RGB(0,0,127) // синий цвет #define DARK_GREEN RGB(0,127,0) // зеленый цвет // Идентификаторы меню приложения #define CM_EXIT 101 #define CM_ABOUT 102 // Сообщение WM_CONNECT #define WM_CONNECT WM_USER // Размеры входной и выходной очереди #define INQUEUE 4096 #define OUTQUEUE 4096 // ASCII-коды #define ASCII_BELL 0x07 #define ASCII_BACK 0x08 #define ASCII_LF 0x0A #define ASCII_CR 0x0D // Определяем глобальные переменные только в главном модуле #ifdef MAIN_MODULE HINSTANCE hInst; int idOpenCommPort; #else // Объявляем глобальные переменные, // определенные в главном модуле extern int idOpenCommPort; extern HINSTANCE hInst; #endif // Функции, определенные в файле TELETYPE.CPP void About(HWND hwnd); BOOL InitApp(HINSTANCE hInstance); LRESULT CALLBACK _export WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); // Функция, определенная в файле COMMSG.CPP LRESULT UserChat(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); // Функция, определенная в файле COMMSG.CPP int ProcessCommNotify(HWND hWnd, int nComID, int nNotification); // Функции, определенные в файле COMMPORT.CPP int InitCommPort( HWND hwnd); int CloseCommPort( int nComID ); int ReadCommPort( int nComID , LPSTR Data , int nMaxLength ); // Функции, определенные в файле TTY.CPP void InitTTY(HWND hWnd); void WriteTTY(LPSTR lpOutString, HWND hwnd, COLORREF rgbColor); void SetFocusTTY(HWND hwnd); void KillFocusTTY(HWND hwnd); В листинге 7.23 приведено изображение пиктограммы, расположенной в файле TELETYPE.ICO, на который ссылается оператор ICON в файле описания ресурсов TELETYPE.RC. Листинг 7.23. Файл TELETYPE.ICO
Файл определения модуля для приложения TELETYPE приведен в листинге 7.24. Листинг 7.24. Файл TELETYPE.DEF ; ========================================================== ; Файл определения модуля ; ========================================================== NAME TELETYPE DESCRIPTION 'Приложение TELETYPE, (C) 1994, Frolov G.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 16384 HEAPSIZE 16384 CODE preload moveable discardable DATA preload moveable multiple |