Конструкторы и деструкторы delphi

Обновлено: 04.05.2024

Когда читаешь о той или иной реализации механизмов ООП, то весьма забавно видеть, как та или иная конкретная особенность конкретного языка или библиотеки называется «очевидной». Особенно выделяется в этом отношении описание ООП в C++, в котором по факту реализация одна из самых непрозрачных и запутанных.
Так вот, я ничего не буду писать про очевидность, а расскажу про способ облегчить себе жизнь в критических точках жизненного цикла объектов, используя не «очевидные», но весьма полезные особенности реализации ООП в Delphi.

Обработка невозможности завершить создание объекта в конструкторе

Если конструктор хранит свои данные только в полях объекта, то решение элементарно — достаточно возбудить исключение. RTL Delphi сама его перехватит, вызовет деструктор, высвободит занятую память и возбудит исключение повторно.
Соответственно, если часть данных для конструирования хранится в глобальных переменных, то достаточно использовать обычный блок try..except с повторным возбуждением исключения.
Отсюда выводятся два требования к деструкторам: не провоцировать исключения, что означает не пытаться заниматься чем-либо, кроме освобождения ресурсов (например, сохранением настроек) и обязательно поддерживать…

Удаление частично инициализированного объекта

В Delphi это не представляет никаких трудностей, так как любой объект еще до передачи управления конструктору инициализируется нулями. Соответственно, в деструкторе достаточно корректно обрабатывать нулевые значения, в чем сильно помогает процедура FreeAndNil, которая освобождает объект, только если ссылка на него уже не nil.

Порядок вызова конструкторов

В Delphi он не имеет значения. Можно вызывать конструкторы предка, другие конструкторы того же класса точно так же, как и обычные методы или не вызывать ничего — так что если надо использовать инициализацию предка, то забывать ее вызвать не рекомендуется, зато и проблем сделать что-то до или вместо вызова конструктора предка нет никаких.

Вызов виртуальных методов в конструкторе

Так как в Delphi объект сразу инициализируется конечной ссылкой на VMT, то никакой разницы между вызовами виртуальных методов из конструктора от прочих вариантов нет.

Виртуальные конструкторы и ссылки на класс

В Delphi конструкторы могут быть виртуальными. Смысл такой особенности — возможность создания объектов с неизвестным на этапе компиляции классом без необходимости реализации фабрики. С этой целью используется переменные со ссылкой на класс (а не объект!), для которых и можно вызвать виртуальный конструктор, получив экземпляр соответствующего класса или его потомка, в зависимости от значения ссылки.

Автоматическое управление временем жизни объекта

Если объект реализует тот или иной явно заданный интерфейс, то его можно привести к ссылке на этот интерфейс, используя присваивание или операцию as. В этом случае Delphi будет сама генерировать вызовы методов IUnknown, что позволяет не запрашивать интерфейсы и не удалять объекты явно.

Итоги


С одной стороны, все вышеперечисленное должен знать каждый разработчик; с другой — я встречал не так уж мало программистов со стажем, изобретающих велосипеды только от незнания особенностей реализации своего рабочего инструмента. Надеюсь, что эта статья немного поможет. Из полезных особенностей реализации ООП в Delphi осталась нерассмотренной поддержка делегирования реализации интерфейсов, но это тема для отдельной статьи.


Общая иерархия классов Delphi, поддерживающих СОМ технологию имеет следующий вид.

• TInterfacedObject — Определен в модуле System. Реализует интерфейс
IUnknown. Используется для реализации объектов внутри приложений.

• TComObject — Определен в модуле ComObj. Реализует интерфейс IUnknown и ISupportEiTorlnfo. Используется для реализации объектов во внутренних СОМ-серверах (реализованных в виде D11). Имеет фабрику классов TComObjectFaetory, которая реализует интерфейсы IUnknown, IClassFactory и IClassFactory2.

• TTypedComObject — Определен в модуле ComObj. Дополнительно реализует интерфейс IProvideClassInfo. Используется для реализации объектов локальных и удаленных СОМ-серверов (реализованных в виде Ехе). Имеет фабрику классов TTypedComObjectFactory.

• TAutoObject — Определен в модуле ComObj. Дополнительно реализует интерфейс IDispatch. Фактически определяет дуальные интерфейсы. Используется для реализации объектов на внутренних, локальных и удаленных OLE-серверах. Имеет фабрику классов TAutoObjectFactory. Для создания таких серверов также следует создавать библиотеку типов с помощью соответствующего редактора.

• TActiveXControl — Определен в модуле AxCtrls. Используется для реализации компонентов ActiveX. Имеет фабрику классов TActiveXControlFactory.

• TActiveFormControl — Определен в модуле AxCtrls. Используется для реализации компонентов ActiveForm. Имеет фабрику классов TActiveFormFactory.

Переменные интерфейсного типа
Если вы объявляете переменную типа интерфейс, то эта переменная может ссылаться на экземпляры любого класса, реализующего интерфейс. Такие переменные позволяют вызывать методы интерфейса, не зная во время компиляции, где интерфейс реализован. Для получения указателя на интерфейс можно присвоить интерфейсной переменной экземпляр класса, реализующего интерфейс, использовать приведение типа или использовать метод Getlnterface класса TObject.

Intf:=MyObj; //Присвоение экземпляра
Intf:=TMyCIass.Create As IMylntf; // Приведение типа
Intf:=MyObj As IMylntf; // Приведение типа

• переменная типа интерфейс дает доступ только к методам и свойствам, объявленным в интерфейсе, но не к иным элементам класса реализации;

• переменная типа интерфейс не может ссылаться на объект, чей класс реализует интерфейс потомка, если этот класс (или тот, из которого он наследуется) не реализует и интерфейс предка тоже;

• использование оператора As приводит к побочному эффекту, заключающемуся в том, что он вызывает функцию _AddRef в начале работы с интерфейсом и функцию _Release по окончании работы. В результате объект уничтожается. Если в программе предусмотрен доступ с помощью экземпляра класса реализации и интерфейсной переменной, то с помощью экземпляра класса уже нельзя будет обратиться, поскольку объект уничтожен.

• Интерфейсные ссылки управляются через переменную FRefCount, значение которой зависит от методов _AddRef и _Release, унаследованных из IUnknown. Когда объект вызывается только через интерфейсы, не требуется уничтожать его вручную, поскольку он удалиться автоматически, когда исчезнет последняя ссылка к нему.

• Чтобы определить, ссылается ли выражение типа интерфейса на объект, следует обратиться к нему с помощью стандартной функции Assigned,которая возвращает False, если интерфейс не ссылается на объект.

• Классовый тип совместим с любым типом интерфейса, реализованным классом. Интерфейсный тип совместим с любым типом интерфейса предка. Значение Nil может быть определено любому интерфейсному типу.

• Выражение типа интерфейса может быть определено как Variant. Если интерфейс имеет тип IDispatch или его потомка, Variant получает значение типа varDispatch. Иначе, вариант получает значение типа varUnknown.

• Интерфейсные типы следуют тем же правилами что и классовые типы в переменных и привидениях типов. Выражения типа класса могут приводиться для связывания с интерфейсным типом, например:

IMyIntf(SomeObject); // Класс реализует интерфейс

Свойства интерфейсов
Помимо методов, описание интерфейса может включать и объявления свойств.
Синтаксис объявления свойства интерфейса:

Примечания:
• Они доступны только через переменные интерфейсного типа.
• К свойствам нельзя обращаться, используя переменные типа класса.
• Свойства интерфейса видимы только внутри программ, где интерфейс компилируется.
• СОМ-объекты не имеют свойств, а компоненты ActiveX- имеют.
• В компонентах ActiveX свойства делятся на несколько групп: основные, внешние, расширенные и дополнительные.
• Так как интерфейс не может иметь полей, команды свойств Read и Write должны обращаться только к методам. Команды хранения свойств не разрешены, но свойство-массив может быть объявлено как Default.

Конструкторы и деструкторы отвечают за существование объекта в памяти, т.е. выделяют память для экземпляра класса, затем и освобождают ее.

Конструктор — это специальный вид подпрограммы, присоединенный к классу. Его на-значение — создавать представителей (экземпляры) класса. Он ведет себя как функция, которая возвращает ссылку на вновь созданный экземпляр класса, т.е. на объект. Одновременно выделяется память для хранения значений полей экземпляра класса.

Деструктор — это специальная разновидность подпрограммы, присоединенной к классу. Его назначение заключается в уничтожении экземпляра класса, т.е. объекта и освобождении памяти, выделенной под экземпляр.

Синтаксис объявления конструкторов и деструкторов:

Type
=Сlass[)]
. . .
Constructor Имя конструктора>[( )]; [Override;]
Destructor [( )>; [Override;]
End;

Примечания:
• Объявляются конструкторы и деструкторы, как правило, в разделе Public класса.
• В классе может быть объявлено несколько конструкторов, однако чаще бывает один конструктор. Общепринятое имя для единственного конструктора Create.
• В одном классе может быть объявлено несколько деструкторов, но чаще бывает один деструктор без параметров (всегда!) с именем Destroy.
• За объявлением деструктора по имени Destroy следует указывать ключевое слово-директиву Override, разрешающее выполнение предусмотренных по умолчанию действий для уничтожения экземпляра объекта, если при его создании возникла какая-либо ошибка. Фактически Override переопределяет метод предка
• Метод Free так же удаляет (разрушает) экземпляры класса, предварительно проверяя их на Nil.

Реализация конструкторов

В задачу конструктора входит создание экземпляра класса и выполнение операторов, содержащихся в его теле. Назначение кода внутри конструктора — инициализировать только что созданный экземпляр объекта. Синтаксис реализации конструктора:

Constructor . [( >>;
[ >
Begin

End;

Реализация наследуемых конструкторов.

Constructor . [( )];
[ >
Begin
Inherited [( )>;

End;

Примечания:
• Следует убедиться, что для каждого поля в конструкторе предусмотрен оператор присвоения, и что все поля переходят из неопределенного состояния в какое-то конкретное, предусмотренное по умолчанию, хотя и известно, что транслятор сам выполнит инициализацию нулевыми значениями (», Nil, False — для других полей). Для неклассовых полей такая инициализация вполне допустима, поскольку позволяет их использовать в дальнейшем.
• Для вызова наследуемого конструктора следует использовать ключевое слово Inherited, которое фактически обеспечивает доступность перекрытого метода. Сила оператора в том, что он вызывает старое, а затем возвращается к новому.
• Как правило, следует вызывать подходящий наследуемый конструктор в первом исполняемом операторе.
• Только, если у пользовательского класса нет новых полей, можно не создавать для него конструктор.
• Хотя в объявлениях конструкторов и не указывается тип возвращаемого результата, и они выглядят, как объявления процедур, конструкторы используются скорее как функции, а не как процедуры. Можно сказать, что конструктор является неявной функцией — он возвращает нового представителя того класса, который использовался при его вызове. Нет необходимости в явном виде задавать тип возвращаемого результата, поскольку этот тип и так известен на момент вызова — это тип использованного в вызове конструктора класса.
• Внутри конструктора отсутствует и явное присвоение возвращаемого значения. Такое возможно, поскольку он всегда возвращает ссылку (адрес) на вновь созданный и инициализированный экземпляр класса.
• Начиная с класса TComponent конструктор Create стал виртуальным и при его переопределении необходимо указывать слово-директиву Override.

Реализация деструкторов

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

Синтаксис реализации деструктора:

Destructor . [( )];
[ ]
Begin

End;

Реализация наследуемых деструкторов
Если использовать механизм наследования деструкторов, то можно упростить задачу уничтожения экземпляров класса, таким образом, чтобы каждый раз заботиться лишь об уничтожении тех полей, которые были добавлены в данном классе. Всю работу по очистке наследуемых полей можно возложить на наследуемые деструкторы. Для вызова наследуемого деструктора необходимо используется ключевое слово Inherited.

Синтаксис объявления наследуемого деструктора следующий:

Destructor . [( )>;
[ ]
Begin

Inherited [ < )];
End;

Примечания:
• Внутри деструктора есть доступ не только к обычным идентификаторам, но и к полям экземпляра класса, инкапсулированным при его определении.
• Исполняемые операторы деструктора должны позаботиться обо всех операциях очистки, необходимых для уничтожения экземпляра класса. Код деструктора должен уничтожить все внутренние экземпляры объектов и освободить динамическую память, которая была зарезервирована во время существования экземпляра класса. Однако нет необходимости явно устанавливать в нулевые значения поля прямого доступа.
• Необходимость в объявлении деструкторов с параметрами возникает очень редко. Обычно все, что нужно сделать деструктору — уничтожить экземпляр класса, и вся необходимая для этого информация и так доступна ему.
• Если у класса нет полей косвенного доступа, то деструктор можно не создавать для такого пользовательского класса.

Вызов конструкторов

Для того чтобы вызвать конструктор, необходимо правильно объявить и определить класс. Объявление класса должно быть доступно, т.е. он должен находится в области видимости из того места, где конструктор будет вызываться.

Если в пользовательском классе не определен конструктор, то по умолчанию будет использоваться конструктор, унаследованный от класса-потомка. В любом случае у всех объектов есть доступ к конструктору Create, определенному в классе TObject.

Определение класса создает активную структуру, способную создавать представителей этого класса. Объекты, которые создаются с помощью определения класса, способны хранить ссылки на вновь создаваемые объекты.

Var : ; // Объявление переменной -указателя
Begin
:= . [( )];

• Если вызвать конструктор от имени объекта, то новый объект не будет создан (память не выделяется), но будут выполнены операторы, указанные в коде конструктора.
• Конструктор может также вызываться с помощью переменной типа указателя на класс.

Вызов деструкторов

Деструкторы вызываются точно так же, как и большинство других методов класса — через его действующий экземпляр.

Синтаксис вызова конструктора следующий:

Примечания:
• После вызова деструктора объект становится недоступен. Целесообразно присваивать объекту значение Nil сразу после его уничтожения, чтобы в дальнейшем можно было бы проверить его существование.
• Не следует вызывать деструкторы непосредственно. Вызов метода Free, наследуемого от TObject.Free, сравнивает указатель экземпляра со значением Nil перед тем, как вызвать деструктор Destroy.

Вызовы конструкторов и деструкторов визуальных компонентов Delphi. Любой компонент, попавший в ваше приложение при визуальном проектировании, включается в определенную иерархию объектов, которая замыкается на форме (класс TFonn). Поэтому вызов конструкторов и деструкторов всех компонентов формы производится автоматически при инициализации и удалении формы, незримо для программиста.

Сами формы создаются и уничтожаются приложением — глобальным объектом с именем Application. В файле-проекте с расширением *.Dpr можно увидеть вызов конструктора формы в виде строки:

Application.CreateForm(TForml, Fonnl);

Динамическое создание объектов. Пользователь может создать объект и программным путем:

Var Mem: TMemo;
Begin
Mem:=TMemo.Create(Self); // Создание экземпляра класса TMemo
Mem.Parent:=Self; // Form1 указывать не обязательно
Mem.Name:=’TmpMem’; // Присвоение имени компоненту
FindComponent(‘TmpMem’).Free; // Удаление компонента

Конструкторы - это специальные методы, создающие и инициализирующие объект класса. Объект создается выделением для него памяти в динамически распределяемой области памяти heap. Объявление конструктора выглядит так же, как объявление процедуры, но предваряется ключевым словом constructor. В качестве имени конструктора обычно задают имя Create.

В реализации конструктора обычно первым идет вызов наследуемого конструктора с помощью ключевого слова inherited. В результате инициализируются все наследуемые поля. При этом порядковым типам в качестве начального значения задается 0, указателям — nil, строки задаются пустыми. После вызова наследуемого конструктора в процедуре инициализируются новые поля, введенные в данном классе.

Не обязательно при создании класса объявлять его конструктор. Если он не объявлен, то автоматически в момент создания объекта вызовется конструктор родительского класса. Мы это видели в примере класса TPerson и его тестового приложения, созданных во второй части. Там мы обошлись без конструктора, и все работало нормально. Но давайте несколько расширим возможности нашего класса, и посмотрим, сможем ли мы обойтись без конструктора. Добавим в класс открытые поля AgeMin, AgeMax, обозначающие минимальную и максимальную границы допустимого возраста. Подобные границы полезны, если приложение использует наш класс для регистрации претендентов на какую-то должность при приеме на работу, или для регистрации абитуриентов при приеме в учебное заведение. Пусть при задании года рождения класс автоматически проверяет, удовлетворяет ли регистрируемая личность поставленным возрастным ограничениям. Для реализации этой идеи в объявлении класса надо произвести следующие изменения:

В оператор uses вводится ссылка на модуль DateUtils, В этом модуле объявлены функции, которые мы будем использовать при записи года рождения. В защищенный раздел класса protected вводится объявление процедуры записи года рождения. В открытый раздел класса вводятся переменные AgeMin и AgeMax. И изменяется объявление свойства Year: теперь в него вводится ссылка на процедуру записи SetYear.
Реализация процедуры записи SetYear может быть следующей:

В переменную NowYear заносится текущий год. Делается это так. Вызывается функция Date, которая возвращает текущую дату. Затем к этому результату применяется функция YearOf, которая выделяет из даты год. Функция YearOf объявлена в модуле DateUtils. Именно из-за нее этот модуль подключается предложением uses.

Возможно, вы уже заметили, почему все это пока не будет нормально работать. Если нет, введите описанные изменения в ваш класс и выполните тестовое приложение. Вы увидите, что можете занести в объект класса только того, кто родился в текущем году. А такой гражданин, пожалуй, слишком молод для приема на работу или учебу. Объясняется это просто: целые поля, такие как AgeMin и AgeMax, инициализируются нулевыми значениями. А нам нужны какие-то другие, более разумные возрастные рамки. Конечно, пользователь класса легко может этот недостаток устранить. Достаточно ввести, например, в обработчик события формы OnCreate после оператора создания объекта класса операторы вида:

Pers.AgeMax := 45;
Pers.AgeMin := 16;

Тем самым пользователь задает возрастные рамки для своего приложения. Ведь открытые поля AgeMin и AgeMax мы для того и вводили. Но хотелось бы, чтобы в случаях, если пользователю не требуется какой-то специфический возрастной диапазон, класс позволял бы работать с любыми реальными годами рождения. Но именно реальными, чтобы предотвратить случайную ошибку пользователя, когда он задаст, например, год рождения 3 или 3003.

Как указывалось ранее, задать начальные значения полей в их объявлениях невозможно. Вот для этого и служит конструктор класса. Добавьте в открытый раздел вашего класса объявление:

constructor Create;

А в раздел implementation добавьте реализацию конструктора:

Первый оператор вызывает с помощью ключевого слова inherited наследуемый конструктор родительского класса. А затем задаются начальные значения различных полей. Кроме задания максимального возраста 150 лет, тут исправляются еще некоторые недостатки нашего класса. Задается значение пола равное нулевому символу. По такому значению в дальнейшем можно проверять, был ли указан пол личности. И задаются строки «неизвестный» в качестве начального значения строковых нолей. Так что теперь, если какие-то данные о личности неизвестны, вместо них будет выдаваться этот текст, а не пустая строка. Думаю, что пользователю это будет удобно.

Теперь рассмотрим деструкторы. Это специальные методы, уничтожающие объект и освобождающие занимаемую им память. Деструктор автоматически вызывается при выполнении метода Free объекта класса. Если в вашем классе деструктор не объявлен, вызывается деструктор родительского класса.

В большинстве случаев объявлять деструктор в классе не требуется. И мы до сих пор прекрасно без него обходились. Деструктор нужен в тех случаях, когда в конструкторе или в каком-то методе класса создается объект, динамически размещаемый в памяти. Тогда нужен деструктор, чтобы уничтожить этот объект и освободить занимаемую им память. Аналогично деструктор требуется, если объект создает и хранит информацию в каких-то временных файлах. Тогда в деструкторе надо уничтожить эти файлы, чтобы они не оставались на диске.

Давайте введем в наш класс TPerson еще одно добавление, которое расширит его возможности. Создадим возможность хранить форматированный текст в формате .rtf, который может поступать, как вы знаете, из окна RichEdit. Но форматированный текст можно хранить или в объекте класса TRichEdit, или в файле, в который он записан методом SaveToFile свойства Lines окна RichEdit. Создать в нашем классе внутренний объект класса TRichEdit мы не можем. Так что остается вариант хранения во временном файле.
Добавьте в класс TPerson два поля:

FDoc: ^TRichEdit;
FileTMP: string;

Поле FDoc будет служить указателем на внешний компонент класса TRichEdit, из которого будет загружаться форматированный текст в файл, и в который будет грузиться текст из файла. А в переменной FileTmp будем хранить имя временного файла. Чтобы компилятор принял объявление поля FDoc, он должен понять идентификатор TRichEdit. Этот класс объявлен в модуле ComCtrls. Так что добавьте ссылку на этот модуль в предложение uses.
Введите в открытый раздел класса объявления двух процедур:

procedure SetDoc(var Value: TRichEdit); procedure GetDoc(var Value: TRichEdit);

Первая из них будет запоминать форматированный текст из окна RichEdit, переданного в нее как указатель на объект (вспомните смысл ключевого слова var). А вторая процедура будет заносить в окно, указанное аналогичным образом ее параметром, текст из файла. Реализация этих функций может иметь вид:

В процедуре SetDoc сначала проверяется, задавалось ли уже имя временного файла. Если нет, то это имя формируется из строки FName (фамилия, имя, отчество) и расширения .tmp. Затем переменной FDoc задается адрес компонента TRichEdit. А дальнейшее понятно: форматированный текст записывается в файл с именем, хранящимся в переменной FileTMP.

Добавьте в свое тестовое приложение окно RichEdit. Можете добавить также компонент FontDialog и обеспечить возможность форматирования текста в окне RichEdit. В обработчик щелчка на кнопке Запись вставьте оператор:

SetDoc(RichEditl);

А в обработчик щелчка на кнопке Чтение вставьте оператор:

GetDoc(RichEditl);

Выполнив тестовое приложение, можете убедиться, что ваш класс стал намного мощнее и может теперь хранить форматированный текст. Но он пока не совсем правильно оформлен. Если в приложении выполнялся вызов процедуры SetDoc, то был создан временный файл. И когда приложение завершается, этот файл остается на диске. Вам нужно написать деструктор, который удалял бы этот файл.

Объявление деструктора выглядит так же, как объявление процедуры, но предваряется ключевым словом destructor. В качестве имени деструктора обычно задают имя Destroy. Реализация деструктора, как правило, завершается вызовом наследуемого деструктора с помощью ключевого слова inherited, чтобы освободить память, отведенную для наследуемых полей.

В нашем случае в открытый раздел класса следует ввести объявление деструктора:

destructor Destroy; override;

Смысл ключевого слова override вы узнаете позднее. Пока просто напишите его, не задумываясь о его назначении. Реализация деструктора имеет вид:

Если файл создавался, то он уничтожается. Введение такого деструктора никак не отразится на внешнем поведении вашего тестового приложения. Но теперь вы не оставляете на диске временный и уже ненужный файл.

Следующий урок будет посвящён методам, наследованиям классов, операциям с классами.

Удачи!
Встретимся в следующем уроке!

Замечательные уроки для новичков 59(классы). 61 (свойства классов) воспроизводяться почти до конца. Но почти. Было бы очень желательно (для новичков опять же) привести законченый лучше зазипований проект в DElphi 7 на уровне информации 61 урока, что бы можно было сравнить работающий проект автора урока с проектом и процедурами собраными новичком на основе информации уроков 59-61 и найти ошибку. Заранее благодарен.

Фабрики классов используются для создания экземпляров объектов СОМ-объекты не порождаются приложением-клиентом. Вместо этого в СОМ используется механизм, названный генератором классов. Генераторы классов — это объекты, чье главное предназначение — создавать другие объекты. В Delphi фабрики классов для СОМ-объектов создаются автоматически.

Фабрики классов являются полноценными СОМ-объектами и доступ к ним также осуществляется через интерфейсы. Фабрики классов реализуют интерфейс IClassFactory, производного от интерфейса IUnknown и содержащего всего два дополнительных метода:
CreateInstance(Const unkOuter: IUnknown; Const IID: TGUID; Out Obj) — создает новый экземпляр класса, объекты которого может создавать данная фабрика.

LockServer(fLook: BOOL) — позволяет управлять загрузкой сервера в памяти, принудительно увеличивая (fLook=Trm) или уменьшая (fLook=False) счетчик ссылок.

Внутренние («in-process») серверы должны экспортировать стандартную функцию (DllGetClassObject), которая после запуска сервера создает экземпляр фабрики класса. Он с помощью метода Createlnstance и создает экземпляр объекта.

Локальные серверы также реализуют фабрику классов, и после запуска сервера регистрируют ее в таблице активных объектов, чтобы механизм peaлизации СОМ мог получить указатель на интерфейс IClassFactory и создать экземпляр объекта.

Первоначально клиент должен получить указатель (назовем его pClFr) на интерфейс IClassFactory, например с помощью функции CoGetClassObject.CoGetClassObject(Class Com, I, Nil, IID IClassFactory, pCIFr);

Механизм создания объекта с помощью фабрики класса
(после активизации самой фабрики классов) поясняется следующим рисунком:

Последовательность действий после получения указателя следующая:
• Вызывается метод Createlnstance интерфейса IClassFactory с передачей ему IID требуемого интерфейса и переменной-указателя (рСоm) на него.
pClFr.createInstance(Nil, IID, pCom);
• В ответ Фабрика классов создает (2) объект, получает указатель на заданный интерфейс и возвращает 3 этот указатель клиенту.
• Получив указатель рСом на интерфейс, реализуемый объектом, клиент Может вызывать (4) методы этого интерфейса,
для реализации фабрики классов в Delph. в модуле ComObj предусмотрен клacc TComObjectFactory, производный ОТ TObject И поддерживающий интерфейсы IUnknown, IClassFactory, IClassFactory2. СОМ-объект связывается со своей фабрикой класса С ПОМОЩЬЮ свойства Factory.

Система времени выполнения
Реализация СОМ включает и систему времени выполнения, поддерживающую структуру данных, известную как таблица активных объектов (Active Object Table). В этой таблице хранятся CLSID и указатели на фабрики классов активных объектов. Когда клиент вызывает функцию API CoGetClassObject, то система времени выполнения СОМ первоначально ищет CLSID в этой таблице, и, если в ней CLSID не находит, то ищет по заданному CLSID значение ключа LocalServer32 (с указанием пути до файла сервера) в реестре Windows и запускает сервер.

Читайте также: