- •Методические указания к лабораторным работам
- •Лабораторная работа №1 простые программы с циклами и операторами консольного ввода/вывода
- •Задание
- •Описание примера
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №2 работа с текстовыми файлами, структурами данных и меню
- •Задание
- •Структурное программирование и функциональная декомпозиция системы
- •Функции
- •Организация меню в консольном приложении
- •Структуры данных
- •Операции с файлами
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №3 разработка и спецификация функций и модулей программы
- •Задание
- •Модульная структура программ
- •Параметры командной строки
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №4 разработка и спецификация структур данных, использование указателей и динамических массивов структур
- •Задание
- •Указатели
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №5 использование объектно-ориентированного программирования в разработке приложений
- •Задание
- •Конструкторы и деструкторы
- •Конструктор по умолчанию
- •Конструктор копирования
- •Массивы объектов
- •Friend-конструкции
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №6 использование наследования, полиморфизма и абстрактных классов
- •Задание
- •Наследование данных и методов
- •Полиморфизм и виртуальные функции
- •Абстрактный класс
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №7
- •Сложные структуры из объектов классов
- •Цель работы - изучение организации различных структур данных и разработка методов манипулирования данными.
- •Задание
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №8 разработка windows-интерфейса приложения
- •Задание
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №9 разработка и использование com-сервера
- •Задание
- •Шаблоны классов
- •Использование библиотеки atl для создания серверов сом
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Литература
Шаблоны классов
Шаблоны предназначены для создания функций и классов, отличающихся друг от друга только типом обрабатываемых ими данных. Такие функции и классы часто называют соответственно параметризованными функциями (generic functions) и параметризованными классами (generic classes). Для их определения в язык C++ введено ключевое слово template.
Общий синтаксис объявления шаблона класса имеет следующий вид:
template<class tType> // Дальше пойдет шаблон
class className
{
// Компоненты класса
};
Здесь префикс template<class tType> говорит о том, что описывается параметризованный класс, имеющий в качестве параметра тип шаблона tType. При необходимости можно определить несколько параметризованных типов данных, разделяя их запятой. В пределах определения класса имя tType, можно использовать в любом месте.
После того как параметризованный класс объявлен, можно создавать его конкретную реализацию:
className<dataType> clObj;
Тип данных, который заменяет собой переменную tType и которыми фактически оперирует класс, задается параметром dataType и может быть любым типом, даже классом, например:
className<float *> clQbj;
В результате такого определения на этапе компиляции на основании заданного типа будет автоматически сгенерирован соответствующий объект. Таким образом, использование шаблонов не влечет никаких дополнительных временных издержек по сравнению с явно задаваемыми типами.
Методы параметризованного класса являются параметризованными функциями, и, когда они определяются вне базового класса, параметр типа шаблона должен быть указан явно, например:
template<class tType> void className<tType>::memberFunc()
{
// Тело функции
}
Замечательно здесь то, что за формирование версии каждой параметризованной функции для каждого типа аргумента отвечает компилятор, а не программист.
Рассмотрим пример шаблона классов Vector - «Вектор линейного пространства».
template <class T> class Vector
{
Т *data; // Указатель начала массива компонентов
int size; // Размер массива
public:
Vector(int);
~Vector() {delete[] data;}
T& operator [](int i) { return data[i]; }
};
// Внешняя реализация тела конструктора
template<class T> Vector<T>::Vector (int n)
{data = new T[n];}
void main()
{
Vector<int> x(5); // Генерируется вектор целых
int i;
for (i=0; i<5; ++i) x[i]=i; // Инициализация
for (i=0; i<5; ++i) cout<<x[i]<<’ ‘; // Вывод
Vector<float> y(10); // Генерируется вектор вещественных
for (i=0; i<10; ++i) // Инициализация
y[i] = float(i);
for (i=0; i<10: ++i)
cout<<y[i]<<' '; //Вывод
}
Использование библиотеки atl для создания серверов сом
Построение компонентов СОМ с помощью низкоуровневых средств C++ требует большой работы повторяющегося характера. Библиотека активных шаблонов ATL помогает создать программный код для выполнения большей части работы, связанной со средствами СОМ. К такой работе относятся: регистрация и отмена регистрации серверов, создание фабрики классов и управление фиксациями сервера. ATL основана на шаблонах C++, которые легко настраиваются для генерирования требуемого программного кода.
Для построения компонента СОМ с помощью библиотеки ATL необходимо создать класс, порожденный хотя бы из двух других классов: CComObjectRoot и CComCoClass. Кроме того, из вашего класса должен быть порожден класс CComObject.
На рисунке проиллюстрированы основные связи между наиболее используемыми классами в сервере ATL COM.
На диаграмме показаны некоторые методы, реализованные с помощью каждого класса. Как видите, класс CComObject предоставляет интерфейс IUnknown. Методы AddRef и Release передают реальную работу классу CComObjectRoot. Поскольку класс CComObject порождается из вашего класса, ему требуется знать имя вашего класса. Поэтому он является шаблоном, а параметр шаблона (template parameter) является именем вашего класса, который становится базовым классом (base class) для CComObject.
Класс CComCoClass определяет модель фабрики классов. Этот класс также предоставляет два стандартных метода получения идентификатора CLSID объекта и его описание. Каждый объект СОМ, создаваемый клиентом, должен быть порожден из класса CComCoClass. Этот класс предоставляется в виде шаблона, имеющего два параметра: имя вашего класса и идентификатор CLSID компонента.
Класс CComObjectRoot обеспечивает фактическую реализацию средств для управления счетчиком ссылок объекта. Класс, реализующий сервер СОМ, должен порождается из класса CComObjectRoot.
Имеется множество других шаблонов классов, глобальных функций и макросов, предоставляемых библиотекой ATL.
Построение внутрипроцессного сервера СОМ с помощью ATL
Построим часть приложения, называющуюся Access Control Manager (Диспетчер доступа к элементу). Этот компонент проверяет, есть ли у пользователя доступ к запрашиваемой функции.
Начнем с создания нового проекта Visual C++, назвав его SecurityMgr. Мы будем использовать мастер ATL Project Wizard (выбрав его в диалоговом окне New Project (Создать проект)).
Мастер прост, но эффективен. Он имеет всего одно диалоговое окно, которое позволяет выбрать тип сервера и, при необходимости, установить дополнительные библиотеки (поддержку COM+ 1.0, библиотеки MFC). Одна из причин для выбора этой опции — потребность в повторном использовании программного кода, доставшегося по наследству, в котором задействованы функции MFC. Окно отрывается после щелчка на ссылке Application Settings.
Выберите в диалоговом окне ATL Project Wizard переключатель Dynamic Link Library (DLL) и щелкните на кнопке Finish. В диалоговом окне New Project Information (Информация о созданном проекте) щелкните на ОК, дав мастеру возможность создать требуемые файлы.
Для добавления к проекту класса компонента CoClass выполните следующие действия.
Выберите команду Add Class… в главном меню Project или контекстном меню в окне Solution Explorer.
Для создания простого компонента выберите объект Simple Object (Простой объект) и щелкните на кнопке Open. Отобразится диалоговое окно ATL Simple Object Wizard.
Введите AccessControl в поле Short Name (Краткое имя). В остальных полях отобразятся стандартные предопределенные имена.
Щелкните на Finish и примите стандартные установки.
Добавим свойство к созданному интерфейсу.
Во вкладке ClassView вы увидите добавленный интерфейс IAccessControl (именно так мы назвали его при добавлении объекта). Щелкните на этом интерфейсе правой кнопкой мыши и выберите в отобразившемся контекстном меню пункт Add Property (Добавить свойство). Откроется диалоговое окно Add Property Wizard (Добавление свойства интерфейса).
Выберите опцию BSTR в поле со списком Property Type (Тип свойства). Далее в поле Property Name (Имя свойства) введите UserName.
Щелкните на ссылке IDL Attributes (Атрибуты), измените атрибут Helpstring (Подсказка) на текст Sets or retrieves UserName (Установить или повторно использовать UserName).
Щелкните на Finish и закройте диалоговое окно Add Property Wizard.
Свойство UserName — строковая переменная для установки имени текущего пользователя. Это свойство используется в сочетании с методом IsAllowed, который мы вскоре добавим.
Тип BSTR определяет особую строку, используемую в средствах СОМ. Значения типа BSTR имеют одно ключевое свойство, отсутствующее в строках с завершающим нулем (null-terminated string): поле данных типа DWORD, предшествующее области памяти для строки BSTR и хранящее длину этой строки. Это свойство используется средствами СОМ при маршализации значений типа BSTR во время взаимодействия клиентов с серверами. Использование значений BSTR также имеет особенности: вы должны размещать строки BSTR в памяти с помощью функции SysAllocString или SysAllocStringByteLen, копируя ее параметр как строку с завершающим нулем во вновь размещаемую в памяти строку. Для освобождения памяти под строку BSTR используется функция SysFreeString.
Теперь реализуем свойство UserName.
Во вкладке Solution откройте файл AccessControl.h. В нем объявлена реализация класса C++.
В конце объявления класса добавьте закрытую (private) переменную-член типа BSTR.
private:
BSTR m_bstrUserName;
Мы будем использовать эту переменную-член для хранения значения свойства UserName.
В конструкторе класса добавьте код инициализации этой переменной значением NULL:
CAccessControl() : m_bstrUserName(NULL)
{
}
Откройте файл AccessControl. срр. В этом файле вы найдете следующие две функции:
STDMETHODIMP CAccessControl::get_UserName(BSTR *pVal)
{
return S_OK;
}
STDMETHODIMP CAccessControl::put_UserName(BSTR newVal)
{
return S_OK;
}
Эти функции добавлены мастером ATL Object Wizard. Заметьте: чтобы быть реализованным как свойство, доступное для чтения/записи, свойству UserName необходимы две функции: get_UserName (для чтения свойства) и put_UserName (для записи свойства). Введите реализации функций:
STDMETHODIMP CAccessControl::get_UserName(BSTR *pVal)
{
// Выделение и возврат памяти для новой строки BSTR.
*pVal=SysAllocString(m_bstrUserName);
return S_OK;
}
STDMETHODIMP CAccessControl::put_UserName(BSTR newVal)
{
// Освободить закрытую переменную-член типа BSTR
if(m_bstrUserName)
SysFreeString(m_bstrUserName);
// Выделить память закрытой переменной-члену типа BSTR
// Используем новое значение
m_bstrUserName = SysAllocString(newVal);
return S_OK;
}
Функция get_UserName выделяет память для новой строки BSTR и копирует в нее закрытую переменную-член m__bstrUserName. Эта новая переменная типа BSTR возвращается с помощью выходного параметра pVal. Функция put_UserName освобождает память существующей переменной-члена m_bstr-UserName и выделяет память для новой строки BSTR, копируя в нее входной параметр newVal. Обе функции возвращают S_OK.
В общем случае вы должны выделять память для выходных параметров в серверной программе (здесь вы знаете их размер) и освобождать их в клиентской программе после ее завершения. Клиент не может передавать указатель размещенной в памяти строки bstr, поскольку он не знает, каков срок жизни возвращаемой строки.
Добавим метод IsAllowed, который возвращает значение TRUE, если пользователю (как задано свойством UserName) доступна требуемая функция. Клиент должен установить свойство UserName, а затем вызвать этот метод, указав нужную функцию. Это позволяет клиенту создать экземпляр компонента AccessControl, единожды установив свойство UserName. Потом он сможет использовать метод IsAllowed для контроля доступа к различным функциям приложения.
Во вкладке ClassView щелкните правой кнопкой мыши на интерфейсе IAccessControl и в контекстном меню выберите пункт Add Method (Добавить метод), отобразив, тем самым, диалоговое окно Add Method Wizard (Добавление метода к интерфейсу).
В поле Method name (Имя метода) введите IsAllowed.
По очереди добавьте записи в список параметров метода. Метод IsAllowed содержит следующие параметры:
[in] short ReqFunction, [out, retval] VARIANT_BOOL *pbIsAllowed
Щелкните на ссылке IDL Attributes и установите атрибут Helpstring равным Returns TRUE if the user has access to the requested function, false otherwise. (Возврат TRUE, если пользователь имеет доступ к требуемой функции, в противном случае возврат FASLE.)
Щелкните на Finish, чтобы закрыть диалоговое окно Add Method Wizard.
Перед реализацией этого метода добавим другой метод - IsUserAllowed. Этот метод подобен методу IsAllowed, но первым параметром у него является имя пользователя. Снова щелкните правой кнопкой мыши на интерфейсе и выберите Add Method.
В поле Method Name задайте IsUserAllowed. В поле Parameters введите следующий код.
[in] BSTR UserName, [in] short ReqFunction, [out, retval] VARIANT_BOOL *pbIsAllowed
Щелкните на кнопке Attributes, установите атрибут Helpstring в режим Returns TRUE if the user specified by UserName has access to the requested function, false otherwise. (Возврат TRUE, если пользователь, указанный в параметре UserName, имеет доступ к требуемой функции, возврат FALSE в противном случае.)
Щелкните на Finish, чтобы закрыть диалоговое окно Add Method Wizard.
Для реализации нашего первого метода откройте файл AccessControl.срр и найдите добавленные методы класса. Добавьте в них следующий код.
STDMETHODIMP CAccessControl::IsUserAllowed(BSTR UserName,
short ReqFunction,
VARIANT_BOOL *pbIsAllowed)
{
// Сейчас мы разрешим доступ всем ко всему.
*pbIsAllowed=VARIANT_TRUE;
return S_OK;
}
STDMETHODIMP CAccessControl::IsAllowed (short ReqFunction,
VARIANT_BOOL *pbIsAllowed)
{
// Убедимся, что переменная-член имени пользователя не равна пустому указателю
if (!m_bstrUserName)
return E_INVALIDARG;
else
// Вызов метода IsUserAllowed и передача переменной m_bstrUserName
return IsUserAllowed(m_bstrUserName,ReqFunction,pbIsAllowed);
}
В этом методе переменная-член m_bstrUserName сравнивается со значением NULL. Если она равна значению NULL, то у нас нет имени пользователя, права доступа которого мы должны проверить, поэтому мы возвращаем значение E_INVALIDARG (это не совсем тип аргумента, предназначенный для указания ошибки, но в данном случае он нам годится).
Сервер создан. Теперь можно построить модуль DLL. В конце построения автоматически вызывается программа REGSVR32.EXE для регистрации нового модуля DLL. При переносе приложения на другой компьютер необходимо зарегистрировать на нем COM-сервер командой
REGSVR32.EXE SecurityMgr
Использование методов COM-сервера в клиентском приложении
Если вызов метода COM-сервера должен происходить после нажатия на кнопку экранной формы, то в обработчик события OnButtonClick нужно добавить следующий код.
// Создание экземпляра компонента SecurityMgr.AccessControl
// и получение указателя на IAccessControl
IAccessControl* pAccessControl=NULL;
HRESULT hr=CoCreateInstance(CLSID_CAccessControl,NULL,
CLSCTX_INPROC_SERVER,
IID_IAccessControl,
reinterpret_cast<void**> (&pAccessControl));
// Проверьте возвращаемый код
if (FAILED(hr))
{
MessageBox ("Could not create instance",
"Error in CoCreateInstance",MB_OK + MB_ICONSTOP);
}
else
{
// Используйте интерфейс
// Выделите память для параметра и инициализируйте его
BSTR bstrUserName=SysAllocStringByteLen("Any user name",14);
// ИЛИ
// BSTR bstrUserName=SysAllocString(L"Any user name");
// L"Any user name" – строка типа wide-char (2 байта на 1 символ)
long lReqFunction=3;
VARIANT_BOOL Result=VARIANT_FALSE;
// Делаем вызов
pAccessControl->IsUserAllowed(bstrUserName,lReqFunction,&Result);
// Проверяем результат
if(Result = = VARIANT_TRUE)
MessageBox("User is allowed access","Access Check",
MB_OK | MB_ICONINFORMATION);
else
MessageBox("User is denied access", "Access Check", MB_OK | MB_ICONSTOP) ;
// Реализация ссылки на интерфейс
pAccessControl->Release();
// Освободите BSTR
SysFreeString(bstrUserName);
}
Кроме того, в модуль, где определен обработчик события OnButtonClick, необходимо добавить следующие два оператора вставки файлов:
#include "..\..\SecurityMgr\_SecurityMgr.h"
#include "..\..\SecurityMgr\_SecurityMgr_i.c"
Если путь к файлам отличается от указанного в примере, исправьте его.
Найдите в приложении метод InitInstance(). Добавьте в него первой строкой вызов CoInitialize(NULL) и вызов CoUninitialize () перед возвратом из метода.
После этого можно перекомпилировать проект и проверить его работу.
Работа со строками типа BSTR
Чтобы облегчить жизнь программистам COM, библиотека ATL содержит класс CComBSTR, который управляет выделением и освобождением памяти под значения типа BSTR, а также предоставляет несколько полезных операторов и функций для обработки строк (Append(), Copy(), Length(), операторы сравнения и т.п.). Класс CComBSTR следует использовать вместо типа BSTR везде, где только возможно, например, в методе CAccesControl::IsUserAllowed.
STDMETHODIMP CAccesControl::IsUserAllowed(BSTR UserName, SHORT ReqFunction, VARIANT_BOOL* pbIsAllowed)
{
// TODO: Add your implementation code here
CComBSTR UName;
UName = UserName;
if (UName = = "admin")
*pbIsAllowed=VARIANT_TRUE;
else
*pbIsAllowed=VARIANT_FALSE;
return S_OK;
}