Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги хакеры / Защита_от_взлома_сокеты,_эксплойты,_shell_код_Фостер_Дж_

.pdf
Скачиваний:
14
Добавлен:
19.04.2024
Размер:
3.68 Mб
Скачать

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

Библиотека ATL 661

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

 

m

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

g

.c

 

 

.

 

 

 

 

g

.c

 

 

 

p

 

 

 

 

 

 

 

 

 

p

 

-x cha

 

 

 

 

 

 

 

-xchэлементa

массива DWORD, функция может модифицировать значение, на ко-

 

 

e

 

 

 

 

df

 

 

n

e

 

 

 

 

df

 

 

n

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

торое указывает p, или только читать его. Если бы вместо DWORD* мы указали тип PVOID, то оставалось бы только гадать, что делает функция. При программировании на C++ не так важно указывать точный тип. Но когда речь идет о вызове метода из другого адресного пространства, ситуация радикально меняется.

Поскольку определения интерфейсов должны быть строго типизированы, в COM применяется специальный язык (IDL) для описания как интерфейсов, так и COM-объектов. Следовательно, в любом проекте для реализации COM-объекта, поддерживающего различные контексты загрузки и потоковые модели, обязательно будет присутствовать файл с определениями всех интерфейсов.

Такие файлы имеют расширение .IDL и передаются компилятору MIDL, который создает несколько других файлов, описывающих интерфейсы, предоставляемые вашим сервером. Сначала составим описание интерфейса на языке IDL, а затем посмотрим, во что его преобразует MIDL.

1[object, uuid("85C5B433-C053-435f-9E4A-8C48557E1D4B")]

2 interface IWarpEngine : IUnknown

3{

4 HRESULT Engage([in] VARIANT_BOOL vbEngage);

5};

Это описание типичного интерфейса на языке IDL. Рассмотрим его подробнее.

В первой строке указаны атрибуты интерфейса:

Данный интерфейс принадлежит объекту (слово object);

IID интерфейса равен 85C5B433-C053-435f-9E4A-8C48557E1D4B.

Во второй строке говорится, что интерфейс называется IWarpEngine. В языке C++ можно указывать наследование; то же позволяет и IDL – можно сообщить, что один интерфейс наследует другому. Следующий пример типичен для описания любого интерфейса, совместимого с автоматизацией, который должен наследовать интерфейсу IDispatch.

Как и в случае определения класса, внутри объявления интерфейса пере- числяются методы. В фигурных скобках указаны названия всех методов и свойств, поддерживаемых данным интерфейсом.

Семантика функции Engage в примере выше очевидна из ее описания: она возвращает значение типа HRESULT и принимает в качестве параметра VARIANT_BOOL. Относительно ее использования не возникает никаких сомнений, в этом и состоит смысл IDL.

Свойства COM-класса описываются на IDL следующим образом:

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

662 Глава 13. Написание компонентов для задач, связанных с безопасностью

w Click

 

 

 

 

 

 

 

 

 

 

 

 

 

m

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

 

 

 

 

e

 

 

 

 

df-xchan

[propget] Speed([out, retval] LONG *pSpeed);

[propput] Speed([in] LONG Speed);

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

В описании свойства перед его именем должен находиться один из атрибутов propget èëè propput. Атрибут propget говорит, что функция может принимать только один выходной параметр. Этот факт описывается атрибутом, предшествующим объявлению аргументов: [out, retval]. Функция с атрибутом propput принимает только один входной параметр, что подчеркивается нали- чием перед объявлением его атрибута [in]. При описании интерфейсов поча- ще обращайтесь к документации в MSDN, чтобы выяснить, как выразить желаемое на языке IDL. Главное, помните, что описание интерфейса не должно допускать неоднозначного толкования.

Закончив описывать COM-интерфейсы на IDL, приступим к определению окружения, в котором они будут существовать, то есть библиотеки типов и кокласса (сокращенное название для «класса компонента»). На IDL это выглядит так:

[uuid("DEEC1A90-820C-4744-Be1D-9E3C357EDE81"), version(1.0)] library SpaceShipLib

{

importlib("stdole32.tlb");

[uuid("305441D4-9014-4D49-A54F-2DF536E5EC67")] coclass SpaceShip

{

interface IWarpEngine;

};

};

Как и во всех конструкциях IDL, в первой строке описываются атрибуты следующей ниже библиотеки. Это ее идентификатор LIBID и номер версии. Разделlibrary предписывает IDL построить библиотеку типов (TypeLib). В ней в двоичном виде хранится вся информация об используемых типах. Из этой библиотеки среда исполнения COM узнает о порядке использования компонента.

В теле раздела library находятся, в частности, описания коклассов. В данном случае есть всего один такой класс SpaceShip, а кроме того импортируется откомпилированная библиотека типов stdole32.tlb.

Внутри объявления кокласса SpaceShip есть ссылканаинтерфейс IWarpEngine. Поэтому вся информация об этом интерфейсе включается в библиотеку типов. Кроме того, наличие ссылки означает, что кокласс SpaceShip поддерживает интерфейс IWarpEngine.

После создания IDL-файла его нужно откомпилировать с помощью MIDL. Программа MIDL распознает различные флаги в командной строке, но на

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

Библиотека ATL 663

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

 

m

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

g

.c

 

 

.

 

 

 

 

g

.c

 

 

 

p

 

 

 

 

 

 

 

 

 

p

 

-x cha

 

 

 

 

 

 

 

-xchбольшинствоa

из них можно не обращать внимания, если только вы не делае-

 

 

e

 

 

 

 

df

 

 

n

e

 

 

 

 

df

 

 

n

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

те что-то необычное. Как правило, достаточно задать лишь имя IDL-файла:

midl.exe SpaceShip.idl

В результате успешной компиляции в том же каталоге появляются несколько новых файлов, перечисленных в таблице 13.2.

Таблица 13.2. Список файлов, сгенерированных компилятором MIDL

Имя файла

Назначение

Spaceship.h

Этот файл следует включить в ATL проект, так как он

 

содержит определения всех абстрактных классов

 

в синтаксисе C++, которые предстоит реализовать

 

в вашем коклассе. Помимо этого, здесь же находятся

 

ссылки на CLSID, IID и LIBID, сгенерированные MIDL. Если

 

хотите назвать файл иначе, воспользуйтесь флагом /h

Spaceship_i.c

Этот файл содержит реальные значения тех GUID, на

 

которые ссылается файл SpaceShip.h. Если хотите

 

назвать файл иначе, воспользуйтесь флагом /iid

Spaceship.tlb

Этот файл содержит откомпилированную библиотеку

 

типов. При желании можно восстановить исходный IDL

 

файл из tlb файла. Этот файл можно распространять

 

независимо от модуля или включить его в состав

 

ресурсов. Обычно ему назначается первый порядковый

 

номер в разделе ресурсов

Dlldata.c

Этот файл содержит информацию о точках входа для

 

заглушки и заместителя, которые необходимы при

 

удаленном вызове интерфейсов

Spaceship_p.c

Этот файл содержит код заглушки и заместителя для

 

вызова методов интерфейса объекта, находящегося

 

в отдельном процессе, например, в EXE файле

Регистрация класса

Как вы уже знаете, любой COM-объект должен быть предварительно зарегистрирован. Если объект реализован в виде DLL, то регистрация выполняется, когда клиент обращается к точке входа DllRegisterServer. Если же объект представлен в виде EXE-файла, то для регистрации нужно запустить этот файл с флагом /regserver. Сама же процедура регистрации мало отличается.

Регистрация компонентов, созданных без применения ATL, – это муторное занятие, сводящееся к многократным вызовам функций для работы с реестром. Напротив, ATL существенно упрощает дело, позволяя ассоциировать с каждым коклассом так называемый сценарий реестра. Формат таких сценариев был изобретен задолго до появления ATL.

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

i

 

 

 

F

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

t

 

 

 

 

 

 

 

 

 

 

t

 

P

D

 

 

 

 

 

 

 

 

o

 

P

D

 

 

 

 

 

 

 

 

o

 

 

 

 

NOW!

r

 

 

 

 

 

NOW!

r

 

 

 

 

 

BUY

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

664 Глава 13. Написание компонентов для задач, связанных с безопасностью

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

m

 

w

 

 

 

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

o

 

w Click

 

 

 

 

 

 

o

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

 

.c

 

 

 

.

 

 

 

 

 

 

.c

 

 

 

p

df

 

 

 

 

e

 

 

 

 

p

df

 

 

 

 

e

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

-xcha

 

 

 

 

 

Как вы, наверное, догадались, сценарии реестра обрабатываются во время-x cha

 

 

 

 

 

регистрации COM-объекта. ATL запускает встроенный механизм, который обновляет реестр в соответствии со сценарием. Рассмотрим пример:

HKCR {

NoRemove CLSID {

ForceRemove {9C129B36-EE42-4669-B217-4154821F9B4E} =

s 'MySimpleObject Class' {

InprocServer32 = s '%MODULE%' {

val ThreadingModel = s 'Apartment'

}

}

}

}

Любой программист на C++ сразу же распознает в этом фрагменте иерархическую структуру. Вначале указывается корневой ключ. Он соответствует улью реестра, который обновляется сценарием. Корневой ключ может принимать следующие значения:

HKEY_CLASSES_ROOT (èëè HKCR);

HKEY_CURRENT_USER (èëè HKCU);

HKEY_LOCAL_MACHINE (èëè HKLM);

HKEY_USERS (èëè HKU);

HKEY_PERFORMANCE_DATA (èëè HKPD);

HKEY_DYN_DATA (èëè HKDD);

HKEY_CURRENT_CONFIG (èëè HKCC).

В нашем примере использован ключ HKCR (или HKEY_CLASSES_ROOT). Все, что следует за корневым ключом, называется реестровым выражением и состоит из команд добавления или удаления ключа. Все потомки корневого элемента в показанном выше сценарии – это выражения, инструктирующие ATL добавить в реестр ключи с указанными значениями. Рассмотрим каждую строку сценария:

NoRemove CLSID {

Здесь от ATL требуется при необходимости создать ключ CLSID, но никогда не удалять существующий.

ForceRemove {9C129B36-EE42-4669-B217-4154821F9B4E} =

s 'MySimpleObject Class' {

В этой строке дается задание создать ключ, содержащий значение GUID. Атрибут ForceRemove говорит, что ключ следует удалить при удалении COM-

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

Библиотека ATL 665

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

 

m

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

g

.c

 

 

.

 

 

 

 

g

.c

 

 

 

p

 

 

 

 

 

 

 

 

 

p

 

-x cha

 

 

 

 

 

 

 

-xchобъекта.a

Кроме того, это выражение заставляет ATL создать для нового

 

 

e

 

 

 

 

df

 

 

n

e

 

 

 

 

df

 

 

n

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ключа строковое значение по умолчанию (о чем свидетельствует буква ‘s’, предшествующая литералу) и записать в него строку «MySimpleObject Class». Важно отметить, что если ключ помечен атрибутом ForceRemove, то и все его потомки тоже будут удалены.

InprocServer32 = s '%MODULE%' {

val ThreadingModel = s 'Apartment'

}

Этот фрагмент аналогичен предыдущему; здесь тоже создается новый ключ, на этот раз с именем InprocServer32, для которого значением по умол- чанию является путь к файлу регистрируемого модуля. Вы, конечно, поняли, что строка %MODULE% интерпретируется как переменная окружения сценария или макрос, который подставляется во время запуска сценария препроцессором, входящим в состав ATL.

Теперь вы знаете, как писать сценарии реестра, осталось связать их с вашим коклассом. Для этого нужно сделать две вещи:

поместить сценарий в раздел ресурсов компонента;

объявить идентификатор соответствующего ресурса.

Для выполнения первого шага перейдите на вкладку Resource View в проекте, создаваемом в Visual Studio, щелкните правой кнопкой по ресурсному файлу и выберите из контекстного меню пункт Add Resource (ñì. ðèñ. 13.3).

Рис. 13.3. Диалоговое окно для добавления ресурса в Visual Studio.NET

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

i

 

 

 

F

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

t

 

 

 

 

 

 

 

 

 

 

t

 

P

D

 

 

 

 

 

 

 

 

o

 

P

D

 

 

 

 

 

 

 

 

o

 

 

 

 

NOW!

r

 

 

 

 

 

NOW!

r

 

 

 

 

 

BUY

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

666 Глава 13. Написание компонентов для задач, связанных с безопасностью

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

m

 

w

 

 

 

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

o

 

w Click

 

 

 

 

 

 

o

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

 

.c

 

 

 

.

 

 

 

 

 

 

.c

 

 

 

p

df

 

 

 

 

e

 

 

 

 

p

df

 

 

 

 

e

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

-xcha

 

 

 

 

 

Затем нажмите кнопку Import, найдите среди файлов проекта сценарий-x cha

 

 

 

 

 

реестра и нажмите OK. Visual Studio попросит указать тип импортируемого ресурса. Принято обозначать RGS-сценарии строкой REGISTRY, поэтому введите эту строку и нажмите OK. Осталось лишь переименовать ресурс так, чтобы он ассоциировался с вашим коклассом. После всего этого сценарий реестра окажется в одном файле с компонентом и найти его можно будет по имени, которое, кстати, указано и в сгенерированном файле resource.h.

Покончив с шагом 1, можно перейти к шагу 2, на котором мы ассоциируем сценарий реестра с коклассом. Для этого предназначен макрос DECLARE_REGISTRY_RESOURCE_ID, которому передается идентификатор ресурса.

Макрос DECLARE_REGISTRY_RESOURCE_ID расширяется в вызов следующей статической функции:

#define DECLARE_REGISTRY_RESOURCE_ID(x) \

static HRESULT WINAPI UpdateRegistry(BOOL bRegister) throw()\ { }; // Код опущен

Функция UpdateRegistry строит массив пар имя/значения, который регистратор ATL использует для расширения таких макросов препроцессора, как %MODULE%. Если у вас возникнет желание передать нестандартное значе- ние, то надо будет лишь переписать функцию UpdateRegistry с учетом своих требований. Основное назначение этой функции – передать информацию об именах, а также идентификатор ресурса сценария глобальной функции UpdateRegistryFromResource, которая и выполняет основную часть работы по регистрации класса.

Реализация внутрипроцессного COM-сервера

При разработке COM-сервера у вас есть выбор: написать весь код вручную, воспользоваться встроенными в Visual Studio мастерами или применить смешанный подход. Общее правило – поступать так, как диктует конкретный проект. Если вы делаете что-то необычное, то желательно получить больший контроль над проектом, и тогда лучше писать вручную. Но на каком бы решении вы ни остановились, надо понимать, что требуется от модуля и как этого добиться.

Глобальная переменная _AtlModule

В любом приложении, которое пишется с применением библиотеки ATL, вне зависимости от типа сервера, должна быть определена глобальная переменная с именем _AtlModule. Меняется от проекта к проекту ее òèï, который зависит от выбранного вида проекта. Например, при реализации сервера в виде DLL эта переменная должна иметь тип CAtlDllModuleT, а для сервера в виде EXE-

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

Библиотека ATL 667

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

 

m

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

g

.c

 

 

.

 

 

 

 

g

.c

 

 

 

p

 

 

 

 

 

 

 

 

 

p

 

-x cha

 

 

 

 

 

 

 

-xchфайлаa

– òèï CAtlExeModuleT. Именно тип переменной _AtlModule и включает

 

 

e

 

 

 

 

df

 

 

n

e

 

 

 

 

df

 

 

n

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

в приложение нужную функциональность.

Объявить один лишь класс модуля при работе с ATL нельзя; он должен быть производным от создаваемого вами класса. Это позволяет включить в модуль некоторые константные свойства, не вызывая никаких функций инициализации. Приведем пример:

class CMyApplicationModule :

public CAtlDllModuleT< CMyApplicationModule >

{

public: DECLARE_LIBID(LIBID_MyApplicationModule)

DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MYAPPLICATIONMODULE, "{4DD88301-0C57-416B-953C-3829095440C05}")

}

CMyApplicationModule _AtlModule;

Предыдущий фрагмент содержит скелет объявления и определения любого внутрипроцессного COM-сервера. Класс CMyApplicationModule наследует входящему в ATL шаблонному классу CAtlDllModuleT, который предоставляет всю свойственную DLL функциональность и принимает в качестве параметраимя класса CMyApplicationModule.

Следующие две строки сообщают классу CMyApplicationModule константную информацию, а именно: GUID библиотеки типов (LIBID), хранящийся в переменной LIBID_MyApplicationModule, и идентификатор ресурса, в котором находится сценарий реестра. Кроме того, макрос DECLARE_REGISTRY_APPID_RESOURCEID, как следует из его названия, передает классу модуля идентификатор приложения APPID, который будет нужен во время регистрации.

Функции, экспортируемые из DLL

Выше мы уже говорили, что COM-объекты, реализованные в виде DLL, должны экспортировать четыре функции, чтобы среда исполнения могла их корректно загрузить. Функции называются DllGetClassObject, DllCanUnloadNow, DllRegisterServer и DllUnregisterServer. Поддержка, предоставляемая ATL, позволяет переместить весь связанный с ними стандартный код в библиотеку. Чтобы понять, как это делается, рассмотрим следующий код, сгенерированный мастером:

STDAPI DllGetClassObject(

REFCLSID rclsid,

REFIID riid,

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

 

668 Глава 13. Написание компонентов для задач, связанных с безопасностью

w Click

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

m

 

w

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

 

.

 

 

 

 

 

 

.c

 

 

 

 

p

 

 

 

 

g

 

 

LPVOID* ppv)

 

 

 

df-xchan

e

 

 

 

 

 

 

 

 

{

return _AtlModule.DllGetClassObject( rclsid, riid, ppv);

}

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Прежде чем переходить к анализу этого кода, уместно будет сделать несколько замечаний. ATL не экспортирует явно никаких функций из вашего модуля. Поэтому вы сохраняете полный контроль над тем, как модуль строится. Однако ATL предоставляет стандартную реализацию того, что требуется от экспортируемых функций, заключая ее в класс CAtlDllModuleT, которому принадлежит переменная _AtlModule.

Мастер, входящий в Visual C++, сгенерировал показанную выше экспортируемую функцию DllGetClassObject, заставив ее просто делегировать всю работу методу DllGetClassObject глобального объекта _AtlModule. Тот проанализирует запрос и, если запрошенный компонент будет найден, создаст и вернет фабрику класса для него.

ATL определяет, какие объекты может предоставить, на основании информации, полученной от самих объектов, которые объявили о себе и пожелали предоставлять свои услуги клиентам модуля. Такое объявление делается с помощью макроса OBJECT_ENTRY_AUTO, который обычно располагается после объявления соответствующего класса. Этот макрос вставляет CLSID и имя класса в карту объектов модуля, поддерживаемую ATL. Зная это, ATL может правильно выполнить регистрацию и создание фабрики класса.

STDAPI DllRegisterServer(void)

{

HRESULT hê = _AtlModule.DllRegisterServer() return hr;

}

Здесь представлено определение экспортируемой функции DllRegisterServer. Как видно, она делегирует всю работу методу DllRegisterServer глобального объекта _AtlModule.

STDAPI DllUnregisterServer(void)

{

HRESULT hê = _AtlModule.DllUnregisterServer() return hr;

}

И здесь то же самое – экспортируемая функция делегирует запрос методу _AtlModule.

STDAPI DllCanUnloadNow(void)

{

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

 

o

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

return _AtlModule.DllCanUnloadNow()

 

 

 

df-xchan

e

 

 

 

 

 

}

Та же картина.

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

Библиотека ATL 669

 

to

 

 

 

 

 

 

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Точка входа в модуль

Все динамически загружаемые библиотеки в Windows должны экспортировать точку входа DllMain. Если вас устраивает стандартная реализация, то можете просто обратиться к методу DllMain класса CAtlDllModuleT. Впрочем, это необязательно. Предлагаемая ATL реализация делает несколько простых проверок и возвращает TRUE.

Реализация внепроцессного COM-сервера

Зная, как реализуется COM-сервер в виде DLL, вы, наверное, сочтете, что написание сервера в виде EXE-файла аналогично и в чем-то даже проще. ATL предоставляет обширную поддержку практически для всех аспектов реализации EXE-сервера.

Глобальная переменная _AtlModule

В случае EXE-сервера переменная _AtlModule представляет собой объект класса производного от шаблонного класса CAtlExeModuleT. Вы можете сконфигурировать этот класс с помощью макросов ATL. Вот пример объявления и определения переменной _AtlModule.

class CSpaceShipModule :

public CAtlExeModuleT< CSpaceShipModule >

{

public: DECLARE_LIBID(LIBID_SpaceShipLib)

DECLARE_REGISTRY_APPID_RESOURCEID(IDR_SPACESHIP, "{48DF7A09-18CF-4C05-969C-2AAA42363B4AD}")

}

CSpaceShipModule _AtlModule;

Здесь объявлен класс CSpaceShipModule, наследующий классу CAtlExeModuleT, и и с помощью макросов для него задана некоторая статическая информация.

Точка входа в модуль

Существенная часть работы EXE-сервера выполняется в его точке входа. Чтобы предоставить доступ к своим объектам клиентам, находящимся в другом процессе, сервер должен выполнить ряд действий:

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

670 Глава 13. Написание компонентов для задач, связанных с безопасностью

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

w Click

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

m

 

 

 

 

 

 

 

m

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

-xcha

 

 

.c

 

 

зарегистрировать свои объекты в глобальном кэше;

 

.

 

-x cha

 

 

.c

 

 

 

p

 

g

 

 

 

 

 

p

 

g

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

посмотреть, нет ли в командной строке флага /regserver, и, если есть, зарегистрировать объект в реестре;

если в командной строке есть флаг /unregserver, удалить из реестра информацию о ранее зарегистрированных объектах.

Все эти действия выполняются одной функцией WinMain, предоставляемой классом CAtlExeModuleT, которая решает их следующим образом:

T* pT = static_cast<T*>(this); LPTSTR lpCmdLine = GetCommadLine();

if (pT->ParseCommadLine(lpCmdLine, &hr) == true) hr = pt->Run(nShowCmd);

Сначала указатель this приводится к классу вашего EXE-сервера на случай, если вы захотите реализовать свой способ разбора аргументов в командной строке или самостоятельно управлять состоянием приложения из метода Run. Решить эти задачи можно, добавив в класс, которому принадлежит объект _AtlModule, такие строки:

HRESULT Run(int nShowCmd)

{

return CAtlExeModuleT<CSpaceShipModule>::Run(nShowCmd);

}

HRESULT ParseCommadLine(LPCTSTR lpCmdLine, HRESULT *pnRetCode)

{

return CAtlExeModuleT<CSpaceShipModule>::ParseCommadLine( lpCmdLine, pnRetCode);

}

Метод Run класса CAtlExeModuleT вызывает метод PreMessageLoop того же класса. Этот метод и отвечает за регистрацию объектов, которыми могут пользоваться клиенты.

После того как объекты зарегистрированы и окружение EXE-сервера подготовлено, метод Run вызывает метод RunMessageLoop, который входит в стандартный цикл обработки сообщений. Его можно также переопределить с помощью кода, аналогичного приведенному выше.

Когда модуль будет готов к завершению, метод Run вызовет PostMessageLoop, который удалит зарегистрированные объекты и освободит ресурсы, занятые ATL. Затем управление вернется в точку входа в приложение.

Атрибуты ATL

Итак, мы познакомились с основами разработки COM-объектов с помощью библиотеки ATL. Сейчас вы уже могли бы написать собственные COM-объ-