Динамическое приведение типа (делегирование) Назад В начало Вперед

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

Оглавление раздела

Интерфейс IDynamicCast. Функция QueryInterface

Функция OuterInterface

Функция Delegate

Функция DynamicCast

Пример