Показаны сообщения с ярлыком hockey. Показать все сообщения
Показаны сообщения с ярлыком hockey. Показать все сообщения

пятница, 26 апреля 2013 г.

Что я ещё обычно правлю в VCL и других сторонних библиотеках

Заменяю Free на FreeAndNil.

Понятно почему?

Обычный сценарий таков:


TA = class
private
 FField : TComponent;
protected
 procedure DoSomething;
 procedure DoSomething1; virual;
 destructor Destroy; override;
end;
 
TB = clas(TA)
protected
 procedure DoSomething1; override;
end;
 
 
destructor TA.Destroy;
begin
 FField.Free;
 DoSomething1;
 inherited;
end;
 
procedure TA.DoSomething;
begin
 if (FField <> nil) then
  FField.CallSomeMethod;
end;
 
procedure TA.DoSomething1;
begin
end;
 
procedure TB.DoSomething1;
begin
 DoSomething;
 inherited;
end;


-- никакой магии и никакого "хоккея" (http://18delphi.blogspot.com/2013/04/blog-post_8.html), но огребаем AV.

Посему - я обычно тупо беру FAR и Alt-F7. И тупо заменяю все Free на FreeAndNil. Хуже - уж точно не становится.

Update. Вот и обоснование - http://www.gunsmoker.ru/2009/04/freeandnil-free.html

И цитата оттуда:
"Заметьте, что речь идёт именно о замене Free на FreeAndNil везде. Не просто об использовании FreeAndNil, когда вы хотите проверять ссылку на nil, а именно - целиком и полностью везде. Т.е. не писать Free вообще никогда. Да, включая сценарии с локальными переменными.

Почему? Ну причина проста - нет никаких доводов так не делать (пожалуйста, дочитайте до конца). Зато есть доводы против использования Free в этих ситуациях."

четверг, 25 апреля 2013 г.

САМЫЙ БОЛЬШОЙ проект сегодня взлетел под XE3, но там что-то падает

САМЫЙ БОЛЬШОЙ проект сегодня взлетел под XE3, но там что-то падает. В основном из-за хоккея. Отладим.

Самые БОЛЬШИЕ проблемы были из-за доступа к приватным полям, через объявление "зеркального" класса..

пятница, 12 апреля 2013 г.

Портировал весь свой "хоккей" с Delphi 7 на Delphi XE3

Прошло 95% тестов...

Осталась бизнес-логика и реальные "непонятки"...

Порт с Delphi 1 на Delphi 2 занял несколько месяцев...

С Delphi 2 на Delphi 6 - аналогично...

вторник, 9 апреля 2013 г.

Сегодня мне показалось, что я нашёл ошибку в менеджере памяти Delphi XE

Все симптомы говорили об этом.

Я даже выделил тест:


procedure TReallocMemTest.DoIt;
var
 l_Index : Integer;
 l_P : PAnsiChar;
 l_I : Integer;
 l_Size : Integer;
 l_OldSize : Integer;
begin
 for l_Index := 0 to 5000 do
 begin
  l_Size := 10;
  GetMem(l_P, l_Size);
  try
   while (l_Size <= 6144 * 2) do
   begin
    FillChar(l_P^, l_Size, Random($ff));
    l_OldSize := l_Size;
    l_I := PInteger(l_P + l_OldSize - SizeOf(Integer))^;
    Inc(l_Size, Random(20));
    ReallocMem(l_P, l_Size);
    Check(PInteger(l_P + l_OldSize - SizeOf(Integer))^ = l_I);
   end;//l_Size <= 6144
  finally
   FreeMem(l_P);
  end;//try..finally
 end;//for l_Index
end;//TReallocMemTest.DoIt
 
procedure TReallocMemTest.DoIt1;
const
 cMagicSize0 = 10;
 cMagicSize = 3120;
 cNewMagicSize = 6144;
var
 l_P : PAnsiChar;
 l_I : Integer;
begin
 GetMem(l_P, cMagicSize0);
 try
  FillChar(l_P^, cMagicSize0, 10);
  l_I := PInteger(l_P + cMagicSize0 - SizeOf(Integer))^;
  ReallocMem(l_P, cNewMagicSize);
  Check(PInteger(l_P + cMagicSize0 - SizeOf(Integer))^ = l_I);
 
  FillChar(l_P^, cMagicSize, 10);
  l_I := PInteger(l_P + cMagicSize - SizeOf(Integer))^;
  ReallocMem(l_P, cNewMagicSize);
  Check(PInteger(l_P + cMagicSize - SizeOf(Integer))^ = l_I);
 finally
  FreeMem(l_P);
 end;//try..finall
end;//TReallocMemTest.DoIt


И даже поделился с коллегами о своём "открытии". И даже написал в Embarcadero.

Но! Без предварительного вызова МОЕГО КОДА - этот тест - не падает.

Мячик на моей стороне.

Каким же я выглядел идиотом.

Но правда совсем не ошибается лишь тот, кто ничего не делает. Зато я добавил ещё один тест в базу тестов.

Как я уже писал - написанный и ОТЛАЖЕННЫЙ тест - надо не выкидывать, а включать в базу тестов. ЛЮБОЙ. За него "кровью и нервами уплочено".

Как говорится в сказках про Иванушку-дурачка - "не убивай меня - я тебе пригожусь".

Надеюсь, что итоги сегодняшнего дня помогут мне стать капельку мудрее.

P.S. а проблемы на самом деле были вот в этом "хоккее" - http://18delphi.blogspot.com/2013/04/getmem.html

Продолжаю борьбу с "хоккеем" в библиотеках при портировании на XE3

Думаю - это знак... ВЕСЬ код должен быть написан БЕЗ "хоккея"... А "хоккей" - только там где реально нужно.. Опционально включать.. Но не более того...

Ошибки молодости....

понедельник, 8 апреля 2013 г.

О термине "хоккей"

Взялся он вот откуда. СТАНДАРТНАЯ библиотека:


unit Vcl.Menus;
....

{$IFNDEF WIN32}
                                                                                           
// Win64 and CLR both use Iterator objects instead of local procedure for operations which
// require iterating over the menu items.  Calling a local procedure in a class method
// requires special a ASM thunk
{$DEFINE ITERATOR_OBJECTS}
{$ENDIF}

type
  TIterator = function (MenuItem: TMenuItem): Boolean{$IFDEF ITERATOR_OBJECTS} of object{$ENDIF};

procedure IterateMenus(Func: TIterator; Menu1, Menu2: TMenuItem);
var
  IIndex: Integer;

  function Iterate(var I: Integer; MenuItem: TMenuItem; AFunc: TIterator): Boolean;
  var
    Item: TMenuItem;
  begin
    Result := False;
    if MenuItem = nil then Exit;
    while not Result and (I < MenuItem.Count) do
    begin
      Item := MenuItem[I];
      if Item.GroupIndex > IIndex then Break;
{$IFDEF ITERATOR_OBJECTS}
      Result := AFunc(Item);
{$ELSE !ITERATOR_OBJECTS}
{$IFDEF CPUX86}
      // Thunk to to call a local procedure on a class.  Kinda hokey if you ask ME.
      asm
                MOV     EAX,Item
                MOV     EDX,[EBP+8]
                PUSH    DWORD PTR [EDX]
                CALL    DWORD PTR AFunc
                ADD     ESP,4
                MOV     Result,AL
      end;
{$ENDIF CPUX86}
{$ENDIF !ITERATOR_OBJECTS}
      Inc(I);
    end;
  end;

var
  I, J: Integer;
  JIndex: Byte;
  Menu1Size, Menu2Size: Integer;
  Done: Boolean;

begin
  I := 0;
  J := 0;
  Menu1Size := 0;
  Menu2Size := 0;
  if Menu1 <> nil then Menu1Size := Menu1.Count;
  if Menu2 <> nil then Menu2Size := Menu2.Count;
  Done := False;
  while not Done and ((I < Menu1Size) or (J < Menu2Size)) do
  begin
    IIndex := High(Byte);
    JIndex := High(Byte);
    if (I < Menu1Size) then IIndex := Menu1[I].GroupIndex;
    if (J < Menu2Size) then JIndex := Menu2[J].GroupIndex;
    if IIndex <= JIndex then Done := Iterate(I, Menu1, Func)
    else
    begin
      IIndex := JIndex;
      Done := Iterate(J, Menu2, Func);
    end;
    while (I < Menu1Size) and (Menu1[I].GroupIndex <= IIndex) do Inc(I);
    while (J < Menu2Size) and (Menu2[J].GroupIndex <= IIndex) do Inc(J);
  end;
end;

// Thunk to to call a local procedure on a class.  Kinda hokey if you ask ME.
!!! ОЧЕНЬ УЖ мне этот комментарий, тех, кто разбирался с наследством Borland'а - ПОНРАВИЛСЯ !!!

воскресенье, 7 апреля 2013 г.

"Быстрый" подсчёт числа взведённых бит


unit Bits;
 
interface
 
function  l3BitCount(X: Longint): Longint;
  {* - "быстро" подсчитать число бит. }
function  l3BitCountPrim(X: Longint): Longint;
  {* - подсчитать число бит. }
 
implementation
 
var
 
 l3BitTable: array[0..255] of Longint;
 
procedure LoadBitTable;
var
 i : Longint;
begin
 for i := 0 to 255 do
  l3BitTable[i] := l3BitCountPrim(i);
end;
 
function l3BitCountPrim(X: Longint): Longint;
  {-}
var
 Y : Long;
begin
 Y := X;
 Y := Y - ((Y shr 1) and $55555555);
 Y := (Y and $33333333) + ((Y shr 2) and $33333333);
 Y := Y + Y shr 4;
 Y := Y and $0f0f0f0f;
 Y := Y + Y shr 8;
 y := Y + Y shr 16;
 Result := Y and $000000ff;
end;
 
function l3BitCount(X: Longint): Longint;
  register;
  {-}
asm
   test eax,eax
   jz @@Done
 
   push ebx
   mov ebx,eax
 
   xor ecx,ecx
   xor edx,edx
 
   mov cl,bl
   mov dl,bh
 
   shr ebx,16
   mov eax, dword ptr [l3BitTable+ecx*4]
 
   add eax, dword ptr [l3BitTable+edx*4]
   mov cl,bl
 
   add eax, dword ptr [l3BitTable+ecx*4]
   mov dl,bh
 
   add eax, dword ptr [l3BitTable+edx*4]
   pop ebx
@@Done:
end;
 
initialization
 LoadBitTable;
end.


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

Тогда можно сначала хранить битовую маску для обозначения того - какое свойство у объекта отличается от значения по-умолчанию, а какое - нет. И храним только значения отличающиеся от значений по-умолчанию. А из битовой маски достаточно просто получаем смещение к интересующему значению.

пятница, 5 апреля 2013 г.

Переменные "экземпляра мета-класса в Delphi"

Мне давно не хватает такой конструкции:


TA = class
 class static Count : Integer
end;
 
TB = class(TA)
end;
 
TA.Count := 20;
TB.Count := 45;
 
WriteLen(TA.Count);
WriteLn(TB.Count);


Получаем вывод:
20
45

Клёво не правда ли?

Т.е. чтобы у мета-класса TA была СВОЯ переменная Count, а у мета-класса TB - СВОЯ.

Но язык к сожалению не позволяет устроить такое "безобразие". Или я что-то опять пропустил?

Обращаю внимание на тот факт, что не у ЭКЗЕМПЛЯРОВ КЛАССОВ, а у экземпляров МЕТА-классов.

Опять же для знатоков БД (я к ним - не отношусь, так что - не бейте больно ногами) - привожу пример - "данные" и "мета-данные". Может быть - так понятнее...

Теперь о том - как это устроить.

На помощь придёт "копание в VMT".

Итак:


TA = class
protected
 procedure MetaClassVarPlacement;
  virtual;
public
 class function GetClassVar: Integer;
  class procedure SetClassVar(aValue: Integer);
end;
 
TB = class(TA)
end;
 
procedure TA.MetaClassVarPlacement;
  //virtual;
begin
end;
 
class function TA.GetClassVar: Integer;
  {-}
var
 l_Head : PPointer;
begin
 asm
  mov edx, VMTOffset TA.MetaClassVarPlacement
  add edx, eax
  mov l_Head, edx
 end;//asm
 if ( l_Head^= @TA.MetaClassVarPlacement) then
  Result := 0
 else
  Result := PInteger(l_Head)^;
end;
 
class procedure TA.SetClassVar(aValue: Integer);
  {-}
var
 l_Head : PPointer;
 l_Old  : DWORD;
begin
 assert(aValue <> Int64(@MetaClassVarPlacement), 'Предполагеаем, что это никогда не всплывёт');
 // - если предыдущая строчка не компилируется - закомментируйте её
 asm
  mov edx, VMTOffset TA.MetaClassVarPlacement
  add edx, eax
  mov l_Head, edx
 end;//asm
 if (l_Head^ = @TA.MetaClassVarPlacement) then
 begin
  VirtualProtect(l_Head, 4, PAGE_EXECUTE_READWRITE, @l_Old);
 end;
 PInteger(l_Head)^ := aValue;
end;
...
TA.SetClassVar(20);
TB.SetSlassVar(45);
 
WriteLn(TA.GetClassVar);
WriteLn(TB.GetClassVar);



Disclaimer. Этот код писался "с листа" посему - может и не заработать. Пишите. Тогда приведу рабочую версию.

Для Чего это может быть нужно?

Например для кешей объектов и фабрик. Или подсчёта количества объектов "именно этого класса":


TA.NewInstance:
begin
 Result := inherited NewInstance;
 SetClassVar(GetClassVar+1);
end;

Идея понятна?

Вы скажете - "ассоциативные массивы". Мапа ключ-значение. И вы будете правы. Только мой способ эффективнее по скорости. Для таких "системных" вещей как фабрики, кеш объектов или подсчёт экземпляров класса. Для "прикладных" вещей - КОНЕЧНО мапа или её аналоги.

Попробуйте. Может быть вам понравится.

И ещё - подобны "хоккей" я предпочитаю убирать под директиву NoHack. Т.е. пишу - ДВЕ версии кода. С "выкрутасами" или без. Чтобы легко переключиться можно было на версию "без выкрутасов".

P.S. гораздо более глубоко про VMT написано тут - http://www.transl-gunsmoker.ru/2011/08/hack15-overriding-message-and-dynamic.html

P.P.S. Ссылки из комментариев:

http://hallvards.blogspot.ru/2007/05/hack17-virtual-class-variables-part-i.html
http://hallvards.blogspot.ru/2007/05/hack17-virtual-class-variables-part-ii.html

Как узнать истинный размер памяти выделенной по GetMem

Вы не задумывались, что FreeMem - НЕ ТРЕБУЕТ этого размера. Значит он его - где-то "знает".

Всё очень просто:


function _l3MemorySize(aP: Pointer): Longint; 
 
{$IFDEF XE}
const
  {----------------------------Block type flags---------------------------}
  {The lower 3 bits in the dword header of small blocks (4 bits in medium and
   large blocks) are used as flags to indicate the state of the block}
  {Set if the block is not in use}
  IsFreeBlockFlag = 1;
  {Set if this is a medium block}
  IsMediumBlockFlag = 2;
  {Set if it is a medium block being used as a small block pool. Only valid if
   IsMediumBlockFlag is set.}
  IsSmallBlockPoolInUseFlag = 4;
  {Set if it is a large block. Only valid if IsMediumBlockFlag is not set.}
  IsLargeBlockFlag = 4;
  {Is the medium block preceding this block available?}
  PreviousMediumBlockIsFreeFlag = 8;
  {Is this large block segmented? I.e. is it actually built up from more than
   one chunk allocated through VirtualAlloc? (Only used by large blocks.)}
  LargeBlockIsSegmented = 8;
  {The flags masks for small blocks}
  DropSmallFlagsMask = -8;
  ExtractSmallFlagsMask = 7;
  {The flags masks for medium and large blocks}
  DropMediumAndLargeFlagsMask = -16;
  ExtractMediumAndLargeFlagsMask = 15;
{------------------------------Private types------------------------------}
type
 
  {Move procedure type}
  TMoveProc = procedure(const ASource; var ADest; ACount: NativeInt);
 
  {-----------------------Small block structures--------------------------}
 
  {Pointer to the header of a small block pool}
  PSmallBlockPoolHeader = ^TSmallBlockPoolHeader;
 
  {Small block type (Size = 32 bytes for 32-bit, 64 bytes for 64-bit).}
  PSmallBlockType = ^TSmallBlockType;
  TSmallBlockType = record
    {True = Block type is locked}
    BlockTypeLocked: Boolean;
    {Bitmap indicating which of the first 8 medium block groups contain blocks
     of a suitable size for a block pool.}
    AllowedGroupsForBlockPoolBitmap: Byte;
    {The block size for this block type}
    BlockSize: Word;
    {The minimum and optimal size of a small block pool for this block type}
    MinimumBlockPoolSize: Word;
    OptimalBlockPoolSize: Word;
    {The first partially free pool for the given small block. This field must
     be at the same offset as TSmallBlockPoolHeader.NextPartiallyFreePool.}
    NextPartiallyFreePool: PSmallBlockPoolHeader;
    {The last partially free pool for the small block type. This field must
     be at the same offset as TSmallBlockPoolHeader.PreviousPartiallyFreePool.}
    PreviousPartiallyFreePool: PSmallBlockPoolHeader;
    {The offset of the last block that was served sequentially. The field must
     be at the same offset as TSmallBlockPoolHeader.FirstFreeBlock.}
    NextSequentialFeedBlockAddress: Pointer;
    {The last block that can be served sequentially.}
    MaxSequentialFeedBlockAddress: Pointer;
    {The pool that is current being used to serve blocks in sequential order}
    CurrentSequentialFeedPool: PSmallBlockPoolHeader;
{$ifdef UseCustomFixedSizeMoveRoutines}
    {The fixed size move procedure used to move data for this block size when
     it is upsized. When a block is downsized (which usually does not occur
     that often) the variable size move routine is used.}
    UpsizeMoveProcedure: TMoveProc;
{$else}
    Reserved1: Pointer;
{$endif}
{$if SizeOf(Pointer) = 8}
    {Pad to 64 bytes for 64-bit}
    Reserved2: Pointer;
{$ifend}
  end;
 
  {Small block pool (Size = 32 bytes for 32-bit, 48 bytes for 64-bit).}
  TSmallBlockPoolHeader = record
    {BlockType}
    BlockType: PSmallBlockType;
{$if SizeOf(Pointer) <> 8}
    {Align the next fields to the same fields in TSmallBlockType and pad this
     structure to 32 bytes for 32-bit}
    Reserved1: Cardinal;
{$ifend}
    {The next and previous pool that has free blocks of this size. Do not
     change the position of these two fields: They must be at the same offsets
     as the fields in TSmallBlockType of the same name.}
    NextPartiallyFreePool: PSmallBlockPoolHeader;
    PreviousPartiallyFreePool: PSmallBlockPoolHeader;
    {Pointer to the first free block inside this pool. This field must be at
     the same offset as TSmallBlockType.NextSequentialFeedBlockAddress.}
    FirstFreeBlock: Pointer;
    {The number of blocks allocated in this pool.}
    BlocksInUse: Cardinal;
    {Small block pool signature. Used by the leak checking mechanism to
     determine whether a medium block is a small block pool or a regular medium
     block.}
    SmallBlockPoolSignature: Cardinal;
    {The pool pointer and flags of the first block}
    FirstBlockPoolPointerAndFlags: NativeUInt;
  end;
 
  {Small block layout:
   At offset -SizeOf(Pointer) = Flags + address of the small block pool.
   At offset BlockSize - SizeOf(Pointer) = Flags + address of the small block
   pool for the next small block.
  }
 
  {------------------------Medium block structures------------------------}
 
  {The medium block pool from which medium blocks are drawn. Size = 16 bytes
   for 32-bit and 32 bytes for 64-bit.}
  PMediumBlockPoolHeader = ^TMediumBlockPoolHeader;
  TMediumBlockPoolHeader = record
    {Points to the previous and next medium block pools. This circular linked
     list is used to track memory leaks on program shutdown.}
    PreviousMediumBlockPoolHeader: PMediumBlockPoolHeader;
    NextMediumBlockPoolHeader: PMediumBlockPoolHeader;
    {Padding}
    Reserved1: NativeUInt;
    {The block size and flags of the first medium block in the block pool}
    FirstMediumBlockSizeAndFlags: NativeUInt;
  end;
 
  {Medium block layout:
   Offset: -2 * SizeOf(Pointer) = Previous Block Size (only if the previous block is free)
   Offset: -SizeOf(Pointer) = This block size and flags
   Offset: 0 = User data / Previous Free Block (if this block is free)
   Offset: SizeOf(Pointer) = Next Free Block (if this block is free)
   Offset: BlockSize - 2*SizeOf(Pointer) = Size of this block (if this block is free)
   Offset: BlockSize - SizeOf(Pointer) = Size of the next block and flags
 
  {A medium block that is unused}
  PMediumFreeBlock = ^TMediumFreeBlock;
  TMediumFreeBlock = record
    PreviousFreeBlock: PMediumFreeBlock;
    NextFreeBlock: PMediumFreeBlock;
  end;
 
  {-------------------------Large block structures------------------------}
 
  {Large block header record (Size = 16 for 32-bit, 32 for 64-bit)}
  PLargeBlockHeader = ^TLargeBlockHeader;
  TLargeBlockHeader = record
    {Points to the previous and next large blocks. This circular linked
     list is used to track memory leaks on program shutdown.}
    PreviousLargeBlockHeader: PLargeBlockHeader;
    NextLargeBlockHeader: PLargeBlockHeader;
    {The user allocated size of the Large block}
    UserAllocatedSize: NativeUInt;
    {The size of this block plus the flags}
    BlockSizeAndFlags: NativeUInt;
  end;
 
 
{---------------------------Private constants-----------------------------}
const
  {The size of the block header in front of small and medium blocks}
  BlockHeaderSize = SizeOf(Pointer);
  {The size of a small block pool header}
  SmallBlockPoolHeaderSize = SizeOf(TSmallBlockPoolHeader);
  {The size of a medium block pool header}
  MediumBlockPoolHeaderSize = SizeOf(TMediumBlockPoolHeader);
  {The size of the header in front of Large blocks}
  LargeBlockHeaderSize = SizeOf(TLargeBlockHeader);
 
function _l3MemorySize(aP: Pointer): Longint;
var
lBlockHeader: Cardinal;
LPSmallBlockType: PSmallBlockType;
LOldAvailableSize: Cardinal;
begin
if (aP = nil) then
  Result := 0
else
begin
{Get the block header: Is it actually a small block?}
  LBlockHeader := PNativeUInt(PByte(aP) - BlockHeaderSize)^;
  {Is it a small block that is in use?}
  if LBlockHeader and (IsFreeBlockFlag or IsMediumBlockFlag or IsLargeBlockFlag) = 0 then
  begin
    {----------------------------Small block------------------------------}
    {The block header is a pointer to the block pool: Get the block type}
    LPSmallBlockType := PSmallBlockPoolHeader(LBlockHeader).BlockType;
    {Get the available size inside blocks of this type.}
    Result := LPSmallBlockType.BlockSize - BlockHeaderSize;
  end
  else
  begin
    {Is this a medium block or a large block?}
    if LBlockHeader and (IsFreeBlockFlag or IsLargeBlockFlag) = 0 then
    begin
     Result:= (LBlockHeader and DropMediumAndLargeFlagsMask) - BlockHeaderSize;
    end
    else
    begin
      {Is this a valid large block?}
      if LBlockHeader and (IsFreeBlockFlag or IsMediumBlockFlag) = 0 then
      begin
        {-----------------------Large block------------------------------}
        {Get the block header}
        //LBlockHeader := PNativeUInt(PByte(aP) - BlockHeaderSize)^;
        {Subtract the overhead to determine the useable size in the large block.}
        Result := (LBlockHeader and DropMediumAndLargeFlagsMask) - (LargeBlockHeaderSize + BlockHeaderSize);
      end
      else
      begin
        {-----------------------Invalid block------------------------------}
        {Bad pointer: probably an attempt to reallocate a free memory block.}
        Result := 0;
      end;
    end;
  end;
end;
end;
{$ELSE XE}
const
  cThisUsedFlag = 2;
  cPrevFreeFlag = 1;
  cFillerFlag   = Integer($80000000);
  cFlags        = cThisUsedFlag or cPrevFreeFlag or cFillerFlag;
 
type
  PUsed = ^TUsed;
  TUsed = packed record
    sizeFlags: Integer;
  end;//TUsed
 
function _l3MemorySize(aP: Pointer): Longint; 
begin
 if (aP = nil) then
  Result := 0
 else
  Result := PUsed(PAnsiChar(aP)-SizeOf(TUsed)).sizeFlags and not cFlags - sizeof(TUsed);
//  Result := (PLong(Long(aP) - 4)^ AND not cFlags) - 4;
end;
{$EndIf XE}


Зачем это нужно? Спросите вы... Для того чтобы не хранить Capacity. "Экономим на спичках". Может быть я доберусь до темы "свой "микро"-STL для Delphi". Там эта тема всплывёт.

Disclaimer. Этот код написан не только мной, это код стандартной библиотеки. Ну и коллега помогал мне разбираться в нём. Если я нарушил чьё-то авторское право - я уберу этот пост.

четверг, 28 марта 2013 г.

Вызов локальных функций для глобального контекста

http://ru.wikipedia.org/wiki/%D0%90%D0%BD%D0%BE%D0%BD%D0%B8%D0%BC%D0%BD%D0%B0%D1%8F_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F

http://www.delphimaster.ru/cgi-bin/faq.pl?look=1&id=19-988623694


Зравствуете Акжан.
У вас в разделе есть следующий ворос и несколько ответов на него:

Вот всю жизнь в TVision в итераторах нужно было (параметром) передавать указатель на локальную процедуру, а тут задумал сделать свой итератор для обхода некоей древовидной структуры и на тебе - компилятор ругается. Да еще и в хелпе носом тыкают, что так мол в принципе нельзя делать... Гм. И как быть?

- могу предложить собственное решение данной проблемы. Тем более, что мой способ работает с рекурсивными вызовами любого уровня сложности:
type
  Long = LongInt;
  Bool = Boolean; // - так уж у меня в библиотеке сложилось
  Tl3IteratorAction = function(Data: Pointer; Index: Long): Bool;
                      {$IfDef Win32}
                      register;
                      {$EndIf Win32}
var
 l3StubHead : THandle = 0;
 
function l3AllocStub: THandle;
  {-}
(*  register;
asm
          mov   ecx, l3StubHead
          jecxz @Alloc
          mov   eax, ecx
          mov   ecx, [ecx]
          mov   l3StubHead, ecx
          ret
@Alloc:
          xor   eax, eax
          push  16               { SizeOf(TCode) -> stack  }
          push  eax              { GMem_Fixed -> stack     }
          call  GlobalAlloc
@ret:
end;{asm}*)
begin
 if (l3StubHead = 0) then
  Result := Windows{l3System}.GlobalAlloc(GMem_Fixed, 16)
 else begin
  Result := l3StubHead;
  l3StubHead := PHandle(Result)^;
 end;
end;
 
procedure l3FreeLocalStub(Stub: Pointer);
  {-}
begin
 PHandle(Stub)^ := l3StubHead;
 l3StubHead := THandle(Stub);
end;
 
(*procedure l3FreeLocalStub(Stub: Pointer);
                          {eax}
  register;
  {-}
asm
          push eax                               { Handle -> stack         }
          call GlobalFree
end;{asm}*)
 
procedure l3FreeStubs;
var
 Prev : THandle;
 Next : THandle;
begin
 Prev := l3StubHead;
 while (Prev <> 0) do begin
  Next := PHandle(Prev)^;
  Windows{l3System}.GlobalFree(Prev);
  Prev := Next;
 end;{Prev <> 0}
 l3StubHead := 0;
end;
 
(*type
  TCode = array [0..11] of Byte;
const
  Code : TCode = (
    $66, $58,               { pop eax         }
    $68, $FF, $FF,          { push $FFFF      } { OldBP  }
    $66, $50,               { push eax        }
    $EA, $EE, $EE, $FF, $FF { jmp $FFFF:$EEEE } { Action }
  );*)
 
function l3LocalStub(Action: Pointer): Pointer;
                     {eax}
  register;
  {-}
asm
          push edi                               { Save edi                }
          push eax                               { Save Action             }
          call l3AllocStub
          {! --- !}
          {xor  eax, eax                          { 0 -> eax                }
          {push 16                                { SizeOf(TCode) -> stack  }
          {push eax                               { GMem_Fixed -> stack     }
          {call GlobalAlloc}
          {! --- !}
 
          { Создаем новый код: }
          mov  edi, eax                          { Handle -> edi           }
          mov  edx, eax                          { Handle -> edx           }
          cld                                    { Move forward            }
 
          mov  eax, $68
          stosb
          mov  eax, ebp                          { предыдущий ebp -> eax   }
          stosd                                  { "push OldBP" -> [edi]   }
 
          mov  eax, $B9
          stosb
          pop  eax                               { Action -> eax           }
          stosd                                  { "mov ecx, Action" -> [edi] }
 
          mov  eax, $D1FF
          stosw                                  { "call ecx" -> [edi]     }
 
          mov  eax, $59
          stosb                                  { "pop ecx" -> es:[di]    }
 
          mov  eax, $C3
          stosb                                  { "ret" -> [edi]          }
 
          mov  eax, edx                          { Handle -> eax           }
          pop  edi                               { Restore edi             }
end;{asm}
 
function  l3L2IA(Action: Pointer): Tl3IteratorAction;
                {eax}
  register;
  {-}
asm
          jmp  l3LocalStub
end;{asm}
 
procedure l3FreeIA(Stub: Tl3IteratorAction);
                  {eax}
  register;
  {-}
asm
          jmp  l3FreeLocalStub
end;{asm}
 
теперь простейшая реализация итератора:
 
procedure Tl3VList.Iterate(aLo, aHi: Tl3Index; Action: Tl3IteratorAction);
  {virtual;{!v19}         {edx, ecx}
  register;
  {-}
(*asm
         push ebx
         mov  ebx, eax
         mov  eax, [eax].Tl3VList.f_Count
         or   eax, eax
         jle  @@ret // список пуст
 
         dec  eax
         cmp  ecx, eax
         jle  @@aHiLECount
         mov  ecx, eax
@@aHiLECount:
 
         mov  eax, [ebx].Tl3VList.f_List
         or   eax, eax
         jz   @@ret // список пуст
 
         or   edx, edx
         jge  @@aLoGE0
         xor  edx, edx
@@aLoGE0:
         sub  ecx, edx
         jl   @@ret // верхний индекс меньше нижнего
 
         mov  ebx, edx
         shl  ebx, 2
         add  eax, ebx
 
         pop  ebx
         inc  ecx
 
@@loop:
         push eax
         push edx
         push ecx
 
         call Action
 
         pop  ecx
         pop  edx
 
         or   al, al
         jz   @@loopend
 
         pop  eax
         add  eax, 4
         inc  edx
 
         loop @@loop
 
         jmp  @@ex
@@loopend:
         pop  eax
         jmp  @@ex
@@ret:
         pop  ebx
@@ex:
end;//asm*)
var
 i, j, k : Long;
 l_TmpItem : Pointer;
begin
 if (f_List <> nil) then begin
  j := Max(0, aLo);
  k := Min(Pred(Count), aHi);
  if IsMultiThread then
   for i := j to k do begin
    l_TmpItem := Items[i];
    if not Action(@l_TmpItem, i) then break;
   end
  else
   for i := j to k do
    if not Action(PChar(f_List) + i * SizeOf(Pointer), i) then break;
 end;{f_List <> nil}
end;
 
procedure Tl3VStorage.IterateF(I1, I2: Tl3Index; Action: Tl3IteratorAction);
  {-}
begin
 try
  Iterate(I1, I2, Action);
 finally
  l3FreeIA(Action);
 end;{try..finally}
end;
 
и его вызов:
 
function Tl3VList.IndexOf(Item: Pointer): LongInt;
 
 function FindItem(P: PPointer; Index: Long): Bool; far;
 begin
  if (P^ = Item) then begin
   IndexOf := Index;
   Result := false;
  end else
   Result := true;
 end;
 
begin
 Result := -1;
 IterateAllF(l3L2IA(@FindItem));
end;


- забавно, что метод Iterate можно вызывать как для глобального, так и для локального метода (естественно с предшествующим вызовом l3L2IA).
в секции finalization модуля где живет l3L2IA надо не забыть вызвать метод: l3FreeStubs.
- это схематично идеи, просто выдирать все целиком из своей библиотеки - тяжело да и некогда.

-- Прислал: Alex W. Lulin lulin@garant.ru http://lulinalex.chat.ru --