Динамическое приведение типа (делегирование) |
Динамическое приведение типа позволит реализовать такую интересную парадигму COM-идеологии, как делегирование. Т.е. передачу выполнения какой-то функциональности другому объекту.
Для поддержки динамического приведения типа, VIP-интерфейс реализует предопределённый OBJ-интерфейс IDynamicCast, описанный в заголовочном файле Source\SysObjIfc\IDynamicCast.vih из поставки средства разработки
. Динамическое приведение реализуется через вызов функции QueryInterface данного интерфейса.Динамическое приведение (т.е. вызов функции QueryInterface) происходит при попытке приведения ссылки к неимплементированному через implements Obj-интерфейсу. При этом компилятор проверку корректности приведения не производит. Аналогично система работает с obj-интерфейсами. Т.е. вначале определяется, какой vip-интерфейс имплементирует в данный момент данный obj-интерфейс, а затем отрабатывает вышеописанный алгоритм. В случае невозможности приведения (возврат NullRef) или абстрактной реализации функции QueryInterface генерируется исключение ExObjIfcNoImpl. Применение динамического и обычного приведения типов никак не отличается и записывается как:
<динамическое-приведение-типа> = <имя-типа> (<ссылка>);
В свою очередь, интерфейс, поддерживающий динамическое приведение типа, может быть делегирован другим интерфейсом. При такой схеме работы интересно знать, кто является обрамляющим ( т.е. делегирующим ) интерфейсом. Данная информация позволит динамически привести себя к любому интерфейсу из числа имплементированных объемлющим интерфейсом. Для этого заводится специализированная функция - метод интерфейса OuterInterface.
Функция OuterInterface вернёт ненулевое значение только в делегированных интерфейсах, т.е. в тех, которым был вызван Delegate.
Если освободить все ссылки на вышестоящий интерфейс, то в нижестоящем интерфейсе ссылка на него может остаться, и он реально не освободится (перекрёстные ссылки). Во избежание подобной ситуации запрещается присваивать значение функции OuterInterface каким-либо переменным.
Одновременно возникает проблема преждевременного освобождения ссылки и, как следствие, выгрузки вышестоящего интерфейса, что делает невозможным использование OuterInterface в делегированных объектах.
Все делегируемые ссылки должны быть проинициализированы в том интерфейсе, откуда происходит делегирование. Т.е. они не могут быть получены из других интерфейсов.
В качестве примера рассмотрим следующую схему работы:
Рис. 5 Иллюстрация проблемы подсчёта ссылок на внешний интерфейс
Интерфейс-клиент получил ссылку типа IObj1 на Interface_1 (ссылка "a"). В процессе приведения к IObj2, Interface_1 делегировал клиенту функциональность IObj2, передав ссылку на Interface_2 (ссылка "b"). После освобождения ссылки "a" Interface_1 выгружается и использование OuterInterface внутри Interface_2 невозможно. Следовательно, приведение ссылки "b" к IObj1 не пройдёт.
vipInterface Interface_1 implements IDynamicCast, IObj1 ; vipInterface Interface_2 implements IDynamicCast, IObj2 ; vipInterface Interface_3 implements IDynamicCast, IObj3 ; interface Interface_1; ... var v2: Interface_2; v3: Interface_3; ... function IDynamicCast.QueryInterface (strObjName : string ) : ObjRef; { Result := NullRef; case strObjName of 'IOBJ2': Result := IObj2(v2); 'IOBJ3': Result := IObj3(v3); end; } ... end. interface Interface_2; ... function IDynamicCast.QueryInterface (strObjName : string ) : ObjRef; { Result := NullRef; if (OuterInterface <> NullRef) // Меня делегировали ? { case strObjName of 'IOBJ1': Result := IObj1(OuterInterface); end; } } ... end. interface Interface_С; ... var v1: Interface_1; o1: IObj1; o2: IObj2; ... o2 := IObj2(v1); // Здесь произойдёт делегирование v1 := NullRef; // Выгрузка Interface_1 o1 := IObj1(o2); // Ничего не выйдет ... ... end.
Для обеспечения "прозрачной" работы клиента с делегированными ссылками необходимо явное указание того, что ссылка делегируется. Это обеспечит правильный подсчёт ссылок для внешнего интерфейса с учётом делегированных ссылок. Указание делегирования производится вызовом функции Delegate.
Именно результат функции Delegate (приведённый к нужному типу) следует возвращать из QueryInterface. Делегировать OuterInterface не нужно.
Таким образом, реализация QueryInterface в Interface_1 принимает вид:
interface Interface_1; ... function IDynamicCast.QueryInterface (strObjName : string ) : ObjRef; { Result := NullRef; case strObjName of 'IOBJ2': Result := IObj2(Delegate(v2)); 'IOBJ3': Result := IObj3(Delegate(v3)); end; } ... end.
Реализация функции QueryInterface из Interface_2 не совсем удобена, т.к. она явно "закладывается" на особенности делегирующего интерфейса. Для снятия данного ограничения существует функция DynamicCast. Окончательно функция QueryInterface из Interface_2 должна выглядеть так:
... function IDynamicCast.QueryInterface (strObjName : string ) : ObjRef; { Result := NullRef; if (OuterInterface <> NullRef) // Меня делегировали? Result := DynamicCast(OuterInterface, strObjName); } ...
Фактически, функция QueryInterface из Interface_2 не выполняет динамического приведения типа и реализована только для правильной работы делегированной ссылки. Поэтому у интерфейсов, не поддерживающих динамическое приведение типа, автоматически выполняются аналогичные действия. Таким образом обеспечивается "прозрачность" как для делегированной ссылки так и для интерфейса, запрашивающего функциональность.