Казахстан |
Разработка полноценных Windows-приложений
3. Шифрование и дешифрование текста
Создадим ресурс диалога для шифрования с идентификатором IDD_CIPHER_DIALOG. Добавим два текстовых поля (Edit Control), два флажка-переключателя (Check Box), кнопку (Button), индикатор сосотояния (Progress Bar) и групповое поле (Group Box). Шаблон получившегося диалога приведен на рисунке: рис. 5.5
Первое текстовое поле предназначено для ввода ключа шифрования, свойство Password должно иметь значение true. В групповое поле объединены элементы управления, отвечающие за вывод информации. Кнопка открывает диалог для указания пути к файлу в который будет сохранен шифротекст. Путь к файлу выводится во второе текстовое поле. Защитим его от изменения, установив свойство Read Only в значение true. Первый флажок-переключатель сигнализирует о необходимости записи конечного результата в файл. Второй-о необходимости вывода результата в основное текстовое поле программы. Добавим в приложение класс диалога, созданный на основе данного ресурса. Добавим также необходимые переменные. Класс CCipherDialog:
#pragma once // CCipherDialog dialog class CCipherDialog : public CDialog { DECLARE_DYNAMIC(CCipherDialog) public: CCipherDialog(CWnd* pParent = NULL); // standard constructor virtual ~CCipherDialog(); // Dialog Data enum { IDD = IDD_CIPHER_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support DECLARE_MESSAGE_MAP() public: BOOL m_bCiphType; //Флаг указывает на то,какое действие необходимо выполнить (шифрование/дешифрование) CString m_sFilePathStr; //Переменная, связанная с текстовым полем, в которое выводится путь файла для сохранения CString m_sBufStr; //Переменная буфер BOOL m_bToFile; //Переменная, связанная с первым флажком переключателем BOOL m_bDisplay; //Переменная, связанная со вторым флажком переключателем CString m_sKeyStr; //Переменная, связанная с первым текстовым полем, в которое вводится ключ int table[161]; //Таблица алфавита int m_nTimer; //Счетчик таймера int m_nCount; //Переменная индикатор хода процесса шифрования/дешифрования bool m_bFinProc; //Флаг, сигнализирующий о том, прошел ли процесс до конца public: afx_msg void OnBnClickedButtonBrowse(); afx_msg void OnTimer(UINT_PTR nIDEvent); protected: virtual void OnCancel(); virtual void OnOK(); };
Конструктор класса CCipherDialog:
CCipherDialog::CCipherDialog(CWnd* pParent /*=NULL*/) : CDialog(CCipherDialog::IDD, pParent) //Инициализация переменных , m_bCiphType(FALSE) , m_sFilePathStr(_T("")) , m_sBufStr(_T("")) , m_bToFile(FALSE) , m_bDisplay(FALSE) , m_sKeyStr(_T("")) , m_nTimer(0) , m_nCount(0) , m_bFinProc(true) { for(int i = 0; i < 95; i++) //Создание таблицы для шифрования символов table[i] = i + 32; //Используются ASCII коды только печатных символов for(int i = 95; i < 159; i++) table[i] = i - 159; table[159] = -88; table[160] = -72; }
Функция DoDataExchenge:
void CCipherDialog::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT_FILEPATH, m_sFilePathStr); DDX_Check(pDX, IDC_CHECK_TOFILE, m_bToFile); DDX_Check(pDX, IDC_CHECK_TOCLIENT, m_bDisplay); DDX_Text(pDX, IDC_EDIT_KEY, m_sKeyStr); }
Функция DoDataExchenge используется для создания связей между переменными и элементами управления. При добавлении переменных с помощью мастера, ее код генерируется автоматически. Для связи диалога с основным окном программы создадим пункт меню Cipher и в нем два подпункта Cipher Encipher… и Cipher Decipher… и обработаем их. Для создания объектов класса CCipherDialog добавим в начало файла ChildView.h строку
#include "CipherDialog.h"
Обработчик пункта меню Cipher Encipher…
void CChildView::OnCipherEncipher() { CCipherDialog dlg; //Создание диалога шифрования/дешифрования dlg.m_bCiphType = TRUE; //Инициализация флага индикатора действия (будем производить шифрование) m_Rich.GetWindowTextA(dlg.m_sBufStr); //Копирование текста из текстового поля в строковую переменную - буфер диалога if(dlg.DoModal() == IDOK) //Если нажата кнопка Ok { if(dlg.m_bToFile) //Если установлен флаг сохранения результата шифрования/дешифрования { int nBuf; //количество символов в тексте char *dinBuf; //буфер с текстом if(dlg.m_sFilePathStr != "") //Если верно указан путь файла { CFile OutFile(dlg.m_sFilePathStr,CFile::modeCreate|CFile::modeWrite); //Открываем файл для записиси nBuf = dlg.m_sBufStr.GetLength() + 1; //Записываем количество символов текста в переменную dinBuf = new char [nBuf]; //Инициализируем буфер strcpy_s(dinBuf,nBuf,dlg.m_sBufStr); //Копируем данные из диалога в буфер OutFile.Write(dinBuf,nBuf); //Записываем данные из буфера в файл } else AfxMessageBox("Неправильно указан путь к файлу. Сохранение не выполнено"); //Если неверно указан путь к файлу,то сообщаем об ошибке } if(dlg.m_bDisplay) //Если установлен флаг вывода результатов на экран { m_Rich.Clear(); //Очищаем текстовое поле m_Rich.SetWindowTextA(dlg.m_sBufStr); //Записываем результат шифрования/дешифрования в текстовое поле } } }
В данном пункте важен код, выделенный жирным шрифтом, остальное относится к выводу результатов шифрования/дешифрования на экран или их сохранения в файл. Для получения текста из элемента управления Rich Edit, используется метод GetWindowText(CString), аргументом ему передается строка, в которую будет записан текст. Для записи текста в текстовое поле, используется метод SetWindowText(CString). Обработчик пункта меню Cipher Decipher…
void CChildView::OnCipherDecipher() { CCipherDialog dlg; m_Rich.GetWindowTextA(dlg.m_sBufStr); if(dlg.DoModal() == IDOK) { if(dlg.m_bToFile) { int nBuf; char *dinBuf; if(dlg.m_sFilePathStr != "") { CFile OutFile(dlg.m_sFilePathStr,CFile::modeCreate|CFile::modeWrite); nBuf = dlg.m_sBufStr.GetLength() + 1; dinBuf = new char [nBuf]; strcpy_s(dinBuf,nBuf,dlg.m_sBufStr); OutFile.Write(dinBuf,nBuf); } else AfxMessageBox("Неправильно указан путь к файлу. Сохранение не выполнено"); } if(dlg.m_bDisplay) { m_Rich.Clear(); m_Rich.SetWindowTextA(dlg.m_sBufStr); } } }
Также как и в предыдущем обработчике строки, важные в данном пункте, выделены жирным шрифтом. Единственное отличие данного обработчика от предыдущего состоит в том, что мы не инициализируем переменную dlg.m_bCiphType. Это говорит о том, что будет происходить дешифрование. Теперь реализуем алгоритм шифрования. Переопределим виртуальную функцию OnOk() класса CCipherDialog.
Переопределенный метод OnOk:
void CCipherDialog::OnOK() { MSG message; //Структура сообщение UpdateData(true); //Обновляем данные GetDlgItem(IDOK)->EnableWindow(FALSE); //Отключаем возможность повторного нажатия кнопки Ok пока происходит шифрование int nKeyLength = m_sKeyStr.GetLength(); //Инициализация переменной для хранения длины ключа if(nKeyLength) //Если ключ введен { m_nTimer = (int)SetTimer(1,100,NULL); //Пускаем таймер с интервалом 100 милисекунд CString tempstr; //Вспомогательная строка for(m_nCount = 0; m_nCount < m_sBufStr.GetLength(); m_nCount++) //Пока не конец открытого текста { int buf_ascii = int(m_sBufStr[m_nCount]); //Шифруем или дешифруем по алгоритму if(buf_ascii == 10 || buf_ascii == 13 || buf_ascii == 9) //Табуляции и переносы на новую строку не шифруем { tempstr += m_sBufStr[m_nCount]; continue; } int nTextChar = std::find(table,table+160,int(m_sBufStr[m_nCount])) - table; int nKeyChar = std::find(table,table+160,int(m_sKeyStr[m_nCount % nKeyLength])) - table; if(m_bCiphType) tempstr += char(table[(nTextChar + nKeyChar) % 161]); else tempstr += char(table[(nTextChar + 161 - nKeyChar) % 161]); if(::PeekMessageA(&message,NULL,0,0,PM_REMOVE)) //При этом на каждой иттерации цикла обрабатываем сообщения { ::TranslateMessage(&message); ::DispatchMessageA(&message); } } if(m_bFinProc) //Если процесс полностью завершен { m_sBufStr = tempstr; //Записываем все полученные данные в буфер KillTimer(m_nTimer); //Сбрасывем таймер CDialog::OnOK(); //Закрываем диалог } else //Иначе { m_bFinProc = true; //Сбрасываем флаг завершения процедуры в начальное положение m_nCount = 0; //Сбрасываем счетчик символов CProgressCtrl *pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS_CIPH); //Ининциализируем переменную для управления индикатором хода процесса pBar->SetPos(0); //Устанавливаем индикатор в начальное состояние KillTimer(m_nTimer); //Сбрасываем таймер } } else //Если ключ не введен { GetDlgItem(IDOK)->EnableWindow(TRUE); //Разблокируем кнопку Ok AfxMessageBox("Введите ключ шифрования"); //Просим ввести ключ } }
Обратим внимание на использовании в данном методе функций: PeekMessageA(&message,NULL,0,0,PM_REMOVE), TranslateMessage(&message) и DispatchMessageA(&message). Они позволяют обрабатывать сообщения на каждой иттерации цикла, создавая тем самым впечатление параллельности вычислений и реакции на действия пользователя. Функция SetTimer(1,100,NULL) запускает системный таймер, т.е. с интервалом, указанным во втором параметре данной функции, посылаются сообщения типа WM_TIMER.Первый аргумент функции - целое число, идентифицируещее таймер, третий - указатель на функцию, которая должна обрабатывать сообщение WM_TIMER. Если он равен NULL, то сообщение WM_TIMER посылается в общую очередь сообщений программы. Функция std::find(table,table+160,int(m_sBufStr[m_nCount])) - это алгоритм stl, который используется для поиска элементов в контейнерах (массивах). Добавим в класс CCipherDialog обработчик сообщения WM_TIMER, для того чтобы выводить информацию о ходе шифрования в индикатор состояния. Обработчик сообщения WM_TIMER:
void CCipherDialog::OnTimer(UINT_PTR nIDEvent) { CProgressCtrl *pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS_CIPH); //Инициализируем переменную для управления индикатором процесса if(m_sBufStr.GetLength()) //Если буфер не пуст pBar->SetPos(m_nCount*100/m_sBufStr.GetLength()); //Устанавливаем позицию индикатора процесса в соответствии с текущей позицией шифрования/дешифрования CDialog::OnTimer(nIDEvent); }
Функция GetDlgItem(IDC_PROGRESS_CIPH) возвращает указатель на объект элемента управления диалога, идентификатор которого передается ей в качестве аргумента. Также необходимо переопределить функцию OnCancel() для данного диалога. Она прерывает процесс шифрования/дешифрования, если процесс начался, но не закрывает диалог. И закрывает диалог если процесс не запущен. Переопределенный метод OnCancel():
void CCipherDialog::OnCancel() { if(m_nCount) //Если идет процесс, то { GetDlgItem(IDOK)->EnableWindow(TRUE); //разблокируем кнопку Ok m_nCount = m_sBufStr.GetLength(); //завершаем цикл шифрования, присваивая переменной счетчику конечное значение m_bFinProc = false; //Указываем на то, что шифрование/дешифрование было прервано } else Cdialog::OnCancel(); //Иначе закрываем диалог }
Осталось обработать нажатие на кнопку Browse… Она должна открывать диалог для указания пути к файлу, в который будет выводиться результат шифрования/дешифрования. Обработчик кнопки Browse…
void CcipherDialog::OnBnClickedButtonBrowse() { Cstring strFilter; //Строка с поддерживаемыми форматами Cstring strFileName; //Строка с путем и именем файла Cstring strExtension; //Строка с расширением файла UpdateData(true);//Обмен данными между элементами управления и переменными strFilter = "Text file|*.txt||"; //Инициализация строки поддерживаемыми форматами CfileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,strFilter); //Создание диалога if (dlg.DoModal() == IDOK) //Если нажата кнопка Ok { strFileName = dlg.m_ofn.lpstrFile; //Определяем путь и имя файла в который будем сохранять if (dlg.m_ofn.nFileExtension == 0) //Если пользователь не ввел расширение, то { switch (dlg.m_ofn.nFilterIndex) //В зависимости от того, какой из поддерживаемых форматов выбран { case 1: strExtension = "txt"; break; //Инициализируем строку с расширением default: break; } strFileName = strFileName + '.' + strExtension; //Окончательно определяем строку с именем путем и расширением } m_sFilePathStr = strFileName; //Записывае готовую строку с полным именем в пременную член, связанную с текстовым полем UpdateData(false); //Обновляем данные в элементах управления в соответствии со связанными с ними переменными } }