Но реальность оказалась не столь оптимистичной - на текущий момент Delphi такого функционала не поддерживает:
- http://groups.google.com.ua/group/borland.public.delphi.objectpascal/browse_thread/thread/4c5dcb094b82522f?pli=1
- http://qc.embarcadero.com/wc/qcmain.aspx?d=941 (а судя по датам и не будет поддерживать)
- метод в интерфейсе напрямую возвращающий адрес метода объекта
IAnIntf = interface
function GetPointerToProc: TAnObjectProc:
end
- объект-обертку над интерфейсом
TWrapObj = class
private
AnIntf: ITheIntf;
protected
procedure AnProc(const Value: string);
end;
procedure TWrapObj.AnProc(const Value: string);
begin
AnIntf.AnProc(Value);
end;
возможно как-то можно будет выкрутиться через анонимные функции?нельзя, ибо
TAnProc = procedure (S: string) of object;
это разные типы
TAnProc2 = reference to procedure (S: string);
> Still I think making (procedure of object) and (procedure of Interface) equivalent
> typeswould be the better solution.
Like I said, the semantics for the compiler would be quite different, so
it would make sense to make them different types (even if both
represented internally by a TMethod). Actually, if the interface
instance variable is nil, it is impossible to get the method address.
This is not a problem for objects, since there the static address is
taken.
4 комментария:
Плохо, коллега.
Плохо, грустно и неинтересно. Какие-то классовые обертки, странные функции для ломки архитектуры, "онани(з)мные" функции. Код/язык слишком хорош, чтобы пхать его в рамки детских/книжных решений.
А теперь мое решение :) Всего 14 строк кода, учитывая комментарии и пустые строки. Как говорится, I challenge you...
program Project1;
{$APPTYPE CONSOLE}
type
TProc1 = procedure (const S:string; i:integer) of object;
IIntf = interface(IUnknown)
procedure IntfProc(const S:string; i:integer);
end;
TObj=class(TInterfacedObject,IIntf)
procedure IntfProc(const S:string; i:integer);
destructor Destroy;override;
end;
//==============код адаптера=============================
procedure Adapter; assembler;
asm
// само собой тип вызова fastcall :)
MOV EBX, [EAX]
JMP DWORD PTR [EBX] + VMTOFFSET IIntf.IntfProc
end;
function ToObj(I:IIntf):TProc1;
begin
//строка для проверки совместимости типов callback'а и интерфейса
if false then Result:=TObj(nil).IntfProc;
TMethod(Result).Code:=@Adapter;
TMethod(Result).Data:=Pointer(I);
end;
//==============код адаптера=============================
{ TObj }
destructor TObj.Destroy;
begin
Writeln('Destructor called');
inherited;
end;
procedure TObj.IntfProc(const S: string; i: integer);
begin
Writeln(S, ' ', i);
end;
procedure Test(P:TProc1);
//callback emulator
begin
P('Test', 1);
end;
var O:TObj;
I:IIntf;
begin
O:=TObj.Create;
I:=O;
I.IntfProc('ss', 1);
Test( ToObj(I) );
end.
Гм, но о живучести интерфейса, ежели где этот указатель запоминать, заботиться прийдется самому. Или делать детскую обертку...
Ибо по выходу из ToObj ссылок на интерфейс уже не будет, так?
И попутно вопрос, насколько корректно в TMethod(Result).Data класть указатель на интерфейс, если в самой функции будут обращения к Self?
По первому вопросу - "живучесть" интерфейса как обеспечивалась программистом, так и должна обеспечиваться. Ситуация, когда в callback'е есть указатель на функцию, интерфейс которой уже невалиден, должна и будет наказываться исключением :)
При большом желании можно сделать AddRef, только это все равно ручная работа. Да и странно это.
По второму - интерфейс и есть указатель, хоть как ты его заворачивай и обзывай. С обращениями к Self тоже проблем быть не должно (тестил). Если для вызова метода, как по-умолчанию, используется fastcall.
Так что я и дальше (пока) продолжаю считать мое решение оптимальным ;)
Если на метод интерфейса есть ссылка, то вполне логично держать счетчик у самого интерфейса.
А для чего используется TMethod(Result).Data? Только для отсчета смещения? Ведь ссылка на интерфейс и сам Self это вроде разные адреса?
Отправить комментарий