Статья
Версия для печати
Обсудить на форуме (6)
Менеджер com-порта


В этой статье описан класс CComPortManager, который позволяет выполнять операции записи/чтения с com-портом (по стандартному протоколуRS-232).

Конечно же это не первая реализация менеджера, существуют менеджеры и более крутые, реализованные в виде компонентов, и с более широким набором функций. Вы наверняка найдёте их с помощью любого поисковика. Не стану останавливаться на их обзоре. Моя скромная задача - показать основы работы с com-портом на примере открытого кода представленного класса CComPortManager. Кроме того, возможностей этого класса достаточно для большинства задач по обмену данными между ПК и внешними устройствами, использующими протокол RS-232. Но - вам решать, что использовать.

(В примерах использована среда VC++6.0.)



Ниже в статье будет следующее

1) описано, как встроить класс в новый проект test (несколько муторный процесс :) но в самом конце статьи ждёт сурпрыз - способ копирования класса),
2) описано, как работают процедуры класса
3) также будет приведён пример использования класса.
4) небольшой мануал по работе с коммуникационным портом.




файлы приложения,

которые будут упомянуты ниже в статье, можно взять здесь.



Итак, встраиваем класс в новый проект.


Создаём "пустой" проект test (dialog-based)

  • Нажимаем Ctrl+N. Во вкладке Projects выбираем MFC AppWizard (exe). В окошке Location указываем путь, где будет создан проект. В окошке Project name пишем имя проекта - test. Нажимаем OK.
  • Выбираем тип проекта - Dialog based. Давим кнопку Finish, затем OK.
  • Сохраняем проект (кнопка SaveAll на тулбаре)

Теперь добавим вручную наш класс CComPortManager в проект. Делаем заготовку для нашего класса:
  • Добавляем к ресурсам новый диалог. В окне Workspace во вкладке ResourceView добираемся до папки ресурсов "Dialog". Правой кнопкой мыши (RMB) щёлкаем по папке - и выбираем InsertDialog. Новый диалог сразу откроется в редакторе ресурсов. Щелкаем по этому диалогу левой кнопкой (LMB) , тем самым выделив его, затем Alt+Enter - откроется окно свойств. Выберите вкладку General. В окошке ID напишите идентификатор IDD_dlgCPM_SETDIALOG.
  • Добавляем к проекту класс с именем CComPortManager. В окне Workspace во вкладке ClassView щёлкаем RMB по корню дерева списка классов. Выбираем NewClass. В списке Class type выбираем MFC Class, задаём имя класса  CComPortManager, выбираем базовый класс - CDialog, выбираем ID диалога - IDD_dlgCPM_SETDIALOG. Нажимаем OK.
  • Сохраняем проект.

Закрываем среду VC++6.0. Теперь открываем папку проекта и находим там файлы, которые надо будет подправить -
CComPortManager.h
CComPortManager.cpp
test.rc
Resource.h

(Далее, везде, где написано "открываем файл ..." имеется "щёлкаем по файлу для его открытия". Это замечание я сделал из-за того, что файлы также можно открывать и редактировать в MS Блокноте, а по умолчанию они откроются в Студии. Исключение составляет: файл *.rc - его редактировать удобнее именно в блокноте. Файл *.dsw - это файл проекта, его надо просто "запускать".)

Заголовочный файл и файл реализации.


Открываем файл CComPortManager.h. В этом файле находим описание класса:

Код:
/////////////////////////////////////////////////////////////////////////////
// CComPortManager dialog
class CComPortManager : public CDialog
{
...
...
};
   
и заменяем на описание нашего класса CComPortManager
(текст описания можно найти в файле приложения CComPortManager_h_class.txt)

Код:
/////////////////////////////////////////////////////////////////////////////
// CComPortManager dialog

class CComPortManager : public CDialog
{
public:
CComPortManager(CWnd* pParent = NULL);//конструктор
~CComPortManager();//деструктор

//Структура свойств порта. Эта структура хранит все основные
//настройки, используемые для открытия порта.
struct PORTPROPERTY
{
//закладка для элементов диалога//

//флаг показа окна настроек при открытии порта
bool bShowSetupOnOpenPort;
CString Name;//имя порта "COM1","COM2"...

DWORD CommInQueueSize;//размер очереди приема, байты
DWORD CommOutQueueSize;//размер очереди передачи, байты

DWORD BaudRate;//скорость передачи данных
BYTE ByteSize;//кол-во бит/байт (4-8)
BYTE Parity;//режим контроля чётности
BYTE StopBits;//кол-во стоповых бит
//Межбайтный таймаут при чтении, мс
DWORD ReadIntervalTimeout;
//Множитель для таймаута при чтении байта, мс
DWORD ReadTotalTimeoutMultiplier;
//Дополнительный таймаут при чтении, мс
DWORD ReadTotalTimeoutConstant;
//Множитель для таймаута при записи, мс
DWORD WriteTotalTimeoutMultiplier;
//Дополнительный таймаут при записи, мс
DWORD WriteTotalTimeoutConstant;

//конструктор
PORTPROPERTY();
//оператор присваивания
operator=(const PORTPROPERTY& Prop);
};

//структура для передачи данных потоку,
//который отслеживает "звонок" (RING)
struct PointersForThread
{
HANDLE* m_portH;//указатель на описатель файла порта
bool* bIsRing;//указатель на флаг звонка
//конструктор
PointersForThread(){m_portH=NULL;bIsRing=false;}
};

private:
HANDLE m_portH;//описатель файла порта
PORTPROPERTY m_undo_PortProp;//для хранения старых настроек
PORTPROPERTY m_curr_PortProp;//для хранения текущих настроек
PointersForThread m_PFT;
//копирование данных из контролов в переменные свойств
void Copy_controls_to_property();
//копирование свойств в контролы
void Copy_property_to_controls();
void PrepareUndo();//подготовить откат
void MakeUndo();//выполнить откат

///////////// интерфейс класса //////////////
public:
int DoModal();//показать окно настроек COM порта
bool PortIsOpen();//запрос состояния порта
bool OpenPort();//открыть порт
bool ClosePort();//закрыть порт
DWORD WritePort(BYTE *bufer,DWORD N);//запись в порт
DWORD ReadPort(BYTE *bufer,DWORD N);//чтение из порта
//получить свойства порта
void GetCurrPortPropery(PORTPROPERTY *CPMpp);
//установить свойства порта
void SetCurrPortPropery(PORTPROPERTY *CPMpp);
//запуск потока для отслеживания звонка RING
void RunRingPolling(bool* bIsRing);
//очистить порт
bool ClearPort();
///////////////////////
private:
// Dialog Data
//{{AFX_DATA(CComPortManager)
enum { IDD = IDD_dlgCPM_SETDIALOG };
int m_nBaudRate;
int m_nByteSize;
int m_nParity;
int m_nStopBits;
BOOL m_BOOLShowSetupOnOpenPort;
int m_nPortName;
UINT m_edReadIntervalTimeout;
UINT m_edReadTotalTimeoutMultiplier;
UINT m_edReadTotalTimeoutConstant;
UINT m_edWriteTotalTimeoutMultiplier;
UINT m_edWriteTotalTimeoutConstant;
DWORD m_edCommInQueueSize;
DWORD m_edCommOutQueueSize;
//}}AFX_DATA

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CComPortManager)
protected:
virtual void DoDataExchange(CDataExchange* pDX);   // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CComPortManager)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

Сохраняем файл CComPortManager.h. Теперь открываем файл реализации CComPortManager.cpp. В этом файле нужно заменить весь текст после строк :



Код:
/////////////////////////////////////////////////////////////////////////////
// CComPortManager dialog
...
...

   
на текст реализации нашего класса CComPortManager.
(Здесь текст реализации не приводится, поскольку он довольно большой. Его можно найти в файле приложения CComPortManager_cpp_class.txt.
Ниже будут приводится фрагменты кода из файла.)

Сохраните полученный файл реализации CComPortManager.cpp.


Теперь нужно подправить
ресурс диалога для класса


В блокноте открываем файл проекта test.rc

1) В этом файле находим описание свойств и контролов (элементов управления) диалога. Найдите строки 
   
Код:
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
   
и немного далее этих строк - текст
   
Код:
IDD_dlgCPM_SETDIALOG DIALOG DISCARDABLE  0, 0, 186, 95
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Sans Serif"
BEGIN
   DEFPUSHBUTTON   "OK",IDOK,129,7,50,14
   PUSHBUTTON "Cancel",IDCANCEL,129,24,50,14
END

   
замените этот текст на из файла приложения CComPortManager_SRC_DIALOG.txt.

Файл test.rc пока не закрывайте.

2) Добавьте в test.rc ещё описание данных комбо-боксов - прямо сразу после только что добавленного текста
(файл приложения CComPortManager_SRC_COMBOBOX.txt.

3) Теперь в файле test.rc найдите текст

Код:
/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO DISCARDABLE
BEGIN
   IDD_dlgCPM_SETDIALOG, DIALOG
   BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 20
TOPMARGIN, 7
BOTTOMMARGIN, 70
   END
END
#endif   // APSTUDIO_INVOKED


Здесь задаются размеры габаритной синей штрихпунктирной рамки, которая при редактировании ресурса ограничивает максимальные положения контролов. Сейчас эта рамка имеет размеры той заготовки диалога, которую мы сделали. Эту рамку проще подправить в запущенном редакторе - если попытаться курсором изменить размеры этой рамки, то она автоматом подгонит свои размеры как надо. Но можно и выставить размеры вручную, они в нашем случае такие:
Код:
   LEFTMARGIN, 7
   RIGHTMARGIN, 228
   TOPMARGIN, 7
   BOTTOMMARGIN, 72

Сохраните файл test.rc.



Теперь надо добавить и пронумеровать
идентификаторы элементов управления


Откройте файл проекта Resource.h. Там вы увидите такой текст (только значения идентификаторов могут быть другими)
   
Код:
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by test.rc
//
#define IDM_ABOUTBOX 0x0010
#define IDD_ABOUTBOX 100
#define IDS_ABOUTBOX 101
#define IDD_TEST_DIALOG   102
#define IDR_MAINFRAME    128
#define IDD_dlgCPM_SETDIALOG   129

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 130
#define _APS_NEXT_COMMAND_VALUE 32771
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif



   
добавьте после строкИ ,
Код:
#define IDD_dlgCPM_SETDIALOG				129


содержащей идентификатор нашего диалога, идентификаторы элементов управления диалога:
(файл приложения CComPortManager_RES_CONTROLS.txt.


Код:
#define IDC_cbxBAUDRATE                 1004
#define IDC_cbxBYTESIZE                 1005
#define IDC_cbxPARITY                   1006
#define IDC_cbxSTOPBITS                 1007
#define IDC_cbxPORTNAME                 1008
#define IDC_chkSHOWSETUPONOPENPORT      1009
#define IDC_edREADINTERVALTIMEOUT       1010
#define IDC_edREADTOTALTIMEOUTMULTIPLIER 1011
#define IDC_edREADTOTALTIMEOUTCONSTANT  1012
#define IDC_edWRITETOTALTIMEOUTMULTIPLIER 1013
#define IDC_edWRITETOTALTIMEOUTCONSTANT 1014
#define IDC_edCOMMINQUEUESIZE           1015
#define IDC_edCOMMOUTQUEUESIZE          1016

Замечание: значения идентификаторов контролов не должны повторяться в пределах одного и того же ресурса диалога. При добавлении контролов через редактор ресурсов, за этим следит Визард.
Раз мы добавляем вручную, то должны убедится, что значения идентификаторов различны. (В нашем примере это так и есть :) )

Сохраните файл Resource.h.

Теперь запускаем файл test.dsw - откроется наш проект. Не забываем подправляем пресловутую синюю рамку на диалоге (по желанию, конечно).





Описание интерфейса класса CComPortManager


Интерфейс включает в себя следующие процедуры


Код:
1) //показать окно настроек COM порта
int DoModal();
2) //запрос состояния порта
bool PortIsOpen();
3) //открыть порт
bool OpenPort();
4) //закрыть порт
bool ClosePort();
5) //запись в порт
DWORD WritePort(BYTE *bufer,DWORD N);
6) //чтение из порта
DWORD ReadPort(BYTE *bufer,DWORD N);
7) //получить свойства порта
void GetCurrPortPropery(PORTPROPERTY *CPMpp);
8) //установить свойства порта
void SetCurrPortPropery(PORTPROPERTY *CPMpp);
9) //очистить порт
bool ClearPort();
10) //запуск потока для отслеживания звонка RING
void RunRingPolling(bool* bIsRing);

Теперь подробнее о каждой


  • Показать окно настроек COM порта
    int DoModal();
    Выводит на экран диалог, в котором отображаются текущие настройки порта (вернее те настройки, с которыми порт будет открыт, когда в очередной раз будет вызвана OpenPort() ). Здесь можно изменить настройки порта, выбрав нужные значения. Текущие настройки хранятся в переменной curr_PortProp. Перед выводом диалога на экран настройки сохраняются в переменной undo_PortProp, затем "копируются" в контролы. Диалог выводится и показывает текущие настройки.
    Если изменение настроек подтверждается кнопкой OK, то данные из контролов "копируются" в curr_PortProp. При отмене - curr_PortProp восстанавливается из undo_PortProp и настройки не поменяются.
       
  • Запрос состояния порта
    bool PortIsOpen();
    Производит проверку хендла порта m_portH на действительность. Если хендл имеет значение INVALID_HANDLE_VALUE, то есть хендл недействителен, и порт считается закрытым (возврат false). Иначе - возвращается true, порт открыт.
       
  • Процедура открытия порта
    bool OpenPort();
    Каждый экземпляр класса CComPortManager может в любой момент открыть только один порт.
    Хендл порта инкапсулирован в объекте класса CComPortManager и недоступен прямо через интерфейс. Он там создаётся , там же и умирает. При открытии порта используются настройки из переменной curr_PortProp, которую можно настроить через диалог. Если порт нормально открылся, возвращается true.
    Если установлен флаг curr_PortProp.bShowSetupOnOpenPort, то перед открытием порта автоматически будет выведено на экран окно настроек порта.
    Подробнее об открытии порта и описание управляющих структур вы найдёте ниже по течению, "Работа с коммуникационным портом COM в программах для Win32".
       
  • Процедура закрытия порта
    bool ClosePort();
    Здесь, если хендл действителен, производится очистка очередей ввода-вывода порта и хендл закрывается. Затем хендлу в любом случае присваивается значение INVALID_HANDLE_VALUE, то есть делаем его недействительным. Если всё прошло нормально, возвращается true.
       
  • Запись в COM порт и
       
  • чтение из COM порта по протоколу RS-232
    DWORD WritePort(BYTE *bufer,DWORD N);//запись в порт
    DWORD ReadPort(BYTE *bufer,DWORD N);//чтение из порта
    В процедуры передаются: указатель на буфер типа BYTE и количество байт, которое надо передать/принять. Длина буфера должна быть больше или равна N, иначе может произойти нехорошая ошибка :) . Процедуры возвращают количество действительно переданных/принятых байт. В случае любых ошибок процедуры возвращают 0.
       
  • void GetCurrPortPropery(PORTPROPERTY *CPMpp); //получить свойства порта
       
  • void SetCurrPortPropery(PORTPROPERTY *CPMpp); //установить свойства порта
    - позволяют программно задавать настройки порта. Пример:

    CComPortManager CPM;
    CComPortManager::PORTPROPERTY CPMpp;
       
    CPM.GetCurrPortPropery(&CPMpp); //читаем настройки
    CPMpp.bShowSetupOnOpenPort=true; //изменяем
    CPMpp.Name="COM2";
    CPM.SetCurrPortPropery(&CPMpp); //записываем обратно
       
    CPM.OpenPort(); //открываем порт

       
  • Очистка порта
    bool ClearPort(); //очистить порт
    Производит очистку буферов ввода/вывода порта и прекращает все незавершенные операции ввода-вывода.
       
  • Запуск потока для отслеживания звонка RING
    void RunRingPolling(bool* bIsRing);
    Процедура предназначена для фонового отслеживания "Звонка модема" (сигнал RING). Она запускает поток RingPolling(LPVOID pnt), работающий параллельно с основным процессом. В качестве аргумента LPVOID pnt в поток передаётся передаётся указатель на структуру PointersForThread, содержащую указатели на хендл порта и флаг наличия звонка. Перед запуском потока этот флаг нужно сбросить. Поток начинает опрашивать состояние "звонка" процедурой GetCommModemStatus() и устанавливает флаг при возникновении звонка. При этом поток прекращает работу.
    Естественно, флаг, переменная типа bool, указатель которого передан в поток, должен существовать ДОЛЬШЕ существования потока. Например быть членом класса, экземпляр которого существует всю продолжительность работы программы. Также желательно не закрывать порт во время работы потока. Если порт не открыт то поток не будет запущен. Остановить работающий поток можно присвоив флагу bIsRing значение true.
       



ОБРАТИТЕ внимание на следующий момент: если при работающем потоке закрыть программу, то в памяти останется "мусор". Для избежания такой радости нужно запретить пользователю закрывать программу во время , когда значение переменной *bIsRing равно false. Это можно сделать следующим образом.

1) Для Dialog based приложения.

Нужно заблокировать обработчики сообщений от кнопок IDOK и  IDCANCEL.
Открываем в редакторе ресурсов файл реализации диалога (в данном примере testDlg.cpp). Жмём Ctrl+W  (открывается визард). Во вкладке Message Maps в окне Objects IDs выбираем IDCANCEL. В окне Messages  - двойной щелчок по BN_CLICKED, будет создан обработчик. Затем то же самое сделайте с IDOK. Закройте визард - вы увидите оба обработчика

Код:
void CTestDlg::OnCancel(){CDialog::OnCancel();}
void CTestDlg::OnOK(){CDialog::OnOK();}

Допустим флаг m_bIsRing - это ..хм.. член главного диалога программы.
Нужно добавить в обоих обработчиках перед вызовом обработчика базового класса:
   
Код:
void CTestDlg::OnCancel()
{
if (!m_bIsRing)
{
::AfxMessageBox("текст предупреждения");
return;
}
CDialog::OnCancel();
}
   
А можно так:
   
Код:
void CTestDlg::OnCancel()
{
if (!m_bIsRing){m_bIsRing=true;return;}
CDialog::OnCancel();
}
   
Тогда при повторном нажатии поток скорее всего уже остановится.

2) Для приложений с MainFrame/CView.

Нужно заблокировать обработчик сообщения WM_CLOSE класса CMainFrame (файл MainFrm.cpp). В редакторе ресурсов открываем файл реализации MainFrm.cpp. Жмём Ctrl+W. В окне Objects IDs выбираем CMainFrame. В окне Messages - WM_CLOSE, будет создан обработчик.
void CMainFrame::OnClose(){CFrameWnd::OnClose();}

Тут несколько вариантов расположения флага - в CView, в theApp, в самом CMainFrame. Если в CMainFrame - тут всё понятно.

Если m_bIsRing  находится в theApp (это класс самого Приложения, эта переменная единственная, её класс и она сама определены в файле "название_класса_программы".cpp). Можете найти , где расположено определение theApp через поиск (Find In Files). это определение имеет вид
"название_класса_программы" theApp;
Для доступа к theApp из CMainFrame её надо описать в классе CMainFrame как внешнюю:
   
Код:
extern "название_класса_программы" theApp;
void CMainFrame::OnClose()
{
if(theApp.m_bIsRing)
{::AfxMessageBox("сначала остановите генерацию строки");return;}
CFrameWnd::OnClose();
}

   
Если m_bIsRing находится в CView, то для приложения с одной вьюхой - доступ можно получить так:
((CMyView*)GetActiveView())->m_bIsRing;

Однако следует помнить, что тотального контроля ошибок в классе нет (всё предусмотреть невозможно) и не следует пытаться, например, изменить настройки порта при открытом порте. Сначала закрываем порт, потом уже меняем.



Пример использования класса ComPortManager


Код:
#include "ComPortManager.h"

CComPortManager CPM; //переменная-менеджер
bool flag;//флаг звонка
BYTE bufer[5]={0,5,2,3,4}; //буфер с данными

CPM.DoModal(); //показ настроек

if(CPM.OpenPort()) //открываем порт
{
::AfxMessageBox("порт открыт");
}
else
{
::AfxMessageBox("ошибка при открытии порта");
}

CPM. ClearPort(); //очистка порта

WritePort(bufer, 5); // записываем 5 байт из буфера

//ждём "звонка" (ВНИМАНИЕ - если звонка не будет, то программа зависнет,
//это просто для примера приведено)
flag=false;
RunRingPolling(&flag); //запускаем ожидание звонка

while(!flag);//ждём до талого снега


ReadPort(bufer, 5);// читаем 5 байт из порта

CPM.ClosePort(); //закрываем порт



Теперь о том, как добавить в диалог элементы для настроек других параметров, если в этом возникнет необходимость.

К примеру, на диалоге ещё нет (на самом деле уже есть, и весь добавляемый тут код тоже уже есть :) ) окна для задания величины очереди приёма (CommInQueueSize).
  • Открываем окно настроек в редакторе ресурсов.
  • Помещаем элемент Static и пишем в него "Размер очереди приёма драйвера порта, байт".
  • Помещаем элемент Edit и открываем его свойства. Во вкладке General Присваиваем ему идентификатор IDC_edCOMMINQUEUESIZE. Во вкладке Styles выбираем выравнивание текста (Align text) == Left . Устанавливаем флажок Number, чтобы пользователь мог вводить только цифры.
  • Добавляем переменную-член, связанную с окном ввода. Удерживая Ctrl дважды щёлкаем LMB по окну Edit. Открывается диалог Add Member Variable для ввода имени и типа переменной. Если визард ругается вроде "Parsing error: Unexpected end-of-line. Input line: "DDX_ ......." , то найдите эти строки (в начале файла реализации) и сделайте так, чтобы строка не была разорвана переводом строки, а вся умещалась в одной строке.
    Итак, в диалоге Add Member Variable в окне Member variable type пишем имя m_edCommInQueueSize, в окне Variable type выбираем тип DWORD. Жмём OK.
  • Все ключевые места в тексте заголовочного файла и файла реализации класса помечены строкой
       "//закладка для элементов диалога//"

Выполните команду из главного меню Edit->Find in Files... и найдите все вхождения этих строк. Их должно быть 6. Список найденных строк будет выведен в окне Output внизу экрана. Двойным щелчком по строке можно перейти к ней.

-Первая закладка - в процедуре CComPortManager::Copy_controls_to_property(). В конце процедуры добавляем строку кода:
curr_PortProp.CommInQueueSize=m_edCommInQueueSize;

-Вторая - в процедуре CComPortManager::Copy_property_to_controls(). Добавляем:
m_edCommInQueueSize=curr_PortProp.CommInQueueSize;

-Третья - в процедуре CComPortManager::PORTPROPERTY::PORTPROPERTY(). Добавляем:
CommInQueueSize=1000;//размер очереди приёма

-Четвёртая - CComPortManager::PORTPROPERTY::operator=(const PORTPROPERTY& Prop). Добавляем:
CommInQueueSize=Prop.CommInQueueSize;

-Пятая - bool CComPortManager::OpenPort(). Здесь надо  - по смыслу - скопировать значение из curr_PortProp.CommInQueueSize в нужную переменную (здесь приведены строки кода из файла реализации)-

//установка размеров очередей I/O
SetupComm(m_portH,curr_PortProp.CommInQueueSize,curr_PortProp.CommOutQueueSize);

- Шестая - в заголовке   struct PORTPROPERTY{}. Добавляем описание переменной для хранения свойства:
DWORD CommInQueueSize;//размер очереди приема





РАБОТА С КОММУНИКАЦИОННЫМ ПОРТОМ COM В ПРОГРАММАХ ДЛЯ WIN32


(Здесь из источника (см. в самом конце статьи) приведёно только то, что необходимо для понимания действий с портом в статье выше. Также имеется краткий обзор функций работы с портом, не использованных в статье.

С последовательными COM (впрочем как и с параллельными - LPT) портами в Win32 работают как с файлами. Следовательно, начинать надо с открытия порта как файла. Для этого необходимо воспользоваться функцией CreateFile. Эта функция предоставляется Win32 API. Ее прототип выглядит так:
Код:
HANDLE  CreateFile(
LPCTSTR   lpFileName,
DWORD   dwDesiredAccess,
DWORD   dwShareMode,
LPSECURITY_ATTRIBUTES   lpSecurityAttributes,
DWORD   dwCreationDistribution,
DWORD   dwFlagsAndAttributes,
HANDLE   hTemplateFile);

Функция имеет много параметров, большинство из которых нам не нужны. Краткое описание параметров:

lpFileName   Указатель на строку с именем открываемого или создаваемого файла. Формат этой строки может быть очень хитрым. В частности можно указывать сетевые имена для доступа к файлам на других компьютерах. Можно открывать логические разделы или физические диски и работать в обход файловой системы. Однако для наших задач это не нужно. Последовательные порты имеют имена "COM1", "COM2", "COM3", "COM4" и так далее. Точно так же они назывались в MS-DOS, так что ничего нового тут нет. Параллельные порты называются "LPT1", "LPT2" и так далее.
Учтите, что если у Вас к порту СОМ1 подключена мышь, Windows не даст открыть этот порт. Аналогично не удастся открыть LPT1 если подключен принтер. А вот с модемом дела обстоят немного по другому. Если какая-либо программа использует модем, например вы дозвонились до своего провайдера Internet, то Вашей программе не удастся открыть порт к которому подключен модем. Во всех остальных случаях порт будет открыт и Вы сможете работать с модемом сами, из своей программы.

dwDesiredAccess   Задает тип доступа к файлу. Возможно использование следующих значений:
NULL   Опрос атрибутов устройства без получения доступа к нему.
GENERIC_READ   Файл будет считываться.
GENERIC_WRITE   Файл будет записываться.
GENERIC_READ|GENERIC_WRITE   Файл будет и считываться и записываться.

dwShareMode      Задает параметры совместного доступа к файлу. Коммуникационные порты нельзя делать разделяемыми, поэтому данный параметр должен быть равен 0.
lpSecurityAttributes   Задает атрибуты защиты файла. Поддерживается только в Windows NT. Однако при работе с портами должен в любом случае равняться NULL.
dwCreationDistribution   Управляет режимами автосоздания, автоусечения файла и им подобными. Для коммуникационных портов всегда должно задаваться OPEN_EXISTING.
dwFlagsAndAttributes   Задает атрибуты создаваемого файла. Так же управляет различными режимами обработки. Для наших целей этот параметр должен быть или равным 0, или FILE_FLAG_OVERLAPPED. Нулевое значение используется при синхронной работе с портом, а FILE_FLAG_OVERLAPPED при асинхронной, или другими словами, при фоновой обработке ввода/вывода. Подробнее про асинхронный ввод/вывод я расскажу позже.
hTemplateFile      Задает описатель файла-шаблона. При работе с портами всегда должен быть равен NULL.

При успешном открытии файла, в нашем случае порта, функция возвращает описатель (HANDLE) файла. При ошибке INVALID_HANDLE_VALUE. Код ошибки можно получить вызвав функцию GetLastError, но ее описание выходит за рамки данной статьи. Открытый порт должен быть закрыт перед завершением работы программы. В Win32 закрытие объекта по его описателю выполняет функция CloseHandle:

Код:
BOOL CloseHandle(HANDLE hObject);

Функция имеет единственный параметр - описатель закрываемого объекта. При успешном завершении функция возвращает не нулевое значение, при ошибке NULL. Теперь пример (достаточно очевидный):
Код:
#include <windows.h>
HANDLE port;
port=CreateFile("COM2",GENERIC_READ|GENERIC_WRITE,
0,NULL,OPEN_EXISTING,0,NULL);
if(port==INVALID_HANDLE_VALUE)
{
MsgBox(NULL,"Невозможно открыть последовательный порт",
"Error",MB_OK);ExitProcess(1);
}
CloseHandle(port);

В данном примере открывается порт СОМ2 для чтения и записи, используется синхронный режим обмена. Проверяется успешность открытия порта, при ошибке выводится сообщение и программа завершается. Если порт открыт успешно, то он закрывается. Открыв порт мы получили его в свое распоряжение. Теперь с портом может работать только наша программа. Однако, прежде чем мы займемся вводом/выводом, мы должны настроить порт. Это касается только последовательных портов, для которых мы должны задать скорость обмена, параметры четности, формат данных и прочее.
 Кроме того существует несколько специфичных для Windows параметров. Речь идет о тайм-аутах, которые позволяют контролировать как интервал между принимаемыми байтами, так и общее время приема сообщения. Есть возможность управлять состоянием сигналов управления модемом. Но обо всем по порядку. Основные параметры последовательного порта описываются структурой DCB. Временные параметры структурой COMMTIMEOUTS. Существует еще несколько информационных и управляющих структур, но они используются реже.
 Настройка порта заключается в заполнении управляющих структур и последующем вызове функций настройки. Поскольку основную информацию содержит структура DCB с ее описания и начнем:

Код:
typedef struct _DCB {
DWORD DCBlength;  // sizeof(DCB)
DWORD BaudRate;   // current baud rate
DWORD fBinary:1;   // binary mode, no EOF check
DWORD fParity:1;   // enable parity checking
DWORD fOutxCtsFlow:1;   // CTS output flow control
DWORD fOutxDsrFlow:1;   // DSR output flow control
DWORD fDtrControl:2;   // DTR flow control type
DWORD fDsrSensitivity:1;   // DSR sensitivity
DWORD fTXContinueOnXoff:1;  // XOFF continues TxD
WORD fOutX:1;   // XON/XOFF out flow control
DWORD fInX:1;   // XON/XOFF in flow control
DWORD fErrorChar:1;   // enable error replacement
DWORD fNull:1;   // enable null stripping
DWORD fRtsControl:2;   // RTS flow control
DWORD fAbortOnError:1;   // abort reads/writes on error
DWORD fDummy2:17;   // reserved
WORD  wReserved;   // not currently used
WORD  XonLim;   // transmit XON threshold
WORD  XoffLim;   // transmit XOFF threshold
BYTE  ByteSize;   // number of bits/byte, 4-8
BYTE  Parity;   // 0-4=no,odd,even,mark,space
BYTE  StopBits;   // 0,1,2 = 1, 1.5, 2
char  XonChar;   // Tx and Rx XON character
char  XoffChar;   // Tx and Rx XOFF character
char  ErrorChar;   // error replacement character
char  EofChar;   // end of input character
char  EvtChar;   // received event character
WORD  wReserved1;   // reserved; do not use} DCB;



Вторая страница.


Автор: Алексей1153
Версия для печати
Обсудить на форуме (6)