Безопасный способ в Delphi для формы распространять объекты интерфейса, привязанные к его времени жизни?

У меня есть форма Delphi, которая обеспечивает функциональность объекта интерфейса, на которую другие части кода также получают ссылки через свойство, принадлежащее форме. Я не могу делегировать функциональность интерфейса дочернему объекту, потому что слишком большая часть этой функциональности обслуживается элементами управления / компонентами в форме. Я не могу использовать TAggregatedObject или TConolatedObject для привязки времени жизни интерфейсных объектов, передаваемых к форме, потому что класс TForm не наследуется от TinterfacedObject, а Delphi не поддерживает множественное наследование, поэтому я не могу смешивать TInterfacedObject в цепочке наследования . Эта ситуация может привести к нарушениям доступа, если форма уничтожается, в то время как какой-либо другой код содержит одну из ссылок на интерфейс, переданных этой формой. Кто-нибудь может придумать хорошее решение этой проблемы?


person Robert Oschler    schedule 10.10.2011    source источник


Ответы (2)


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

Вы можете использовать TAggregateObject или TContainedObject для своих нужд. Они не требуют, чтобы Форма производилась от TInterfacedObject. Все, что им требуется, это указатель интерфейса IInterface, а TComponent является производным от IInterface (и отменяет _AddRef() и _Release(), чтобы отключить подсчет ссылок), поэтому вы можете передать саму форму (являющуюся потомком TComponent) в качестве требуемого указателя IInterface.

Остается единственная проблема - закрытие формы, в то время как активные ссылки на интерфейс удерживаются другим кодом. Самое простое решение - либо 1) переписать этот код, чтобы не удерживать эти ссылки, пока форма закрывается, или 2) не позволять форме закрыться до тех пор, пока эти ссылки не будут освобождены.

person Remy Lebeau    schedule 10.10.2011
comment
Простите за поздний вопрос, но можете ли вы указать мне на хорошую ссылку, которая объясняет, когда использовать TContendedObject вместо TAggregatedObject? Я какое-то время смотрел на Delphi Help и не могу подробно описать различия в вариантах использования. - person Robert Oschler; 30.10.2011

Примечание: это будет работать, только если ваш потребитель также является производным от TComponent.

Чтобы избежать мертвых ссылок, вы можете запросить IInterfaceComponentReference (доступный на каждом TComponent) из вашей формы, вызвать GetComponent в этом интерфейсе и присоединиться к FreeNotification возвращенного компонента / формы.

Теперь происходит следующее: когда форма уничтожается, она уведомляет всех «листинговых», что она собирается уничтожить себя, вызывая метод Notification на потребителе с самим собой (формой) как AComponent и opRemove как операция. Таким образом, вы можете обнулить ссылку на интерфейс. Но имейте в виду, что ссылки на объекты и ссылки на интерфейсы не должны быть равными. Также не забудьте позвонить RemoveFreeNotification, когда вам больше не нужно Уведомление, чтобы избежать ненужных вызовов.

TSomeConsumer = class(TComponent)
private
  FInterfaceToAService: ISomeInterface;        
protected
  procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
  procedure SetService(const Value: ISomeInterface); 
end;

procedure TSomeConsumer.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent = TObject(FInterfaceToAService)) then
    SetService(nil); // Takes care of niling the interface as well.
end;

procedure TSomeConsumer.SetService(const Value: ISomeInterface);
var
  comRef: IInterfaceComponentReference;
begin
  if Supports(FInterfaceToAService, IInterfaceComponentReference, comRef) then
    comRef.GetComponent.RemoveFreeNotification(self);

  FInterfaceToAService := Value;

  if Supports(FInterfaceToAService, IInterfaceComponentReference, comRef) then
    comRef.GetComponent.FreeNotification(self);
end;
person Tobias R    schedule 11.10.2011
comment
Хороший! Я не знал об этом. Не могли бы вы немного расширить свое утверждение - но имейте в виду, что ссылки на объекты и ссылки на интерфейсы не должны быть равными.? - person Robert Oschler; 11.10.2011
comment
Причин несколько: а) вы можете создать собственную реализацию QueryInterface, которая может возвращать новый интерфейсный объект при каждом вызове. б) Во-вторых, вы можете делегировать интерфейс свойству (Win32), которое может снова создавать новый объект каждый раз. c) Реализация TObject.GetInterface добавляет к адресу interfaceOffset. (Но эти внутренние механизмы, к сожалению, недоступны нам.) Недавно представленный (Delphi 2010) интерфейс для литья объектов в основном выполняется путем запроса интерфейса маркера, который возвращает адрес входа объекта. - person Tobias R; 11.10.2011
comment
Что произойдет, если ссылки на объект и интерфейс будут равны, и какая самая распространенная ошибка программиста приводит к созданию этой нежелательной ситуации? - person Robert Oschler; 11.10.2011
comment
Я не могу вспомнить проблему, которая может возникнуть помимо ненужного вызова уведомления. Эта привязка происходит, когда вы подключаете / вставляете компоненты и элементы управления в delphi. Только то, что управление временем жизни выполняется неявно формой родитель / владелец, опуская явную FreeNotification регистрацию. Использование ссылки на интерфейс вместо TComponent - это немного больше накладных расходов. - person Tobias R; 11.10.2011