Программирование на C++ глазами хакера

         

Работа с СОМ-портом


Мне по долгу службы часто приходилось работать с интерфейсом RS-232. Так в официальной документации называется СОМ-порт компьютера. Современное оборудование (контроллеры, устройства сбора информации и т.д.) работают через этот порт. К любому модему, даже внутреннему, обращение происходит именно через СОМ-порт. А сколько существует внешних устройств, подключаемых по этому интерфейсу, сосчитать невозможно.

Работа с портами похожа на работу с файлами. Давайте рассмотрим простейший пример. Для этого создайте новое приложение MFC Application на основе диалога с именем COMport. Внешний вид главного окна будущей программы вы можете увидеть на 5.5.

5.5. Окно будущей программы Comport

В верхней части окна находится выпадающий список Combo Box, в котором можно выбирать имя порта. Рядом со списком две кнопки: для открытия и закрытия порта. Чуть ниже расположены текстовое поле для ввода команды и кнопка для ее отправки.

В центре окна расположились элементы управления List Box: для отображения хода работы с портом и многострочное поле ввода для отображения пришедших данных.

Создайте подобный интерфейс, и можно переходить к программированию. По нажатию кнопки Open port должен выполняться код из листинга 5.5.

Листинг 5.5. Открытие порта
void CCOMportDlg::OnBnClickedOpenportButton() { if (hCom != INVALID_HANDLE_VALUE) { OnBnClickedButton1(); Sleep(300); }

char sPortName[10]; cbPorts.GetWindowText(sPortName, 10);

hCom = CreateFile(sPortName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

if (hCom == INVALID_HANDLE_VALUE) lLogList.AddString("Error opening port"); else { lLogList.AddString("Port successfully opened."); hThread = CreateThread(0, 0, ReadThread, (LPVOID)this, 0, 0);

DCB dcb; GetCommState(hCom, dcb); dcb.BaudRate = CBR_57600; dcb.ByteSize = 8; dcb.Parity = NOPARITY; dcb.StopBits = ONESTOPBIT; if (SetCommState(hCom, dcb)) lLogList.AddString("Configuring OK"); else lLogList.AddString("Configuring Error"); } }




Если попытаться открыть порт дважды, то будет получено сообщение об ошибке, поэтому первым делом нужно произвести эту проверку. И если порт открыт, то закрыть его. Эта проверка выполняется в функции onBnClickedButton1, которую я покажу чуть позже, и она будет вызываться при нажатии на кнопку Close port.

Теперь получим имя выбранного порта и откроем его. Для этого используется функция работы с простыми файлами CreateFile, только вместо имени файла указывается имя порта.

Если порт открыт удачно, то выводится соответствующее сообщение, и можно перейти к конфигурированию параметров соединения. Для этого сначала получите текущие настройки системы с помощью функции GetCommState. Ей нужно передать два параметра: указатель на открытый порт и указатель на структуру типа DCB. Эта структура содержит полную информацию о параметрах соединения и выглядит следующим образом:

typedef struct _DCB { DWORD DCBlength; // размер структуры DCB DWORD BaudRate; // скорость передачи данных в бодах DWORD fBinary: 1; // двоичный режим без проверки конца // строки DWORD fParity: 1; // включить проверку четность DWORD fOutxCtsFlow:1; // CTS-правление потоком выхода DWORD fOutxDsrFlow:1; // DSR-управление потоком выхода DWORD fDtrControl:2; // DTR-тип управления потоком скорости // передачи данных DWORD fDsrSensitivity:1; // DSR-чувствительность DWORD fTXContinueOnXoff:1; // стоп-сигнал продолжает выполнение DWORD fOutX: 1; // старт/стоп-сигнал для управления // выходящим потоком DWORD fInX: 1; // старт/стоп - сигнал для управления // входящим потоком DWORD fErrorChar: 1; // включить проверку погрешностей DWORD fNull: 1; // отвергать пустой поток данных DWORD fRtsControl:2; // RTS - управление потоком данных DWORD fAbortOnError:1; // проверять операции чтения/записи DWORD fDummy2:17; // зарезервировано WORD wReserved; // зарезервировано WORD XonLim; // порог чувствительности старт-сигнала WORD XoffLim; // порог чувствительности стоп-сигнала BYTE ByteSize; // количество бит (обычно 7 или 8) BYTE Parity; // четность байта BYTE StopBits; // стоповые биты char XonChar; // вид старт-сигнала в потоке char XoffChar; // вид стоп-сигнала в потоке char ErrorChar; // вид сигнала погрешности



char EofChar; // сигнал окончания потока char EvtChar; // зарезервировано WORD wReserved1; // зарезервировано } DCB;

Если неправильно указаны параметры, то данные не будут передаваться и приниматься. Самое главное — заполнить следующие поля:

BaudRate — скорость передачи данных (бит/с). Указывается константа в виде CBR_скорость, где скорость должна быть равна скорости, поддерживаемой используемым устройством, например, 56000;

ByteSize — размер передаваемого байта (может быть 7 или 8);

Parity — флаг проверки четности;

StopBits — стоповые биты, могут принимать значения ONESTOPBIT (один), ONE5STOPBITS (полтора) или TWOSTOPBITS (два).

Остальные параметры можно оставить по умолчанию (те, которые вернула система). Но прежде чем указывать какие-либо параметры, обязательно прочитайте документацию на аппаратуру, с которой необходимо соединяться. Я не встречал устройств, которые поддерживали бы все режимы работы. Например, модем ZyXel Omni 56 K может поддерживать скорость от 2400 до 56 000, и можно указывать значения только из этого диапазона.

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

После конфигурирования порта запускается поток, в котором мы будем бесконечно пытаться считать данные из порта. Это, конечно же, не эффективно, потому что удобнее использовать сообщения Windows, но для простого примера в обучающих целях достаточно. Функция чтения потока ReadThread выглядит следующим образом:

DWORD __stdcall ReadThread(LPVOID hwnd) { DWORD iSize; char sReceivedChar; while(true) { ReadFile(hCom,sReceivedChar,1,iSize,0); SendDlgItemMessage((HWND)hwnd,IDC_EDIT2,WM_CHAR, sReceivedChar,0); } }

В этой функции вы можете увидеть бесконечный цикл чтения данных, которое выполняется стандартной функцией чтения из файла — ReadFile.

Теперь посмотрите на функцию закрытия порта, которая будет вызываться по нажатию кнопки Close port:

void CCOMportDlg::OnBnClickedButton1() { if (hCom == INVALID_HANDLE_VALUE) return;



if (MessageBox("Close port?", "Warning", MB_YESNO) == IDYES) { TerminateThread(hThread, 0); CloseHandle(hCom); hCom = INVALID_HANDLE_VALUE; } }

Прежде чем выполнить закрытие, надо проверить переменную hCom. Возможно, что порт уже закрыт или вообще никогда не открывался. Если переменная содержит неправильный указатель, то следует просто выйти из функции.

Если порт открыт, то выводится запрос на подтверждение закрытия порта. Если пользователь подтвердит, то прерывается поток, закрывается указатель порта и переменной hCom присваивается значение INVALID_HANDLE_VALUE.

И последнее, что предстоит добавить в программу, — возможность отправки сообщений. Для этого по нажатию кнопки Send command должен выполняться код из листинга 5.6.

Листинг 5.6. Функция отправки данных в порт
void CCOMportDlg::OnBnClickedSendcommandButton() { if (hCom == INVALID_HANDLE_VALUE) { AfxMessageBox("Open port before send command"); return; }

char sSend[10224]; eSendCommand.GetWindowText(sSend, 1024);

if (strlen(sSend)0) { lLogList.AddString(sSend);

sSend[strlen(sSend)] = '\r'; sSend[strlen(sSend)] = '\0';

TerminateThread(hThread,0); DWORD iSize; WriteFile(hCom, sSend, strlen(sSend), iSize,0); hThread = CreateThread(0, 0, ReadThread, (LPVOID)this, 0, 0); } }

Сначала проверяется, открыт ли порт. Если он уже закрыт или никогда не открывался, то нет смысла писать в него данные. После этого надо получить данные для отправки, и если они больше нуля, то добавить в конец отправляемой строки символ завершения строки (нулевой символ). Мне приходилось работать с разным оборудованием, и большинство типов требует в конце команды отправлять символы конца строки и перевода каретки. Иногда бывает достаточно только символа конца строки.

Теперь прерываем поток чтения данных и записываем в порт данные стандартной функцией работы с файлами WriteFile. После записи можно снова запускать поток чтения.

Если у вас есть модем, то можете запустить программу и открыть порт, на котором настроен модем. Отправьте команду ATDTxxxxx, где хххх — это номер телефона. Модем должен будет начать набор указанного номера телефона.

Примечание
Исходный код примера, описанного в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter5\COMport.

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