Я поиграл с примером smartpointer от Сергея Антонова, см .: http://blog.barrkel.com/2008/11/reference-counted-pointers-revisited.html (где-то в комментариях).Почему эта оптимизация smartpointer не работает?
SSCCE:
program TestSmartPointer;
{$APPTYPE CONSOLE}
uses
System.SysUtils;
type
TInjectType<T> = record
public
VMT: pointer;
unknown: IInterface;
RefCount: integer;
AValue: T;
end;
TInject<T> = class
public type
TInjectType = TInjectType<T>;
PInjectType = ^TInjectType;
end;
PInjectObjectType = TInject<TObject>.PInjectType;
TSmartPointer<T: class> = class
class function Wrap(const AValue: T): TFunc<T>; static;
end;
function Trick_Release(const obj: PInjectObjectType): Integer; stdcall; forward;
function Trick_AddRef(const obj: PInjectObjectType): Integer; stdcall; forward;
function Invoke(var obj): TObject; forward;
const
PSEUDO_VMT: array [0 .. 3] of pointer = (nil, @Trick_AddRef, @Trick_Release, @Invoke);
function Trick_AddRef(const obj: PInjectObjectType): Integer; stdcall;
begin
Result:= AtomicIncrement(Obj^.RefCount);
end;
function Trick_Release(const obj: PInjectObjectType): Integer; stdcall;
begin
Result:= AtomicDecrement(Obj^.RefCount);
if Result = 0 then obj^.AValue.Free;
end;
function Invoke(const obj: PInjectObjectType): TObject;
begin
Result:= obj^.AValue;
end;
class function TSmartPointer<T>.Wrap(const AValue: T): TFunc<T>;
var
h: TInjectType<T>;
begin
h.RefCount:= 1;
pointer(h.unknown):= @h;
h.VMT:= @PSEUDO_VMT;
h.AValue:= AValue;
//Alternative A, this fails
Result:= TFunc<T>(@h);
Inc(h.RefCount);
////Alternative B, this works
//Result:= function: T
// begin
// Result:= h.AValue;
// end;
end;
type
TTestObject = class(TObject)
procedure Test;
destructor Destroy; override;
end;
{ TTestObject }
procedure TTestObject.Test;
begin
WriteLn('Test');
end;
destructor TTestObject.Destroy;
begin
WriteLn('Free');
inherited;
end;
procedure Test;
var
TestObject: TFunc<TTestObject>;
begin
TestObject:= TSmartPointer<TTestObject>.Wrap(TTestObject.Create);
TestObject.Test;
ReadLn; //Works up to this point.
<<<--- generates a AV here.
end;
begin
WriteLn('Start');
Test;
WriteLn('End');
ReadLn;
end.
Барри Келли объясняет, что:
TFunc = ссылка на функцию: T;
прямо эквивалентен:
TFunc = интерфейс функции Invoke: T; конец;
за исключением того, что местоположения ссылочного типа метода назначаются с использованием значений функции, метода или анонимного метода.
Анонимные методы реализованы как интерфейсы, которые выглядят так же, как ссылка на метод, на скрытый класс. Захват местоположения реализуется как перемещение (для локальных пользователей) и копирование (для параметров) в поля скрытого класса. Любые обращения захваченных местоположений в основной процедуре преобразуются для доступа к полям скрытого класса; локальная переменная, называемая $ frame, указывает на экземпляр этого скрытого класса.
Цель
Я хочу, чтобы оптимизировать создание умного указателя.
Для этого я использую VMT и использую его для эмуляции интерфейса.
Если я определяю свою wrap
функцию следующим образом:
class function TSmartPointer<T>.Wrap(const AValue: T): TFunc<T>;
var
h: TInjectType<T>;
begin
pointer(h.unknown):= @h;
h.VMT:= @PSEUDO_VMT;
h.AValue:= AValue;
Result:= function: T
begin
Result:= h.AValue;
end;
end;
Все работает.
Если оптимизировать его:
class function TSmartPointer<T>.Wrap(const AValue: T): TFunc<T>;
var
h: TInjectType<T>;
begin
h.RefCount:= 1;
pointer(h.unknown):= @h;
h.VMT:= @PSEUDO_VMT;
h.AValue:= AValue;
//Alternative A, this fails
Result:= TFunc<T>(@h);
Inc(h.RefCount);
end;
It почти работает, но дает AV как только функция вызова закрывается.
procedure Test;
var
TestObject: TFunc<TTestObject>;
begin
TestObject:= TSmartPointer<TTestObject>.Wrap(TTestObject.Create);
TestObject.Test;
ReadLn;
//Works up to this point.
<<<--- generates a AV here.
end;
Вы ожидали бы AV произойти в _Release, но это не так, ведь происходит до этого.
TestNewStringHelper.dpr.98: TestObject.Test;
00419F0B 8B45FC mov eax,[ebp-$04]
Здесь EAX = 0018FF40
00419F0E 8B10 mov edx,[eax]
00419F10 FF520C call dword ptr [edx+$0c]
00419F13 E82CFFFFFF call TTestObject.Test
TestNewStringHelper.dpr.100: end;
00419F18 33C0 xor eax,eax
00419F1A 5A pop edx
00419F1B 59 pop ecx
00419F1C 59 pop ecx
00419F1D 648910 mov fs:[eax],edx
00419F20 68359F4100 push $00419f35
00419F25 8D45FC lea eax,[ebp-$04]
Здесь EAX = 0018FF6C
Очевидно, что это должно быть таким же, как и раньше. Тот факт, что это не является причиной AV, который должен следовать:
00419F28 E87BF6FEFF call @IntfClear <<-- AV
Вызов IntfClear
AV, потому что он не может найти подходящую цель для _Release
. IOW вызов никогда не достигает _Release, но переходит в неизвестность.
System.pas.36036: MOV EDX,[EAX]
004095A8 8B10 mov edx,[eax]
System.pas.36037: TEST EDX,EDX
004095AA 85D2 test edx,edx
System.pas.36038: JE @@1
004095AC 740E jz $004095bc
System.pas.36039: MOV DWORD PTR [EAX],0
004095AE C70000000000 mov [eax],$00000000
System.pas.36043: PUSH EAX
004095B4 50 push eax
System.pas.36044: PUSH EDX
004095B5 52 push edx
System.pas.36045: MOV EAX,[EDX]
004095B6 8B02 mov eax,[edx]
System.pas.36046: CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release
004095B8 FF5008 call dword ptr [eax+$08] <<-- AV here
Почему это необходимо и что мне нужно для настройки, чтобы оптимизированная версия работала?
Я перестал читать на «Я хочу, чтобы оптимизировать создание» –