Опубликован: 10.03.2009 | Доступ: свободный | Студентов: 2297 / 280 | Оценка: 4.31 / 4.07 | Длительность: 09:23:00
Лекция 5:

Разработка полноценных Windows-приложений

3. Шифрование и дешифрование текста

Создадим ресурс диалога для шифрования с идентификатором IDD_CIPHER_DIALOG. Добавим два текстовых поля (Edit Control), два флажка-переключателя (Check Box), кнопку (Button), индикатор сосотояния (Progress Bar) и групповое поле (Group Box). Шаблон получившегося диалога приведен на рисунке: рис. 5.5

Шаблон диалога

Рис. 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 \to Encipher… и Cipher \to Decipher… и обработаем их. Для создания объектов класса CCipherDialog добавим в начало файла ChildView.h строку

#include "CipherDialog.h"

Обработчик пункта меню Cipher \to 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 \to 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);  //Обновляем данные в элементах управления 
  в соответствии со связанными с ними переменными
  }
}