Аннотация:
При программировании нередко возникает необходимость выполнить обращение к объекту, находящемуся в другом загрузочном модуле, например EXE или DLL. Для решения поставленной задачи компания Microsoft разработала технологию COM (Component Object Model) — компонентную модель объектов. Технология получила такое название благодаря тому, что обеспечивает создание программных компонентов — независимо разрабатываемых и поставляемых двоичных модулей. Поскольку объекты различных программ разрабатываются на различных языках программирования, например Delphi, C++, Visual Basic и др., технология COM стандартизирует формат взаимодействия между объектами на уровне двоичного представления в оперативной памяти. Согласно технологии COM взаимодействие между объектами осуществляется посредством так называемых интерфейсов. Рассмотрим, что же они собой представляют и как с ними работают.
>При программировании нередко возникает необходимость выполнить обращение к объекту, находящемуся в другом загрузочном модуле, например 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
Специалист — это варвар, невежество которого не всесторонне :)
Во-первых, нет ни слова о 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
Специалист — это варвар, невежество которого не всесторонне :)
Кстати, можно упомянуть (если еще нет этого), что при взятии делегированного интерфейса счетчик ссылок повышается не у агрегированного объекта, а у агрегата. Впрочем, это логично... А вот о чем стоит написать (правда, может, позже по тексту) — это о TAggregatedObject (или как там его) и о правильной организации делегирования интерфейсов (какие требования предъявляет COM и просто логика, типа обратимости взятия интерфейса и тп)
Slicer
Специалист — это варвар, невежество которого не всесторонне :)
все атрибуты интерфейсов являются общедоступными (public);
Мне кажется, более точным будет утверждать, что методы интерфейса вообще не имеют спецификатора доступа.
И это логично, объявив интерфейс, мы только декларируем поведение, не привязывая его к конкретной реализации
все атрибуты интерфейсов являются общедоступными (public);
S>Мне кажется, более точным будет утверждать, что методы интерфейса вообще не имеют спецификатора доступа.
Ну, это кто-то может оспорить, а насчет фразы я бы предложил оставить "Общедоступные", но убрать "public". А то еще подумает народ, что методы, реализующие интерфейсы, должны быть public (ведь в сам интерфейс public воткнуть некуда).
Slicer
Специалист — это варвар, невежество которого не всесторонне :)
Re[3]: Делегаты и события
От:
Аноним
Дата:
12.12.05 15:56
Оценка:
Здравствуйте, Slicer [Mirkwood], Вы писали:
SM>... я бы предложил оставить "Общедоступные", но убрать "public". А то еще подумает народ, что методы, реализующие интерфейсы, должны быть public (ведь в сам интерфейс public воткнуть некуда).
Согласен, слово public может вызвать неверное толкование.
Re: Программирование на языке Delphi. Глава 6. Вопрос!!!
Кто за что отвечает?
Кто подкован в этом деле на Дельфях, объясните плиз,
что же происходит с памятью...
У меня есть небольшая практика программирования 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 bytesbegin
iw := GetWriter;
// что здесь происходит?
// - загружается библиотека, (или она вместе с exe"шником грузится сразу?)
// - вызывается эксп-функция, т.е. в адресном пространстве библиотеки, "аллокируется"
// память под объект класса TCoWriter - так?
// - ну и наверное здесь происходит первый AddRef?
iw.WriteHello; // вызываем методы интерфейса IWriter
iw.WriteDate;
// запрашиваем другой интерфейс IFileCreator в fcif( iw.QueryInterface(IID_IFileCreator,fc) = S_OK ) then// ну здесь то точно AddRef! refCount = 2 как минимум!begin
fc.createfile('test2.txt'); //вызываем методы этого интерфейса
fc := nil; // больше не нужен - удаляем ссылку с интерфейса
// вот здесь уже не понятно, что происходит.
// Вызывается Release? Ок, значит в "TCoWriter.refcount" лежит единичка?end;
// далее...
// а можно вот так запросить IFileCreator из IWriterwith iw as IFileCreator do// здесь опять AddRef 100%, TCoWriter.refCount = 2begin
createfile('test.txt'); // вызываем методы IFileCreatorend;
iw := nil; // обнуляем ссылку - здесь дельфи вызовет Release
// НО! в RefCount останется 1, ??
// Как здесь вообще управлять памятью??
// Кто здесь позаботится об удалении объекта TCoWriter?end. // а здесь вообще библиотека выгружается?
// в стандартном виде - СОМ менеджер памяти вызывает
// спец функцию, експортируемую библиотекой,
// DllCanUnloadNow которая все знает про кол-во ссылок на
// интерфейсы и кол-во Locks. А здесь как?
Ну вот мои мысли по этому поводу в коментариях.
Помогите разобраться с кашей на дельфях...
Re[2]: Программирование на языке Delphi. Глава 6. Вопрос!!!
Здравствуйте, 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>
*) библиотеку лучше подгружать динамически, дабы оставить за собой возможность выгружать ее самому
(потому что никто этого не делает в итоге)
*) Не использовать при запросах интерфейса оператор 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. Вопрос!!!
Здравствуйте, Базунов Сергей, Вы писали:
БС>Из Ваших поправок делаю вывод:
БС>*) библиотеку лучше подгружать динамически, дабы оставить за собой возможность выгружать ее самому БС> (потому что никто этого не делает в итоге)
Это как душе угодно. Для плагинов именно так и надо поступать.
БС>*) Не использовать при запросах интерфейса оператор 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. Вопрос!!!
БС>>> 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. Вопрос!!!
[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>