Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП_ЛабРаботы.doc
Скачиваний:
13
Добавлен:
28.09.2019
Размер:
716.8 Кб
Скачать

Шаблоны классов

Шаблоны предназначены для создания функций и классов, отличающихся друг от друга только типом обрабатываемых ими данных. Та­кие функции и классы часто называют соответственно параметризованными функциями (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 выполните следующие действия.

  1. Выберите команду Add Class… в главном меню Project или контекстном меню в окне Solution Explorer.

  2. Для создания простого компонента выберите объект Simple Object (Простой объект) и щелкните на кнопке Open. Отобразится диалоговое окно ATL Simple Object Wizard.

  3. Введите AccessControl в поле Short Name (Краткое имя). В остальных полях отобразятся стандартные предопределенные имена.

  4. Щелкните на Finish и примите стандартные установки.

Добавим свойство к созданному интерфейсу.

  1. Во вкладке ClassView вы увидите добавленный интерфейс IAccessControl (именно так мы назвали его при добавлении объекта). Щелкните на этом ин­терфейсе правой кнопкой мыши и выберите в отобразившемся контекстном меню пункт Add Property (Добавить свойство). Откроется диалоговое окно Add Property Wizard (Добавление свойства интерфейса).

  2. Выберите опцию BSTR в поле со списком Property Type (Тип свойства). Да­лее в поле Property Name (Имя свойства) введите UserName.

  3. Щелкните на ссылке IDL Attributes (Атрибуты), измените атрибут Helpstring (Подсказка) на текст Sets or retrieves UserName (Установить или повторно использовать UserName).

  4. Щелкните на Finish и закройте диалоговое окно Add Property Wizard.

Свойство UserName — строковая переменная для установки имени текущего пользователя. Это свойство используется в сочетании с методом IsAllowed, который мы вскоре добавим.

Тип BSTR определяет особую строку, используемую в средствах СОМ. Значения типа BSTR имеют одно клю­чевое свойство, отсутствующее в строках с завершающим нулем (null-terminated string): поле данных типа DWORD, предшествующее области памяти для строки BSTR и хранящее длину этой строки. Это свойство используется средствами СОМ при маршализации значений типа BSTR во время взаимодействия клиентов с серверами. Использование значений BSTR также имеет особенности: вы должны размещать строки BSTR в памяти с помощью функции SysAllocString или SysAllocStringByteLen, ко­пируя ее параметр как строку с завершающим нулем во вновь размещаемую в памяти строку. Для освобождения памяти под строку BSTR используется функция SysFreeString.

Теперь реализуем свойство UserName.

  1. Во вкладке Solution откройте файл AccessControl.h. В нем объявлена реализация класса C++.

  2. В конце объявления класса добавьте закрытую (private) переменную-член типа BSTR.

private:

BSTR m_bstrUserName;

Мы будем использовать эту переменную-член для хранения значения свойства UserName.

  1. В конструкторе класса добавьте код инициализации этой переменной значе­нием NULL:

CAccessControl() : m_bstrUserName(NULL)

{

}

  1. Откройте файл 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 для контроля доступа к различным функциям приложения.

  1. Во вкладке ClassView щелкните правой кнопкой мыши на интерфейсе IAccessControl и в контекстном меню выберите пункт Add Method (Добавить метод), отобразив, тем самым, диалоговое окно Add Method Wizard (Добавление метода к интерфейсу).

  2. В поле Method name (Имя метода) введите IsAllowed.

  3. По очереди добавьте записи в список параметров метода. Метод IsAllowed содержит следующие параметры:

[in] short ReqFunction, [out, retval] VARIANT_BOOL *pbIsAllowed

  1. Щелкните на ссылке IDL Attributes и установите атрибут Helpstring равным Returns TRUE if the user has access to the requested function, false otherwise. (Возврат TRUE, если пользователь имеет доступ к требуемой функции, в противном случае возврат FASLE.)

  2. Щелкните на Finish, чтобы закрыть диалоговое окно Add Method Wizard.

  3. Перед реализацией этого метода добавим другой метод - IsUserAllowed. Этот метод подобен методу IsAllowed, но первым параметром у него является имя пользователя. Снова щелкните правой кнопкой мыши на интерфейсе и выберите Add Method.

  4. В поле Method Name задайте IsUserAllowed. В поле Parameters введите сле­дующий код.

[in] BSTR UserName, [in] short ReqFunction, [out, retval] VARIANT_BOOL *pbIsAllowed

  1. Щелкните на кнопке Attributes, установите атрибут Helpstring в режим Returns TRUE if the user specified by UserName has access to the requested function, false otherwise. (Возврат TRUE, если пользователь, указанный в па­раметре UserName, имеет доступ к требуемой функции, возврат FALSE в противном случае.)

  2. Щелкните на 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;

}