Программирование на языке Delphi. Глава 6.
От: А.Н. Вальвачев, К.А. Сурков, Д.А. Сурков  
Дата: 11.12.05 07:49
Оценка: 112 (9)
Статья:
Программирование на языке Delphi. Глава 6. Интерфейсы
Автор(ы): А.Н. Вальвачев, К.А. Сурков, Д.А. Сурков, Ю.М. Четырько
Дата: 08.12.2005
При программировании нередко возникает необходимость выполнить обращение к объекту, находящемуся в другом загрузочном модуле, например EXE или DLL. Для решения поставленной задачи компания Microsoft разработала технологию COM (Component Object Model) — компонентную модель объектов. Технология получила такое название благодаря тому, что обеспечивает создание программных компонентов — независимо разрабатываемых и поставляемых двоичных модулей. Поскольку объекты различных программ разрабатываются на различных языках программирования, например Delphi, C++, Visual Basic и др., технология COM стандартизирует формат взаимодействия между объектами на уровне двоичного представления в оперативной памяти. Согласно технологии COM взаимодействие между объектами осуществляется посредством так называемых интерфейсов. Рассмотрим, что же они собой представляют и как с ними работают.


Авторы:
А.Н. Вальвачев, К.А. Сурков, Д.А. Сурков, Ю.М. Чет

Аннотация:
При программировании нередко возникает необходимость выполнить обращение к объекту, находящемуся в другом загрузочном модуле, например EXE или DLL. Для решения поставленной задачи компания Microsoft разработала технологию COM (Component Object Model) — компонентную модель объектов. Технология получила такое название благодаря тому, что обеспечивает создание программных компонентов — независимо разрабатываемых и поставляемых двоичных модулей. Поскольку объекты различных программ разрабатываются на различных языках программирования, например Delphi, C++, Visual Basic и др., технология COM стандартизирует формат взаимодействия между объектами на уровне двоичного представления в оперативной памяти. Согласно технологии COM взаимодействие между объектами осуществляется посредством так называемых интерфейсов. Рассмотрим, что же они собой представляют и как с ними работают.
Re: Программирование на языке Delphi. Глава 6.
От: Slicer [Mirkwood] Россия https://ru.linkedin.com/in/maksim-gumerov-039a701b
Дата: 11.12.05 13:26
Оценка: +2
>При программировании нередко возникает необходимость выполнить обращение к объекту, находящемуся в другом загрузочном модуле, например EXE или DLL. Для решения поставленной задачи компания Microsoft разработала технологию COM (Component Object Model) — компонентную модель объектов.

Рекомендую выразиться в том духе, что не "в другом загрузочном модуле", а "в чужом программном продукте". И привести пример с открытием скачиваемых doc-файлов прямо внутри Internet Explorer (мне кажется, это нагляднее, чем демонстрировать выгрузку данных в Excel — это, в конце концов, можно и через файловый ввод-вывод проделать).

>Интерфейс = Объект – Реализация

Очень странная формула. Тогда уж " = класс — реализация". А еще правильнее "= видимые извне составляющие объекта". В том числе, кстати, и поля (если трактовать "интерфейс" более широко, чем в COM). Но и это не отражает смысл понятия. Может, не стоит давать формулу, которая не помогает понять явление? Лучше бы начать разговор с того, что интерфейс объекта — это то, что представляет собой объект для внешнего по отношению к нему кода. То есть это операции, которые может вызывать внешний код, и данные, доступ к которым он имеет. Интерфейс не обязательно включает все видимые стороны объекта: любое подмножество интерфейса объекта также является его интерфейсом, только суженным, более специализированным. Например, если есть определенный класс пользователей, которым у данного объекта интересна только возможность сохраняться в поток, им не нужен для работы полный интерфейс объекта: для их работы им достаточно иметь доступ к такому интерфейсу этого объекта, в котором есть метод для записи его в поток. Это позволяет не перегружать внешних партнеров объекта лишней информацией о нем.

В Delphi используется принятое в COM определение интерфейса (в отличие, скажем, от Java). Интерфейс может содержать методы и свойства, но не поля.................................. и далее по вкусу.

А то объяснение, которое присутствует в книге сейчас — объясняет единичный прием, но оставляет за бортом принцип, парадигму.

> IntfReader := Intf as ITextReader; // Intf.QueryInterface(ITextReader, IntfReader);

Не совсем так. Сначала идет проверка на nil, поэтому as безопасно применять к нулевым указателям.

Slicer
Специалист — это варвар, невежество которого не всесторонне :)
Re: Программирование на языке Delphi. Глава 6.
От: Slicer [Mirkwood] Россия https://ru.linkedin.com/in/maksim-gumerov-039a701b
Дата: 11.12.05 15:44
Оценка: 8 (1)
Продолжаю.

Во-первых, нет ни слова о TObject.GetInterface, а он иногда очень удобен (поскольку определен в TObject и, следовательно, может применяться для взятия интерфейса у объекта, даже когда на объект есть только ссылка по классу, причем точный класс неизвестен. Попробуйте-ка без GetInterface запросить у параметра Obj, переданного в вашу функцию как TObject, интерфейс IUnknown!

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

Конкретно, нет ни слова об особенностях реализации интерфейсов в производных классах. Для начала возьмем очень простой пример с IUnknown. Далее пойдет выпендреж в духе Элджера, только в варианте для Дельфей

IA = interface (IUnknown) //всегда неявно наследуется
  procedure Foo();
end;

TC = class (TInterfacedObject, IA):


Вот как выглядит VMT класса TC:

.... (какие-то методы TInterfacedObject или TC)
IA.QueryInterface = IUnknown.QueryInterface = TInterfacedObject.QueryInterface
IA._AddRef = IUnknown._AddRef = TInterfacedObject._AddRef
IA._Release = IUnknown._Release = TInterfacedObject._Release
IA.Foo = TC.Foo
.... (еще какие-то методы TInterfacedObject или TC)


Не будем сейчас затрагивать размещение методов каждого класса, главное — указатели на методы, реализующие каждый интерфейс, идут подряд. Таково требование COM (иначе нельзя было бы предугадать, по какому смещению в VMT находится указатель на некоторый метод известного интерфейса).

Видно, что часть VMT, относящаяся к интерфейсу IA, перекрыла часть, относящуюся к IUnknown. Но что, если мы введем еще и интерфейс

IB = interface (IUnknown)
  procedure Bar();
end;


и напишем TC = class (TInterfacedObject, IA, IB) ?

Требование неразрывности VMT интерфейса означает, что IB не может начинаться с того же адреса, что и IUnknown и IA — ведь он не наследован от IA, и в нем нет метода A.Foo. В этом случае один из интерфейсов (допустим, IA) может по-прежнему располагаться поверх IUnknown, но для второго компилятору приходится заводить новые элементы под его методы, унаследованные от IUnknown:

.... (какие-то методы TInterfacedObject или TC)
IA.QueryInterface = IUnknown.QueryInterface = TInterfacedObject.QueryInterface
IA._AddRef = IUnknown._AddRef = TInterfacedObject._AddRef
IA._Release = IUnknown._Release = TInterfacedObject._Release
IA.Foo = TC.Foo
.... (еще какие-то методы TInterfacedObject или TC)
IB.QueryInterface = TInterfacedObject.QueryInterface
IB._AddRef = TInterfacedObject._AddRef
IB._Release = TInterfacedObject._Release
IB.Bar = TC.Bar
.... (еще какие-то методы TInterfacedObject или TC)


Посмотрим теперь, что происходит, когда я делаю присванивание

Var 
   iptr: IUnknown;
   oc: TC;
begin
   oc := TC.Create();
   iptr := oc;
end;

Указатель на какую часть VMT окажется в iptr? И IA, и IB можно рассматривать как IUnknown — ведь они от него наследуются, а потому с точки зрения Delphi такая операция присваивания является корректной. Встает выбор — попадет ли в iptr указатель на ту часть VMT, которая относится к IUnknown, или к IA, или к IB? Хорошо, благодаря трюку с наложением фрагментов VMT оказывается, что фрагменты для IA и IUnknown находятся по одному и тому же адресу, но ведь есть еще и IB! Его фрагмент VMT начинается с другого адреса. Так на что будет указывать iptr — на часть VMT, начинающуюся с IA.QueryInterface (или, что то же самое, на IUnknown.QueryInterface), или с IB.QueryInterface? Точно ответить на этот вопрос я не берусь, поскольку это не документировано официально.

У этой проблемы (с тем, что разрешено неявное преобразование интерфейсов) есть и другая сторона: когда я присваиваю указателю на интерфейс-предок значение указателя на интерфейс-потомок, компилятор считает эту операцию корректной (с точки зрения COM это не обязательно так!), и потомок после этого ссылается на ту же часть VMT объекта, что и предок (потому что методы потомка всегда добавляются после методов предка). Отсюда абсурдное следствие:

Var 
   iptrA: IA;
   iptrB: IB;
   iptr1, iptr2: IUnknown;
   oc: TC;
begin
   oc := TC.Create();
   iptrA := oc; //Однозначно взят указатель на блок, реализующий IA
   iptrB := oc; //Однозначно взят указатель на блок, реализующий IB
   iptr1 := iptrA; //Не имея никакой информации об объекте, на который указывает iptrA, кроме 
                     //родственной связи интерфейсов IUnknown и IA, компилятор просто берет для 
                     //iptr1 указатель на ту часть VMT интерфейса (на который указывает iptrA), 
                     //где находятся методы IUnknown - то есть на начало. Т.е. по факту iptr1 = iptrA 
   iptr2 := iptrB; //А здесь берется указатель на начальную часть VMT по адресу iptrB, и iptr2 = iptrB
   //Получается, что iptr1 <> iptr2 !
end;

Все бы еще было не так ужасно (в конце концов, в нашем случае методы через iptr и iptr2 будут вызываться одни и те же!), но попробуйте сравнить iptr и iptr2! Правильно, скорее всего (если компилятор сработает так, как я написал) их значения будут отличаться. Как минимум, это означает, что если сравниваются два интерфейсных указателя (даже на один и тот же интерфейс — в смысле одинакового типа указателей, например, оба IUnknown), результат может не соответствовать реальности. Как в нашем примере: iptr1 и iptr2 указывают на один объект, а адреса отличаются.

С этой проблемой рано или поздно сталкивается всякий, кто использует класс TInterfaceList, который ищет в себе указатель на объект путем сравнения его со всеми содержащимися в нем указателями. Это поможет выснить, добавляли ли уже в список данный указатель, но не скажет нам, добавляли ли уже в список какой-либо указатель на данный объект — ведь на тот же объект мог быть добавлен другой указатель.

Как победить неоднозначность взятия указателя? Для этого вполне достаточно выполнять приведение явно, при помощи операции as или метода QueryInterface: оба способа запрашивают интерфейс по его идентификатору. В этом случае возвращается указатель на ту часть VMT, которая относится к последней реализации именно данного интерфейса.
То есть, например, при вычислении "oc as IUnknown" будет взята та часть VMT, которая относится к определенной еще в TInterfacedObject реализации IUnknown.

Внимание! В расчет берутся только явные реализации интерфейса. Если IDerived унаследован от IBase, а класс TMyObject реализует IDerived, это вовсе не означает, что у него можно опросить интерфейс IBase! QueryInterface или as, в отличие от неявного приведения, вернут ошибку: они считают, что экземпляр TMyObject не поддерживает интерфейс IBase.

---

Остается та же проблема сравнения, но уже для удаленных объектов. Если объект находится на другом компьютере, или даже просто в другом процессе (значит, в другом адресном пространстве), то наши интерфейсные указатели указывают не на сам объект, а на различные его заместителя (proxy), а заместителей у одного объекта может быть и несколько.

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

---

Далее, as создает стековую переменную, которая очищается только после выхода из функции, в которой as применен — поэтому какое-то время (до выхода из функции) на объект будет болтаться "лишняя" ссылка. Пусть это не будет для вас неожиданностью. И не вздумайте удалять вручную (Free, Destroy) объект, если на него висит такая ссылка (в этой функции или в той, которая эту вызвала) — при автовызове _Release для очистки этой ссылки произойдет обращение к удаленному объекту! И вообще, лучше не смешивать доступ к объекту через интерфейсы и через классы (или нужно точно представлять себе, на что идешь, и делать это в небольших фрагментах программы).

---

Еще одни подводные грабли связаны с сочетанием неявного приведения и "оптимизации" передачи const-интерфейсов. Const применительно к интерфейсам имеет особый смысл, причем упоминание об этом в хелпе так сразу и не найти. Речь идет о том, что процедура не должна менять счетчик ссылок const-параметра — не только в сумме за все время своей работы, но вообще ни разу. Причем ограничение-то есть, а вот отслеживать в compile-time его выполнение компилятор не считает нужным!

Пусть имеем процедуру
procedure Eliminate(const Victim: IUnknown);
var
  Person: IPerson;
begin
  if S_OK = Victim.QueryInterface(IPerson, Person) 
    then Murder(Person)
    else Kill(Victim);
  Person := nil; //отпустим ненужную больше ссылку
  Bury(Victim);
end;

И вызываем ее так:
Eliminate(TPerson.Create());


Как ни странно, скорее всего этот вызов приведет к ошибке времени исполнения, если экземпляр TPerson поддерживает IPerson! Почему? А потому, что при передаче созданного объекта как const-параметра компилятор не станет повышать ему счетчик ссылок: зачем, раз процедура его все равно ни разу не меняет. А на деле процедура сперва поднимает счетчик на QueryInterface, затем уменьшает на Person := nil — и, т.к. счетчик изначально был 0, он снова становится 0 и объект уничтожается. Понятно, как это может отразится на вызове Bury().

Slicer
Специалист — это варвар, невежество которого не всесторонне :)
Re[2]: Программирование на языке Delphi. Глава 6.
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 11.12.05 16:46
Оценка:
Здравствуйте, Slicer [Mirkwood], Вы писали:

свои пару копеек http://www.rsdn.ru/Forum/Message.aspx?mid=527260&amp;only=1
Автор: Serginio1
Дата: 02.02.04
и солнце б утром не вставало, когда бы не было меня
Re[3]: Программирование на языке Delphi. Глава 6.
От: Slicer [Mirkwood] Россия https://ru.linkedin.com/in/maksim-gumerov-039a701b
Дата: 11.12.05 17:46
Оценка: +1
Здравствуйте, Serginio1, Вы писали:

S>Здравствуйте, Slicer [Mirkwood], Вы писали:


S>свои пару копеек http://www.rsdn.ru/Forum/Message.aspx?mid=527260&amp;only=1
Автор: Serginio1
Дата: 02.02.04

Кстати, можно упомянуть (если еще нет этого), что при взятии делегированного интерфейса счетчик ссылок повышается не у агрегированного объекта, а у агрегата. Впрочем, это логично... А вот о чем стоит написать (правда, может, позже по тексту) — это о TAggregatedObject (или как там его) и о правильной организации делегирования интерфейсов (какие требования предъявляет COM и просто логика, типа обратимости взятия интерфейса и тп)

Slicer
Специалист — это варвар, невежество которого не всесторонне :)
Re: Делегаты и события
От: Spaider Верблюд  
Дата: 12.12.05 08:43
Оценка: 1 (1)

все атрибуты интерфейсов являются общедоступными (public);


Мне кажется, более точным будет утверждать, что методы интерфейса вообще не имеют спецификатора доступа.
И это логично, объявив интерфейс, мы только декларируем поведение, не привязывая его к конкретной реализации
--
К вашим услугам,
Re[2]: Извините за изменение темы
От: Spaider Верблюд  
Дата: 12.12.05 08:45
Оценка:
Прощу прощения за изменение темы, Opera проявила инициативу и авто-заполнила больше, чем надо было
--
К вашим услугам,
Re[2]: Делегаты и события
От: Slicer [Mirkwood] Россия https://ru.linkedin.com/in/maksim-gumerov-039a701b
Дата: 12.12.05 15:48
Оценка:
Здравствуйте, Spaider, Вы писали:

S>

все атрибуты интерфейсов являются общедоступными (public);

S>Мне кажется, более точным будет утверждать, что методы интерфейса вообще не имеют спецификатора доступа.
Ну, это кто-то может оспорить, а насчет фразы я бы предложил оставить "Общедоступные", но убрать "public". А то еще подумает народ, что методы, реализующие интерфейсы, должны быть public (ведь в сам интерфейс public воткнуть некуда).

Slicer
Специалист — это варвар, невежество которого не всесторонне :)
Re[3]: Делегаты и события
От: Аноним  
Дата: 12.12.05 15:56
Оценка:
Здравствуйте, Slicer [Mirkwood], Вы писали:

SM>... я бы предложил оставить "Общедоступные", но убрать "public". А то еще подумает народ, что методы, реализующие интерфейсы, должны быть public (ведь в сам интерфейс public воткнуть некуда).


Согласен, слово public может вызвать неверное толкование.
Re: Программирование на языке Delphi. Глава 6. Вопрос!!!
От: Bazunoff  
Дата: 19.09.06 08:45
Оценка:
Вопрос!!!

Кто за что отвечает?
Кто подкован в этом деле на Дельфях, объясните плиз,
что же происходит с памятью...

У меня есть небольшая практика программирования COM серверов,
только не на реальных проектах, а по книжке COM and ATL 3.0 на С++.
На С++ все очень прозрачно и понятно. Мне, как программисту дали
все винтики и болтики, чтобы управлять памятью и т.д.

Пробуем на Дельфи:

Файл 1: IWriterIntf.pas


unit IWriterIntf;

Interface

const
  IID_IWriter      : TGUID = '{F6F54C8B-F2F8-406C-908B-76F9B44A9AC3}';
  IID_IFileCreator : TGUID = '{2344030B-9904-4070-8965-BC2FE03A70FA}';

type

  IWriter = interface(IInterface)
    ['{F6F54C8B-F2F8-406C-908B-76F9B44A9AC3}']
    function WriteHello : HResult;
    function WriteDate  : HResult;    
  end;

  IFileCreator = interface(IInterface)
    ['{2344030B-9904-4070-8965-BC2FE03A70FA}']
    function createfile(sFileName : string) : HResult;
    function deletefile(sFileName : string) : HResult;
  end;
  

Implementation

End.


.. здесь все понятно, определили два интерфейса, дали им номерки, все чин по чину.
этот файл будет использоваться и клиентом и сервером.

Файл 2: WriteLib.pas

library writerlib;

uses
  SysUtils, Classes, IWriterIntf in 'IWriterIntf.pas';

type

  TCoWriter = class(TInterfacedObject, IWriter, IFileCreator)
    function WriteHello : HResult;
    function WriteDate  : HResult;    
    function createfile(sFileName : string) : HResult;
    function deletefile(sFileName : string) : HResult;
  end;

  function TCoWriter.WriteHello : HResult;
  begin
    writeln('hello');
    result := S_OK;
  end;

  function TCoWriter.WriteDate  : HResult;    
  begin
    writeln(DateToStr(now));
    result := S_OK;
  end;

  function TCoWriter.createfile(sFileName : string) : HResult;
  var fs : TfileStream;
  begin
    fs := TFileStream.create(sFileName, fmCreate);      
    fs.Free;
    result := S_OK;
  end;

  function TCoWriter.deletefile(sFileName : string) : HResult;
  begin      
    result := S_OK;
  end;

  function GetWriter : IWriter;
  begin
    result := TCoWriter.Create;
  end;

exports
  GetWriter;

begin
end.


... ну во-первых, это библиотека, в которой содержится реализация СоСласса TCoWriter,
который в свою очередь, содержит реализацию моих интерфейсов... (юнит с интерфейсами — в uses).

Прикинемся шлангом...
TCoWriter наследутеся от TInterfacedObject, который содержит в себе реализацию IInterface (IUnknown),
и мне нет надобности писать реализацию самому, а точнее нет возможности.
Я не могу контролировать подсчет ссылок, создание и удаление объекта как в С++.
ОК. Оставляем эту работу компилятору. Этого видимо добивались разработчики Дельфи.

Также библиотека экспортирует (проверено dumpbin'ом ) одну функцию GetWriter,
которая... Что делает? Создает экземпляр класса (объект) TCoWriter и возвращяет указатель
на него.

После компиляции dcc32 writerlib.pas получаем writerlib.dll, которую и будет использовать клиент test.pas
И так Клиент:

Файл 3: test.pas

program test;

{$APPTYPE CONSOLE}

uses IWriterIntf in 'IWriterIntf.pas';  // подключаем юнит с интерфейсами


  function GetWriter : IWriter; external 'writerlib.dll' name 'GetWriter'; 
  // прототип функции GetWriter, объявляем ее внешней

var

  iw : IWriter;      // 4 bytes
  fc : IFileCreator; // 4 bytes

begin

  iw := GetWriter; 
  // что здесь происходит?
  //  - загружается библиотека, (или она вместе с exe"шником грузится сразу?)
  //  - вызывается эксп-функция, т.е. в адресном пространстве библиотеки, "аллокируется"
  //  память под объект класса TCoWriter - так?
  //  - ну и наверное здесь происходит первый AddRef?

  iw.WriteHello;   // вызываем методы интерфейса IWriter
  iw.WriteDate;

  // запрашиваем другой интерфейс IFileCreator в fc
  if( iw.QueryInterface(IID_IFileCreator,fc) = S_OK ) then // ну здесь то точно AddRef! refCount = 2 как минимум!
  begin
    fc.createfile('test2.txt'); //вызываем методы этого интерфейса
    fc := nil;                  // больше не нужен - удаляем ссылку с интерфейса
    // вот здесь уже не понятно, что происходит.
    // Вызывается Release? Ок, значит в "TCoWriter.refcount" лежит единичка?
  end;

  // далее...
  // а можно вот так запросить IFileCreator из IWriter
  with iw as IFileCreator do // здесь опять AddRef 100%, TCoWriter.refCount = 2
  begin
    createfile('test.txt'); // вызываем методы IFileCreator
  end;

  iw := nil; // обнуляем ссылку - здесь дельфи вызовет Release
             // НО! в RefCount останется 1, ??
             // Как здесь вообще управлять памятью??
             // Кто здесь позаботится об удалении объекта TCoWriter?

end. // а здесь вообще библиотека выгружается?
     // в стандартном виде - СОМ менеджер памяти вызывает
     // спец функцию, експортируемую библиотекой, 
     // DllCanUnloadNow которая все знает про кол-во ссылок на
     // интерфейсы и кол-во Locks. А здесь как?


Ну вот мои мысли по этому поводу в коментариях.
Помогите разобраться с кашей на дельфях...
Re[2]: Программирование на языке Delphi. Глава 6. Вопрос!!!
От: Danchik Украина  
Дата: 19.09.06 10:30
Оценка:
Здравствуйте, Bazunoff, Вы писали:

B>Вопрос!!!


B>Кто за что отвечает?

B>Кто подкован в этом деле на Дельфях, объясните плиз,
B>что же происходит с памятью...

B>У меня есть небольшая практика программирования COM серверов,

B>только не на реальных проектах, а по книжке COM and ATL 3.0 на С++.
B>На С++ все очень прозрачно и понятно. Мне, как программисту дали
B>все винтики и болтики, чтобы управлять памятью и т.д.

B>Пробуем на Дельфи:


B>Файл 1: IWriterIntf.pas


[Skip]

B>Файл 2: WriteLib.pas


[Skip]

Первое что бросается в глаза из запрещенного — STRING.
Строки в Delphi менеджерятся через внутренний менеджер памяти. В DLL и EXE они будут разными и в какой-то прекрасный момент память выделенная одним менеджером будет освобождена другим — Invalid Pointer Operation. Посмотрите по форуму об ShareMem (больно заезжено чтобы еще раз разжевывать).
Но как выход можна использовать WideString — для него аллокация происходит через SysAllocStringLen

B> Файл 3: test.pas


B>
B>program test;

B>{$APPTYPE CONSOLE}

B>uses IWriterIntf in 'IWriterIntf.pas';  // подключаем юнит с интерфейсами


B>  function GetWriter : IWriter; external 'writerlib.dll' name 'GetWriter'; 
B>  // прототип функции GetWriter, объявляем ее внешней

B>var

B>  iw : IWriter;      // 4 bytes
B>  fc : IFileCreator; // 4 bytes

B>begin

B>  iw := GetWriter; 
B>  // что здесь происходит?
B>  //  - загружается библиотека, (или она вместе с exe"шником грузится сразу?)
Да загрузилась вместе с EXE
B>  //  - вызывается эксп-функция, т.е. в адресном пространстве библиотеки, "аллокируется"
B>  //  память под объект класса TCoWriter - так?
Да
B>  //  - ну и наверное здесь происходит первый AddRef?
Точно

B>  iw.WriteHello;   // вызываем методы интерфейса IWriter
B>  iw.WriteDate;

B>  // запрашиваем другой интерфейс IFileCreator в fc
B>  if( iw.QueryInterface(IID_IFileCreator,fc) = S_OK ) then // ну здесь то точно AddRef! refCount = 2 как минимум!
Удобнее пользоваться Supports
  if Supports (iw, IID_IFileCreator, fc) then 
B>  begin
B>    fc.createfile('test2.txt'); //вызываем методы этого интерфейса
B>    fc := nil;                  // больше не нужен - удаляем ссылку с интерфейса
B>    // вот здесь уже не понятно, что происходит.
B>    // Вызывается Release? Ок, значит в "TCoWriter.refcount" лежит единичка?
Правильно
B>  end;

B>  // далее...
B>  // а можно вот так запросить IFileCreator из IWriter
B>  with iw as IFileCreator do // здесь опять AddRef 100%, TCoWriter.refCount = 2
B>  begin
B>    createfile('test.txt'); // вызываем методы IFileCreator
B>  end;

B>  iw := nil; // обнуляем ссылку - здесь дельфи вызовет Release
Это не обязательно, даный код автоматически вкомпилирован на выходе из процедуры в неявной try finally секции
B>             // НО! в RefCount останется 1, ??
B>             // Как здесь вообще управлять памятью??
B>             // Кто здесь позаботится об удалении объекта TCoWriter?
Вот тут я не уверен, с with у меня были проблемы, иногда Release не вызывается. Иногда это в initialization секции (я три часа искал memory leak). В процедурах все гуд.
Все дело в том что компилятор для вызова iw as IFileCreator создает неявную локальную переменную которую при выходе из процедуры обнуляет (вызывается Release)

B>end. // а здесь вообще библиотека выгружается?
B>     // в стандартном виде - СОМ менеджер памяти вызывает
B>     // спец функцию, експортируемую библиотекой, 
B>     // DllCanUnloadNow которая все знает про кол-во ссылок на
B>     // интерфейсы и кол-во Locks. А здесь как?
B>

Нет тут не выгрузится. Вам нужна динамическая загрузка LoadLibrary, GetProcAddress, FreeLibrary
Re[3]: Программирование на языке Delphi. Глава 6. Вопрос!!!
От: Базунов Сергей  
Дата: 19.09.06 12:19
Оценка:
Из Ваших поправок делаю вывод:

*) библиотеку лучше подгружать динамически, дабы оставить за собой возможность выгружать ее самому
(потому что никто этого не делает в итоге)

*) Не использовать при запросах интерфейса оператор as, потому что, есть неоднозначности в результиру
ющем коде.

*) При запросах интерфейса использовать специальный набор функций (для перестраховщиков), чтобы если что то не
работало — НЕ СКАЗАЛО об этом ala Supports

*) Использование intf := nil; остается на свой страх и риск. Потому что, то, как автоматизирован подсчет
ссылок знают только сами разработчики компилятора, а может и они уже совневаются...?

*) Использование интерфейсов при всех ограничениях сводиться к дебилизму (запросил — обнулил), я имею ввиду —
никакой динамики, ни какой оптимизации.

*) Все, так называемые "удобства" становятся неуправляемыми местами для багов. Все приходится делать "ручками".

Если я, точно знаю, где мне нужно аллокировать а где отпустить память, почему мне не оставили возможность
делать это. При явном вызове Release вылетают Access violation и его братья по разуму.

А как бы было сдорово аля...


var

  iw : IWriter

begin

  iw := GetInterface(IID_IWriter...);

  iw.Method1();
  iw.Method2();

  iw.QueryInterface(IID_IFileCreator,iw);

  // здесь я знаю, что мне не понадобяться методы IWriter
  iw.Release; // refCount := refCount - 1;

  iw.OtherMethod();
  iw.OtherMethod();

  iw.Release(); // refCount = 0;

  // здесь я точно знаю что TCoWriter удалится
  // и мне нельзя использовать iw...

end.


Наверное программирование на основе интерфейсов имеет смысл только в больших проектах,
где ЧерТ нОгУ слоМиТ, кто кого использует и кто что подгружает и выгружает. А на таких
маленьких задачах, это даже кажется абсурдом и простой глупостью (темболее на Дельфях).

Как же быть? Писать совю реализацию пары IUnknown/IInterfacedObject?
Где можно будет оставить такие важные моменты как подсчет ссылок программеру...
Но ведь у компилятора не выключишь эту фичу...
Re[4]: Программирование на языке Delphi. Глава 6. Вопрос!!!
От: Danchik Украина  
Дата: 19.09.06 15:19
Оценка:
Здравствуйте, Базунов Сергей, Вы писали:

БС>Из Ваших поправок делаю вывод:


БС>*) библиотеку лучше подгружать динамически, дабы оставить за собой возможность выгружать ее самому

БС> (потому что никто этого не делает в итоге)

Это как душе угодно. Для плагинов именно так и надо поступать.

БС>*) Не использовать при запросах интерфейса оператор as, потому что, есть неоднозначности в результиру

БС> ющем коде.

Не совсем так, as тут не причем. Это скорее баг компилятора Delphi в initialization секции (не вызывается Release)

БС>*) При запросах интерфейса использовать специальный набор функций (для перестраховщиков), чтобы если что то не

БС> работало — НЕ СКАЗАЛО об этом ala Supports

Так удобней Никто вас не заставляет их использовать. Ничего тут такого навороченного нету
function Supports(const Instance: IInterface; const IID: TGUID; out Intf): Boolean;
begin
  Result := (Instance <> nil) and (Instance.QueryInterface(IID, Intf) = 0);
end;



БС>*) Использование intf := nil; остается на свой страх и риск. Потому что, то, как автоматизирован подсчет

БС> ссылок знают только сами разработчики компилятора, а может и они уже совневаются...?

Если необходимо побыстрей вызвать Release — присвоить nil. Иначе втоматический Release будет вызван только в конце процедуры.
Как правило intf := nil; явно не пишут — лишний код.

БС>*) Использование интерфейсов при всех ограничениях сводиться к дебилизму (запросил — обнулил), я имею ввиду -

БС> никакой динамики, ни какой оптимизации.

Читайте выше. Никто вас не заставляет их обнулять. Все будет сделано автоматически

БС>*) Все, так называемые "удобства" становятся неуправляемыми местами для багов. Все приходится делать "ручками".


Что то я не понял где вы тут багов наделали. И что вам тут не понравилось. На сколько я знаю такую же функциональность в С++ реализуют через смарт поинтеры CComPtr, кажется. Тут же все за вас делает компилятор.

БС>Если я, точно знаю, где мне нужно аллокировать а где отпустить память, почему мне не оставили возможность

БС>делать это. При явном вызове Release вылетают Access violation и его братья по разуму.

AddRef и Release — это счетчики ссылок. Release — убивает обьект если RefCount = 0. НО никто вас не заставляет порождать обьекты от TInterfacedObject. Сделайте свой класс, которому по барабану эти ссылки

БС>А как бы было сдорово аля...



БС>
БС>var

БС>  iw : IWriter

БС>begin

БС>  iw := GetInterface(IID_IWriter...);

БС>  iw.Method1();
БС>  iw.Method2();

БС>  iw.QueryInterface(IID_IFileCreator,iw);

БС>  // здесь я знаю, что мне не понадобяться методы IWriter
БС>  iw.Release; // refCount := refCount - 1;

Замените на iw := nil. Явно AddRef и Release практически никто не вызывает.

БС>  iw.OtherMethod();
БС>  iw.OtherMethod();

БС>  iw.Release(); // refCount = 0;

БС>  // здесь я точно знаю что TCoWriter удалится
БС>  // и мне нельзя использовать iw...

БС>end.

БС>


Позабирайте все Release и вы будете знать точно чо TCoWriter удалился
Не возвращаемся к ассемблеру

БС>Наверное программирование на основе интерфейсов имеет смысл только в больших проектах,

БС>где ЧерТ нОгУ слоМиТ, кто кого использует и кто что подгружает и выгружает. А на таких
БС>маленьких задачах, это даже кажется абсурдом и простой глупостью (темболее на Дельфях).

Это зависит от того как ВЫ кодируете. Использование интерфейсов в делфях и именно в делфях оччень удобное.

БС>Как же быть? Писать совю реализацию пары IUnknown/IInterfacedObject?

БС>Где можно будет оставить такие важные моменты как подсчет ссылок программеру...
БС>Но ведь у компилятора не выключишь эту фичу...

А зачем? Правда возникают такие моменты но в вашем случае я их просто не вижу.
Re[5]: Программирование на языке Delphi. Глава 6. Вопрос!!!
От: Аноним  
Дата: 19.09.06 16:15
Оценка:
БС>> iw.Release; // refCount := refCount — 1;

D>Замените на iw := nil. Явно AddRef и Release практически никто не вызывает.


В данном случае нельзя выполнить iw := nil, потому что за этим присваиванием стоит

iw.Release;
iw := nil;

В общем случае, если интерфейсной переменной Intf присваивается некоторое значение X

Intf := X;

вполняется следующее:

if Intf <> nil then Intf.Release;
Intf := X;
if Intf <> nil then Intf.AddRef;

P.S. В Delphi технология COM имеет наиболее человеческое лицо. И вообще, с самого начала это была тупиковая технология, и ругать Delphi бессмысленно.
Re[6]: Программирование на языке Delphi. Глава 6. Вопрос!!!
От: Базунов Сергей  
Дата: 20.09.06 05:42
Оценка: 4 (1) +1
Здравствуйте, Аноним, Вы писали:


БС>>> iw.Release; // refCount := refCount — 1;

D>>Замените на iw := nil. Явно AddRef и Release практически никто не вызывает.
А>В данном случае нельзя выполнить iw := nil, потому что за этим присваиванием стоит
А>iw.Release;
А>iw := nil;
А>В общем случае, если интерфейсной переменной Intf присваивается некоторое значение X
А>Intf := X;
А>вполняется следующее:
А>if Intf <> nil then Intf.Release;
А>Intf := X;
А>if Intf <> nil then Intf.AddRef;
А>P.S. В Delphi технология COM имеет наиболее человеческое лицо.
А>И вообще, с самого начала это была тупиковая технология, и ругать Delphi бессмысленно.


1) Интересно, почему intf := x, вызывает у intf.Release сначала?
Как это объяснить? Зачем мы уменьшаем счетчик, хотя на самом деле запрашиваем
еще один интерфейс???
Вы уверены в этом?

2)
А> P.S. В Delphi технология COM имеет наиболее человеческое лицо.
.. вот с этим то я как раз не соглашусь! От СОМ, Delphi программисты получили только зад.
работая с которым можно повлиять на ээ... лицо.

3) СОМ — тупиковая технология???
А вы в курсе, как работают Интерпретируемые языки?, такие как, JScritp, VBScript и вообще WSH, на чем
построена технология ASP? А на чем работает весь пресловутый .NET? а ActiveX?
Я вообще считаю, что молодым прикладным программистам, нужно начинать учиться с понятия "интерфейс", это
правильно, в современном мире. Сервисы, Интерфейсы, Предоставлять — это слова будущего.
Посмотрите на молодое поколение программистов, которые подрастают за нами. Зачем им знать как все
устроено изнутри? Я думаю им понадобяться знания как всю эту "махину" правильно использовать. И этого будет
достаточно, чтобы решать современные задачи. Здесь, как раз СОМ и есть решение!

я думаю СОМ в этом смысле бессмертна
Re[7]: Программирование на языке Delphi. Глава 6. Вопрос!!!
От: Danchik Украина  
Дата: 20.09.06 08:56
Оценка:
Здравствуйте, Базунов Сергей, Вы писали:

[Skip]

БС>1) Интересно, почему intf := x, вызывает у intf.Release сначала?

БС> Как это объяснить? Зачем мы уменьшаем счетчик, хотя на самом деле запрашиваем
БС> еще один интерфейс???
БС> Вы уверены в этом?

При чем тут запрашивание интерфейса? У вас тут явное присваивание. Тоесть одного отпустить, другого — захватить

intf := x вызывается:
procedure _IntfCopy(var Dest: IInterface; const Source: IInterface);
var
  P: Pointer;
begin
  P := Pointer(Dest);
  if Source <> nil then
    Source._AddRef;
  Pointer(Dest) := Pointer(Source);
  if P <> nil then
    IInterface(P)._Release;
end;

И что в этом коде неправильного?

В случае с x.QueryInterface(ISome, Intf):
if intf <> nil then begin {IntfClear}
  intf.Release;
  Pointer (intf) := nil
end;
QueryInterface(x, ISome, Intf) // Addref вызван в QueryInterface
Re[8]: Программирование на языке Delphi. Глава 6. Вопрос!!!
От: Аноним  
Дата: 22.09.06 07:47
Оценка:
Здравствуйте, Danchik, Вы писали:

D>Здравствуйте, Базунов Сергей, Вы писали:


D>[Skip]


БС>>1) Интересно, почему intf := x, вызывает у intf.Release сначала?

БС>> Как это объяснить? Зачем мы уменьшаем счетчик, хотя на самом деле запрашиваем
БС>> еще один интерфейс???
БС>> Вы уверены в этом?

D>При чем тут запрашивание интерфейса? У вас тут явное присваивание. Тоесть одного отпустить, другого — захватить


D>intf := x вызывается:

D>
D>procedure _IntfCopy(var Dest: IInterface; const Source: IInterface);
D>var
D>  P: Pointer;
D>begin
D>  P := Pointer(Dest);
D>  if Source <> nil then
D>    Source._AddRef;
D>  Pointer(Dest) := Pointer(Source);
D>  if P <> nil then
D>    IInterface(P)._Release;
D>end;
D>

D>И что в этом коде неправильного?

D>В случае с x.QueryInterface(ISome, Intf):

D>
D>if intf <> nil then begin {IntfClear}
D>  intf.Release;
D>  Pointer (intf) := nil
D>end;
D>QueryInterface(x, ISome, Intf) // Addref вызван в QueryInterface
D>


ну так Вы имели ввиду случай: intf1 := intf2
Re: Программирование на языке Delphi. Глава 6.
От: Палкин Россия  
Дата: 18.12.06 18:25
Оценка:
type
ITextReader = interface(IInterface)
...
end;

IExtendedTextReader = interface(ITextReader)
...
end;

TExtendedTextReader = class(TInterfacedObject, IExtendedTextReader)
...
end;

var
Obj: TExtendedTextReader;
Intf: ITextReader;
begin
...
Intf := Obj; // Ошибка! Класс TExtendedTextReader не реализует
// интерфейс IExtendedTextReader.
...
end;

Мне кажется, здесь ошибка в комментарии. Класс TExtendedTextReader не реализует
интерфейс ITextReader
Re[2]: Программирование на языке Delphi. Глава 6.
От: ksurkov  
Дата: 18.12.06 18:57
Оценка:
Здравствуйте, Палкин, Вы писали:

П>type

П> ITextReader = interface(IInterface)
П> ...
П> end;

П> IExtendedTextReader = interface(ITextReader)

П> ...
П> end;

П> TExtendedTextReader = class(TInterfacedObject, IExtendedTextReader)

П> ...
П> end;

П>var

П> Obj: TExtendedTextReader;
П> Intf: ITextReader;
П>begin
П> ...
П> Intf := Obj; // Ошибка! Класс TExtendedTextReader не реализует
П> // интерфейс IExtendedTextReader.
П> ...
П>end;

П>Мне кажется, здесь ошибка в комментарии. Класс TExtendedTextReader не реализует

П>интерфейс ITextReader

Да, в комментарии допущена ошибка. Вместо IExtendedTextReader должно быть ITextReader.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.