Leak Watcher Tool - Overloading the memory allocation - delphi

I'm developing a Tool to help me find memory leak on my application.
The feature runs well while I'm working with object (TOBJECT), but I'm getting some problems while I'm working with buffer.
In SOME cases, that I could not identify I got some errors in my application, this errors seems to be some bad access to memory. I could not find any possible error on my logic or my code. If some one with more experience with Delphi can help me. Maybe some memory manager particular behavior is causing the problem.
A little more explanations:
Memory Allocation Control
Objective: Count how much memory buffer with determined size is all allocated by the system, exemple:
Buffer Size | Amount of Allocs | Total Memory Used
325 | 35265 | 11461125
23 | 32 | 736
... | ... | ...
How I control the memory allocation and deallocation:
I created an array of integer that goes from 0 to 65365. This array will be used to keep the amount of allocs of the corresponding size.
For example, If I call GetMem for a buffer of 523, the Array[523] will increase + 1.
The GetMem, ReallocMem, AllocMem, the problem is easy to resolve 'cause one of it's parameters is the size of the buffer. So I can use this to increase the position of the array.
The problem cames with the FreeMem, 'cause the only parameter is the pointer of the buffer. I don't know it's size.
- I can't create a list to keep the Pointer and it's size. 'Cause there is SO much allocations, it will be so much expensive to the application keep searching/adding/removing items from this list. And this list must to be protected with critical section etc etc. So no way.
How I'm trying to solve this problem:
Just to remeber I created the array to keep the number off allocations.
Items: 0 65365
|................................|
Addess: $X $(65365x SizeOf(Integer))
When allocators methos are called, for example: GetMem(52);
I changed the behavior of it, I will alloc the requested size (52), but I'll add here a size of an integer;
So I will have:
0 4 56
|.....|...........................|
$x
In the plus space (0..3) I'll set the address of the corresponding space of the array. In this case the address position $array(52). And I add + (SizeOf(Integer)) to the address result of the GetMem, so it will have access just the 52 bytes that were asked for.
When the FreeMem are called. What I do is:
- Get the pointer asked for deallocation.
- Decrease the pointer by the size of the integer
- Check if the address of the current pointer is relative to the Array of control address.
- If it is, I use the the address and decrease 1 from the Array position
- And ask for the FreeMem
In the biggest part of time and systems it's working very well. BUT, In some moments that I really don't know how and were I get some strange errors in system. Erros that I NEVER get if I deactive this implementation.
I'm commenting the code to get easier to be understood, but It's not a hard code so, here it's:
Other thread in: https://forums.embarcadero.com/thread.jspa?threadID=77787
unit uInstancesAnalyser;
{
Functionality: The feature developed in this unit try to watch how the memory are being allocated by your system. The main focus of it is help to find memory leak in the most non intrusive way.
How to Install: Put this unit as the first unit of yout project. If use use a third memory manager put this unit just after the unit of your memory manager.
How to get it's report: It's not the final version of this unit, so the viewer was not developed. By the momento you can call the
method SaveInstancesToFile. It'll create a text file called MemReport in the executable path.
WARNING: If you use the pointer of the VMT destinated to vmtAutoTable, you should not use the directive TRACEINSTANCES.
How it works:
The feature work in two different approaches:
1) Map the memory usage by objects
2) Map the memory usage by buffers (Records, strings and so on)
How are Objects tracked:
The TObject.NewInstance was replaced by a new method (TObjectHack.NNewInstanceTrace).
So when the creation of an object is called it's redirect to the new method. In this new method is increased the counter of the relative class and change the method in the VMT that is responsible to free the object to a new destructor method (vmtFreeInstance). This new destructor call the decrease of the counter and the old destructor.
This way I can know how much of objects of each class are alive in the system.
(More details about how it deep work can be found in the comments on the code)
How are Memory Buffer Traced:
The GetMem, FreeMem, ReallocMem, AllocMem were replaced by new method that have an special behavior to help track the buffers.
As the memory allocation use the same method to every kind of memory request, I'm not able to create a single counter to each count of buffer. So, I calculate them base on it size. First I create a array of integer that start on 0 and goes to 65365.
When the system ask me to give it a buffer of 65 bytes, I increase the position 65 of the array and the buffer is deallocated I call the decrease of the position of the array corresponding to buffer size. If the size requested to the buffer is bigger or equal to 65365, I'll use the position 65365 of the array.
(More details about how it deep work can be found in the comments on the code)
--------------------------------------------------------------------------------------
Develop by Rodrigo Farias Rezino
E-mail: rodrigofrezino#gmail.com
Stackoverflow: http://stackoverflow.com/users/225010/saci
Please, any bug let me know
}
interface
{$DEFINE TRACEBUFFER} {Directive used to track buffer} //Comment to inactive
{$DEFINE TRACEINSTANCES} {Directive used to track objects} //Comment to inactive
//{$DEFINE WATCHTHREADS} // It's not finished
uses
Classes, SyncObjs, uIntegerList;
{You can register possibles names for some Buffers Sizes, it can be useful when you are working with record. Example
TRecordTest = record
Field1: Integer
Field2: string[50]
So, you can call RegisterNamedBuffer(TRecordTest, SizeOf(TRecordTest));
This way, in on the report of buffer/objects will be explicit what possibles named buffer can be that memory in use.}
procedure RegisterNamedBuffer(ABufferName: string; ASize: integer);
{This function return the possible named buffers registered with that size}
function GetBufferName(ASize: integer): string;
{It's a simple output to save the report of memory usage on the disk. It'll create a file called test.txt in the executable directory}
procedure SaveInstancesToFile;
var
{Flag to say if the memory watcher is on or off}
SIsMemoryWatcherActive: Boolean;
implementation
uses
Windows, SysUtils, TypInfo;
const
SIZE_OF_INT = SizeOf(Integer);
SIZE_OF_MAP = 65365;
{$IFDEF WATCHTHREADS}
GAP_SIZE = SIZE_OF_INT * 2;
{$ELSE}
GAP_SIZE = SIZE_OF_INT;
{$ENDIF}
type
TArrayOfMap = array [0..SIZE_OF_MAP] of Integer;
TThreadMemory = array [0..SIZE_OF_MAP] of Integer;
{This class is used to Register}
TNamedBufferList = class(TIntegerList)
public
constructor Create;
function GetBufferName(ASize: integer): string;
procedure RegisterNamedBuffer(ABufferName: string; ASize: integer);
end;
PClassVars = ^TClassVars;
TClassVars = class(TObject)
private
class var ListClassVars: TList;
public
BaseInstanceCount: Integer;
BaseClassName: string;
BaseParentClassName: string;
BaseInstanceSize: Integer;
OldVMTFreeInstance: Pointer;
constructor Create;
class procedure SaveToDisk;
end;
TNamedBuffer = class(TObject)
Names: string;
end;
PJump = ^TJump;
TJump = packed record
OpCode: Byte;
Distance: Pointer;
end;
TObjectHack = class(TObject)
private
FCriticalSection: TCriticalSection;
class procedure SetClassVars(AClassVars: TClassVars); //inline;
class function GetClassVars: TClassVars; inline;
procedure IncCounter; inline;
procedure DecCounter; inline;
procedure CallOldFunction;
public
constructor Create;
destructor Destroy; override;
class function NNewInstance: TObject;
class function NNewInstanceTrace: TObject;
procedure NFreeInstance;
end;
var
SDefaultGetMem: function(Size: Integer): Pointer;
SDefaultFreeMem: function(P: Pointer): Integer;
SDefaultReallocMem: function(P: Pointer; Size: Integer): Pointer;
SDefaultAllocMem: function(Size: Cardinal): Pointer;
SThreadMemory: TThreadMemory;
SMap: TArrayOfMap;
SNamedBufferList: TNamedBufferList;
{$IFDEF WATCHTHREADS}
SMissedMemoryFlow: Integer;
{$ENDIF}
{$REGION 'Util'}
type
TWinVersion = (wvUnknown, wv95, wv98, wv98SE, wvNT, wvME, wv2000, wvXP, wvVista, wv2003, wv7);
function GetWinVersion: TWinVersion;
var
osVerInfo: TOSVersionInfo;
majorVersion, minorVersion: Integer;
begin
Result := wvUnknown;
osVerInfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
if GetVersionEx(osVerInfo) then
begin
minorVersion := osVerInfo.dwMinorVersion;
majorVersion := osVerInfo.dwMajorVersion;
case osVerInfo.dwPlatformId of
VER_PLATFORM_WIN32_NT:
begin
if majorVersion <= 4 then
Result := wvNT
else if (majorVersion = 5) and (minorVersion = 0) then
Result := wv2000
else if (majorVersion = 5) and (minorVersion = 1) then
Result := wvXP
else if (majorVersion = 5) and (minorVersion = 2) then
Result := wv2003
else if (majorVersion = 6) then
Result := wvVista
else if (majorVersion = 7) then
Result := wv7;
end;
VER_PLATFORM_WIN32_WINDOWS:
begin
if (majorVersion = 4) and (minorVersion = 0) then
Result := wv95
else if (majorVersion = 4) and (minorVersion = 10) then
begin
if osVerInfo.szCSDVersion[1] = 'A' then
Result := wv98SE
else
Result := wv98;
end
else if (majorVersion = 4) and (minorVersion = 90) then
Result := wvME
else
Result := wvUnknown;
end;
end;
end;
end;
function GetMethodAddress(AStub: Pointer): Pointer;
const
CALL_OPCODE = $E8;
begin
if PBYTE(AStub)^ = CALL_OPCODE then
begin
Inc(Integer(AStub));
Result := Pointer(Integer(AStub) + SizeOf(Pointer) + PInteger(AStub)^);
end
else
Result := nil;
end;
procedure AddressPatch(const ASource, ADestination: Pointer);
const
JMP_OPCODE = $E9;
SIZE = SizeOf(TJump);
var
NewJump: PJump;
OldProtect: Cardinal;
begin
if VirtualProtect(ASource, SIZE, PAGE_EXECUTE_READWRITE, OldProtect) then
begin
NewJump := PJump(ASource);
NewJump.OpCode := JMP_OPCODE;
NewJump.Distance := Pointer(Integer(ADestination) - Integer(ASource) - 5);
FlushInstructionCache(GetCurrentProcess, ASource, SizeOf(TJump));
VirtualProtect(ASource, SIZE, OldProtect, #OldProtect);
end;
end;
function PatchCodeDWORD(ACode: PDWORD; AValue: DWORD): Boolean;
var
LRestoreProtection, LIgnore: DWORD;
begin
Result := False;
if VirtualProtect(ACode, SizeOf(ACode^), PAGE_EXECUTE_READWRITE, LRestoreProtection) then
begin
Result := True;
ACode^ := AValue;
Result := VirtualProtect(ACode, SizeOf(ACode^), LRestoreProtection, LIgnore);
if not Result then
Exit;
Result := FlushInstructionCache(GetCurrentProcess, ACode, SizeOf(ACode^));
end;
end;
{$ENDREGION}
function GetInstanceList: TList;
begin
Result := TClassVars.ListClassVars;
end;
procedure SaveInstancesToFile;
begin
TClassVars.SaveToDisk;
end;
procedure OldNewInstance;
asm
call TObject.NewInstance;
end;
procedure OldAfterConstruction;
asm
call TObject.InitInstance;
end;
{ TObjectHack }
type
TExecute = procedure of object;
procedure TObjectHack.CallOldFunction;
var
Routine: TMethod;
Execute: TExecute;
begin
Routine.Data := Pointer(Self);
Routine.Code := GetClassVars.OldVMTFreeInstance;
Execute := TExecute(Routine);
Execute;
end;
constructor TObjectHack.Create;
begin
end;
procedure TObjectHack.DecCounter;
var
ThreadId: Cardinal;
begin
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] - Self.InstanceSize;
ThreadId := 0;
if (Self.ClassType.InheritsFrom(TThread)) then
ThreadId := TThread(Self).ThreadID;
{$ENDIF}
GetClassVars.BaseInstanceCount := GetClassVars.BaseInstanceCount -1;
CallOldFunction;
{$IFDEF WATCHTHREADS}
if ThreadId <> 0 then
begin
if SThreadMemory[ThreadId] < 0 then
SMissedMemoryFlow := SMissedMemoryFlow + SThreadMemory[ThreadId];
SThreadMemory[ThreadId] := 0;
end;
{$ENDIF}
end;
destructor TObjectHack.Destroy;
begin
inherited;
end;
class function TObjectHack.GetClassVars: TClassVars;
begin
Result := PClassVars(Integer(Self) + vmtAutoTable)^;
end;
function _InitializeHook(AClass: TClass; AOffset: Integer; HookAddress: Pointer): Boolean;
var
lAddress: Pointer;
lProtect: DWord;
begin
lAddress := Pointer(Integer(AClass) + AOffset);
Result := VirtualProtect(lAddress, 4, PAGE_READWRITE, #lProtect);
if not Result then
Exit;
CopyMemory(lAddress, #HookAddress, 4);
Result := VirtualProtect(lAddress, 4, lProtect, #lProtect);
end;
class procedure TObjectHack.SetClassVars(AClassVars: TClassVars);
begin
AClassVars.BaseClassName := Self.ClassName;
AClassVars.BaseInstanceSize := Self.InstanceSize;
AClassVars.OldVMTFreeInstance := PPointer(Integer(TClass(Self)) + vmtFreeInstance)^;
if Self.ClassParent <> nil then
AClassVars.BaseParentClassName := Self.ClassParent.ClassName;
PatchCodeDWORD(PDWORD(Integer(Self) + vmtAutoTable), DWORD(AClassVars));
_InitializeHook(Self, vmtFreeInstance, #TObjectHack.DecCounter);
end;
procedure RegisterClassVarsSupport(const Classes: array of TObjectHack);
var
LClass: TObjectHack;
begin
for LClass in Classes do
if LClass.GetClassVars = nil then
LClass.SetClassVars(TClassVars.Create)
else
raise Exception.CreateFmt('Class %s has automated section or duplicated registration.', [LClass.ClassName]);
end;
procedure TObjectHack.IncCounter;
begin
if GetClassVars = nil then
RegisterClassVarsSupport(Self);
GetClassVars.BaseInstanceCount := GetClassVars.BaseInstanceCount + 1;
end;
{ TClassVars }
constructor TClassVars.Create;
begin
ListClassVars.Add(Self);
end;
class procedure TClassVars.SaveToDisk;
var
LStringList: TStringList;
i: Integer;
begin
LStringList := TStringList.Create;
try
LStringList.Add('CLASS | NUMBER OF INSTANCES');
{$IFDEF TRACEINSTANCES}
for i := 0 to ListClassVars.Count -1 do
if TClassVars(ListClassVars.Items[I]).BaseInstanceCount > 0 then
LStringList.Add(TClassVars(ListClassVars.Items[I]).BaseClassName + '|' + IntToStr(TClassVars(ListClassVars.Items[I]).BaseInstanceCount));
{$ENDIF}
{$IFDEF TRACEBUFFER}
for I := 0 to SIZE_OF_MAP do
if SMap[I] > 0 then
LStringList.Add(Format('Mem. Size: %d | Amount: %d', [I, SMap[I]]));
{$ENDIF}
LStringList.SaveToFile(ExtractFilePath(ParamStr(0)) + 'MemReport.txt');
finally
FreeAndNil(LStringList);
end;
end;
//////////////////////////////////////////////////////////////////////////////////////
/// Memory manager controller
function IsInMap(AValue: Integer): Boolean; inline;
begin
try
Result := (AValue > Integer(#SMap)) and (AValue <= Integer(#SMap[SIZE_OF_MAP]));
except
Result := False;
end;
end;
function MemorySizeOfPos(APos: Integer): Integer; inline;
begin
Result := (APos - Integer(#SMap)) div SIZE_OF_INT;
end;
function NAllocMem(Size: Cardinal): Pointer;
var
pIntValue: ^Integer;
MapSize: Integer;
ThreadId: Cardinal;
begin
if Size > SIZE_OF_MAP then
MapSize := SIZE_OF_MAP
else
MapSize := Size;
Result := SDefaultAllocMem(Size + GAP_SIZE);
pIntValue := Result;
SMap[MapSize] := SMap[MapSize] + 1;
pIntValue^ := Integer(#SMap[MapSize]);
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] + Size;
pIntValue := Pointer(Integer(Result) + SIZE_OF_INT);
pIntValue^ := ThreadId;
{$ENDIF}
Result := Pointer(Integer(Result) + GAP_SIZE);
end;
function NGetMem(Size: Integer): Pointer;
var
LPointer: Pointer;
pIntValue: ^Integer;
MapSize: Integer;
ThreadId: Cardinal;
begin
if Size > SIZE_OF_MAP then
MapSize := SIZE_OF_MAP
else
MapSize := Size;
LPointer := SDefaultGetMem(Size + GAP_SIZE);
pIntValue := LPointer;
SMap[MapSize] := SMap[MapSize] + 1;
pIntValue^ := Integer(#SMap[MapSize]);
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] + Size;
pIntValue := Pointer(Integer(LPointer) + SIZE_OF_INT);
pIntValue^ := ThreadId;
{$ENDIF}
Result := Pointer(Integer(LPointer) + GAP_SIZE);
end;
function NFreeMem(P: Pointer): Integer;
var
pIntValue: ^Integer;
LPointer: Pointer;
ThreadId: Cardinal;
LFreed: Boolean;
begin
LPointer := Pointer(Integer(P) - GAP_SIZE);
pIntValue := LPointer;
if IsInMap(pIntValue^) then
begin
{$IFDEF WATCHTHREADS}
ThreadId := Integer(Pointer(Integer(pIntValue) + SIZE_OF_INT)^);
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] - MemorySizeOfPos(pIntValue^);
{$ENDIF}
Integer(Pointer(pIntValue^)^) := Integer(Pointer(pIntValue^)^) - 1;
Result := SDefaultFreeMem(LPointer);
end
else
Result := SDefaultFreeMem(P);
end;
function NReallocMem(P: Pointer; Size: Integer): Pointer;
var
pIntValue: ^Integer;
LPointer: Pointer;
LSizeMap: Integer;
ThreadId: Cardinal;
begin
LPointer := Pointer(Integer(P) - GAP_SIZE);
pIntValue := LPointer;
if not IsInMap(pIntValue^) then
begin
Result := SDefaultReallocMem(P, Size);
Exit;
end;
if Size > SIZE_OF_MAP then
LSizeMap := SIZE_OF_MAP
else
LSizeMap := Size;
//Uma vez com o valor setado, não pode remover o setor
Integer(Pointer(pIntValue^)^) := Integer(Pointer(pIntValue^)^) - 1;
{$IFDEF WATCHTHREADS}
ThreadId := Integer(Pointer(Integer(pIntValue) + SIZE_OF_INT)^);
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] - MemorySizeOfPos(pIntValue^) + Size;
{$ENDIF}
Result := SDefaultReallocMem(LPointer, Size + GAP_SIZE);
SMap[LSizeMap] := SMap[LSizeMap] + 1;
pIntValue := Result;
pIntValue^ := Integer(#SMap[LSizeMap]);
Result := Pointer(Integer(Result) + GAP_SIZE);
end;
procedure TObjectHack.NFreeInstance;
var
ThreadId: Cardinal;
begin
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] - Self.InstanceSize;
{$ENDIF}
CleanupInstance;
SDefaultFreeMem(Self);
end;
class function TObjectHack.NNewInstance: TObject;
var
ThreadId: Cardinal;
begin
Result := InitInstance(SDefaultGetMem(Self.InstanceSize));
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] + Self.InstanceSize;
{$ENDIF}
end;
class function TObjectHack.NNewInstanceTrace: TObject;
var
ThreadId: Cardinal;
begin
try
Result := InitInstance(SDefaultGetMem(Self.InstanceSize));
if (Result.ClassType = TClassVars) or (Result is EExternal) then
Exit;
TObjectHack(Result).IncCounter;
{$IFDEF WATCHTHREADS}
ThreadId := GetCurrentThreadId;
SThreadMemory[ThreadId] := SThreadMemory[ThreadId] + Self.InstanceSize;
{$ENDIF}
except
raise Exception.Create(Result.ClassName);
end;
end;
procedure InitializeArray;
var
I: Integer;
begin
for I := 0 to SIZE_OF_MAP do
SMap[I] := 0;
end;
type
PLocalTest = ^LocalTest;
LocalTest = record
Size: integer;
Size2: string;
end;
procedure ApplyMemoryManager;
var
LMemoryManager: TMemoryManagerEx;
begin
GetMemoryManager(LMemoryManager);
SDefaultGetMem := LMemoryManager.GetMem;
{$IFNDEF TRACEBUFFER}
Exit;
{$ENDIF}
LMemoryManager.GetMem := NGetMem;
SDefaultFreeMem := LMemoryManager.FreeMem;
LMemoryManager.FreeMem := NFreeMem;
SDefaultReallocMem := LMemoryManager.ReallocMem;
LMemoryManager.ReallocMem := NReallocMem;
SDefaultAllocMem := LMemoryManager.AllocMem;
LMemoryManager.AllocMem := NAllocMem;
SetMemoryManager(LMemoryManager);
end;
procedure TestRecord;
var
LTest: PLocalTest;
begin
LTest := AllocMem(1);
Dispose(LTest);
LTest := AllocMem(SIZE_OF_MAP + 1);
Dispose(LTest);
New(LTest);
ReallocMem(LTest, SIZE_OF_MAP +1);
Dispose(LTest);
end;
procedure TesteObject;
var
LTestObject: TObject;
begin
LTestObject := TObject.Create;
LTestObject.Free;
end;
{ TNamedBuffer }
constructor TNamedBufferList.Create;
begin
inherited Create;
Sorted := True;
end;
function GetBufferName(ASize: integer): string;
begin
Result := SNamedBufferList.GetBufferName(ASize);
end;
procedure RegisterNamedBuffer(ABufferName: string; ASize: integer);
begin
SNamedBufferList.RegisterNamedBuffer(ABufferName, ASize);
end;
function TNamedBufferList.GetBufferName(ASize: integer): string;
var
LIndex: Integer;
begin
Result := 'Unknow';
LIndex := IndexOf(ASize);
if LIndex = -1 then
Exit;
Result := TNamedBuffer(Objects[LIndex]).Names;
end;
procedure TNamedBufferList.RegisterNamedBuffer(ABufferName: string; ASize: integer);
var
LIndex: Integer;
LNamedBuffer: TNamedBuffer;
begin
LIndex := IndexOf(ASize);
if LIndex = -1 then
begin
LNamedBuffer := TNamedBuffer.Create;
LNamedBuffer.Names := 'Possible types: ' + ABufferName;
Self.AddObject(ASize, LNamedBuffer);
end
else
TNamedBuffer(Objects[LIndex]).Names := TNamedBuffer(Objects[LIndex]).Names + ' | ' + ABufferName;
end;
procedure InitializeAnalyser;
var
LCan: Boolean;
begin
SIsMemoryWatcherActive := False;
SNamedBufferList := TNamedBufferList.Create;
case GetWinVersion of
wv98, wvXP, wvVista, wv7: LCan := True;
else LCan := False;
end;
if not LCan then
Exit;
{$IFDEF TRACEINSTANCES}
TClassVars.ListClassVars := TList.Create;
{$ENDIF}
{$IFDEF TRACEBUFFER}
InitializeArray;
{$ENDIF}
ApplyMemoryManager;
/// Buffer wrapper
{$IFDEF TRACEBUFFER}
TestRecord;
{$IFNDEF TRACEINSTANCES}
AddressPatch(GetMethodAddress(#OldNewInstance), #TObjectHack.NNewInstance);
{$ENDIF}
{$ENDIF}
///Class wrapper
{$IFDEF TRACEINSTANCES}
AddressPatch(GetMethodAddress(#OldNewInstance), #TObjectHack.NNewInstanceTrace);
TesteObject;
{$ENDIF}
SIsMemoryWatcherActive := True;
end;
{ TThreadDestroy }
initialization
InitializeAnalyser
end.

I was not cleaning the parity byte on FreeMem.
I continue working on this, if someone would like to take a look or help: http://rfrezinos.wordpress.com/delphi-memory-profiler/
Att.

Related

Is it possible to change the value returned by Vcl.Forms.TScreen.WorkAreaRect?

We are starting to use Thinfinity UI to virtualize our application in a browser. When virtualised, the main window of our application is maximised to the bounds of the browser canvas. This means that, in effect, our desktop is reduced to the size of the browser canvas. It also means that when controls such as popup menus are positioned they often exceed the bounds of the browser canvas.
I believe we can overcome this issue if we can set the result of calls made to Vcl.Forms.TScreen.WorkAreaRect to the bounds of the browser canvas. Is this possible?
Based on How to change the implementation (detour) of an externally declared function from #GadDLord, you could hook SystemParametersInfoW which is used in TScreen.GetWorkAreaRect.
I copied his part of code to prevent dead links.
type
//strctures to hold the address and instructions to patch
TJumpOfs = Integer;
PPointer = ^Pointer;
PXRedirCode = ^TXRedirCode;
TXRedirCode = packed record
Jump: Byte;
Offset: TJumpOfs;
end;
PAbsoluteIndirectJmp = ^TAbsoluteIndirectJmp;
TAbsoluteIndirectJmp = packed record
OpCode: Word;
Addr: PPointer;
end;
var
DataCompareBackup: TXRedirCode; //Store the original address of the function to patch
//get the address of a procedure or method of a function
function GetActualAddr(Proc: Pointer): Pointer;
begin
if Proc <> nil then
begin
if (Win32Platform = VER_PLATFORM_WIN32_NT) and (PAbsoluteIndirectJmp(Proc).OpCode = $25FF) then
Result := PAbsoluteIndirectJmp(Proc).Addr^
else
Result := Proc;
end
else
Result := nil;
end;
//patch the original function or procedure
procedure HookProc(Proc, Dest: Pointer; var BackupCode: TXRedirCode);
var
n: {$IFDEF VER230}NativeUInt{$ELSE}DWORD{$ENDIF};
Code: TXRedirCode;
begin
Proc := GetActualAddr(Proc);
Assert(Proc <> nil);
//store the address of the original procedure to patch
if ReadProcessMemory(GetCurrentProcess, Proc, #BackupCode, SizeOf(BackupCode), n) then
begin
Code.Jump := $E9;
Code.Offset := PAnsiChar(Dest) - PAnsiChar(Proc) - SizeOf(Code);
//replace the target procedure address with the new one.
WriteProcessMemory(GetCurrentProcess, Proc, #Code, SizeOf(Code), n);
end;
end;
//restore the original address of the hooked function or procedure
procedure UnhookProc(Proc: Pointer; var BackupCode: TXRedirCode);
var
n: {$IFDEF VER230}NativeUInt{$ELSE}Cardinal{$ENDIF};
begin
if (BackupCode.Jump <> 0) and (Proc <> nil) then
begin
Proc := GetActualAddr(Proc);
Assert(Proc <> nil);
WriteProcessMemory(GetCurrentProcess, Proc, #BackupCode, SizeOf(BackupCode), n);
BackupCode.Jump := 0;
end;
end;
function MySystemParametersInfo(uiAction, uiParam: UINT;
pvParam: Pointer; fWinIni: UINT): BOOL; stdcall;
begin
Result := SystemParametersInfoA(uiAction, uiParam,pvParam,fWinIni);
if uiAction=SPI_GETWORKAREA then
begin
// Fake just for demo
TRect(pvParam^).Right := 1234;
end
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
Caption := IntToStr(Screen.WorkAreaRect.Right)
end;
initialization
HookProc( #SystemParametersInfoW, #MySystemParametersInfo, DatacompareBackup);
finalization
UnHookProc( #SystemParametersInfoW, DatacompareBackup);

Check if memory is readable or why do it not catches the exception?

I have this code that gets called from an injected DLL from a foreign process. It sould read some memory ranges but I sometimes get a segmentation fault at this line DataBuffer := TCharPointer(Address + CharOffset)^;. So is there any way to check if the memory is readable?
function GetCurrentData(Address: Pointer): PChar;
var
DataBuffer: Char;
CharArray: Array of Char;
CharOffset: Integer;
ReadBytes: longword;
begin
CharOffset := 0;
SetLength(CharArray, 0);
repeat
DataBuffer := TCharPointer(Address + CharOffset)^;
CharOffset := CharOffset + 1;
SetLength(CharArray, CharOffset);
CharArray[CharOffset - 1] := DataBuffer;
until (Ord(DataBuffer) = 0);
Result := PChar(#CharArray[0]);
end;
i also tryed to catch the exception but for some reason this is not working. The host programm still crashes.
unit UnitEventBridgeExports;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Windows, ShellAPI, JwaTlHelp32, SimpleIPC;
type
TCharPointer = ^Char;
const
WOWEXE = 'TestProgramm.exe';
var
IPCClient: TSimpleIPCClient;
PID: DWord;
Process: THandle;
procedure EventCalled;
procedure InitializeWoWEventBridge; stdcall;
implementation
function GetProcessIDByName(Exename: String): DWord;
var
hProcSnap: THandle;
pe32: TProcessEntry32;
begin
Result := 0;
hProcSnap := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
if hProcSnap <> INVALID_HANDLE_VALUE then
begin
pe32.dwSize := SizeOf(ProcessEntry32);
if Process32First(hProcSnap, pe32) = True then
begin
while Process32Next(hProcSnap, pe32) = True do
begin
if pos(Exename, pe32.szExeFile) <> 0 then
Result := pe32.th32ProcessID;
end;
end;
CloseHandle(hProcSnap);
end;
end;
procedure InitializeEventBridge; stdcall;
begin
IPCClient := TSimpleIPCClient.Create(nil);
IPCClient.ServerID := 'EventBridgeServer';
IPCClient.Active := True;
IPCClient.SendStringMessage('init');
PID := GetProcessIDByName(EXE);
Process := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
end;
function GetCurrentData(Address: Pointer): PChar;
var
DataBuffer: Char;
CharArray: Array of Char;
CharOffset: Integer;
ReadBytes: longword;
CharPointer: TCharPointer;
BreakLoop: Boolean;
begin
CharOffset := 0;
SetLength(CharArray, 0);
BreakLoop := False;
repeat
try
CharPointer := TCharPointer(Address + CharOffset);
DataBuffer := CharPointer^;
CharOffset := CharOffset + 1;
SetLength(CharArray, CharOffset);
CharArray[CharOffset - 1] := DataBuffer;
except
BreakLoop := True;
end;
until (Ord(DataBuffer) = 0) or BreakLoop;
Result := PChar(#CharArray[0]);
end;
procedure EventCalled;
var
TmpAddress: Pointer;
StringData: PChar;
begin
{$ASMMODE intel}
asm
mov [TmpAddress], edi
end;
StringData := GetCurrentData(TmpAddress);
IPCClient.SendStringMessage('update:' + StringData);
//IPCClient.SendStringMessage('update');
end;
end.
Your GetCurrentData() implementation is returning a pointer to a local array that goees out of scope when the function exits, then EventCalled() tries to use that poiner after it is no longer valid. Try this instead:
function GetCurrentData(Address: Pointer): AnsiString;
var
Offset: Integer;
begin
Result := '';
Offset := 0;
repeat
try
if PByte(Longint(Address) + Offset)^ = #0 then Break;
Inc(Offset);
except
Break;
end;
until False;
SetString(Result, PAnsiChar(Address), Offset);
end;
procedure EventCalled;
var
TmpAddress: Pointer;
StringData: AnsiString;
begin
{$ASMMODE intel}
asm
mov [TmpAddress], edi
end;
StringData := GetCurrentData(TmpAddress);
IPCClient.SendStringMessage('update:' + StringData);
//IPCClient.SendStringMessage('update');
end;
IsBadReadPtr API is here to help. You give address and size, and you get the readability back. Raymond Chen suggests to never use it though.
Other than that, VirtualQuery should give you information about the address in question to tell its readability.
Since Ken in comments below re-warned about danger of IsBadReadPtr, I bring it up to the answer to not pass by. Be sure to read the comments and links to Raymdond's blog. Be sure to see also:
Most efficient replacement for IsBadReadPtr?
How to check if a pointer is valid?

async file I/O in Delphi

in this article delphi.net(prism) support async file io.
Delphi(Native/VCL) has Async File IO Class too?
Have you seen this code? http://pastebin.com/A2EERtyW
It is a good start for ansynchronous file I/O, but personally I would write a wrapper around the standard TStream class to maintain compatibility with VCL/RTL.
EDIT 2: This one looks good, too. http://www.torry.net/vcl/filedrv/other/dstreams.zip
I am posting it here just in case it disappears from Pastebin:
unit xfile;
{$I cubix.inc}
interface
uses
Windows,
Messages,
WinSock,
SysUtils,
Classes;
const
MAX_BUFFER = 1024 * 32;
type
TFileReadEvent = procedure(Sender: TObject; const Buffer; Count: Integer) of object;
TAsyncFile = class
private
FHandle: THandle;
FPosition: Cardinal;
FReadPending: Boolean;
FOverlapped: TOverlapped;
FBuffer: Pointer;
FBufferSize: Integer;
FOnRead: TFileReadEvent;
FEof: Boolean;
FSize: Integer;
function ProcessIo: Boolean;
procedure DoOnRead(Count: Integer);
function GetOpen: Boolean;
public
constructor Create(Filename: string; BufferSize: Integer = MAX_BUFFER);
destructor Destroy; override;
procedure BeginRead;
procedure Seek(Position: Integer);
procedure Close;
property OnRead: TFileReadEvent read FOnRead write FOnRead;
property Eof: Boolean read FEof;
property IsOpen: Boolean read GetOpen;
property Size: Integer read FSize;
end;
function ProcessFiles: Boolean;
implementation
var
Files: TList;
function ProcessFiles: Boolean;
var
i: Integer;
AsyncFile: TAsyncFile;
begin
Result := False;
for i := Files.Count - 1 downto 0 do
begin
AsyncFile := TAsyncFile(Files[i]);
Result := AsyncFile.ProcessIo or Result;
end;
end;
procedure Cleanup;
var
i: Integer;
AsyncFile: TAsyncFile;
begin
for i := Files.Count - 1 downto 0 do
begin
AsyncFile := TAsyncFile(Files[i]);
AsyncFile.Free;
end;
Files.Free;
end;
{ TAsyncFile }
constructor TAsyncFile.Create(Filename: string; BufferSize: Integer);
begin
Files.Add(Self);
FReadPending := False;
FBufferSize := BufferSize;
GetMem(FBuffer, FBufferSize);
FillMemory(#FOverlapped, SizeOf(FOverlapped), 0);
Cardinal(FHandle) := CreateFile(
PChar(Filename), // file to open
GENERIC_READ, // open for reading
0, // do not share
nil, // default security
OPEN_EXISTING, // open existing
FILE_ATTRIBUTE_NORMAL, //or // normal file
//FILE_FLAG_OVERLAPPED, // asynchronous I/O
0); // no attr. template
FSize := FileSeek(FHandle, 0, soFromEnd);
FileSeek(FHandle, 0, soFromBeginning);
FPosition := 0;
end;
destructor TAsyncFile.Destroy;
begin
Files.Remove(Self);
CloseHandle(FHandle);
FreeMem(FBuffer);
inherited;
end;
function TAsyncFile.ProcessIo: Boolean;
var
ReadCount: Cardinal;
begin
Result := False; Exit;
if not FReadPending then
begin
Exit;
end;
if GetOverlappedResult(FHandle, FOverlapped, ReadCount, False) then
begin
FReadPending := False;
DoOnRead(ReadCount);
end
else
begin
case GetLastError() of
ERROR_HANDLE_EOF:
begin
FReadPending := False;
FEof := True;
end;
ERROR_IO_PENDING:
begin
FReadPending := True;
end;
0:
begin
Result := True;
end;
end;
end;
end;
procedure TAsyncFile.BeginRead;
var
ReadResult: Boolean;
ReadCount: Cardinal;
begin
ReadCount := 0;
Seek(FPosition);
ReadResult := ReadFile(FHandle, FBuffer^, FBufferSize, ReadCount, nil);//#FOverlapped);
if ReadResult then
begin
FEof := False;
FReadPending := False;
FPosition := FPosition + ReadCount;
DoOnRead(ReadCount);
end
else
begin
case GetLastError() of
ERROR_HANDLE_EOF:
begin
FReadPending := False;
FEof := True;
end;
ERROR_IO_PENDING:
begin
FReadPending := True;
end;
end;
end;
end;
procedure TAsyncFile.DoOnRead(Count: Integer);
begin
if Assigned(FOnRead) then
begin
FOnRead(Self, FBuffer^, Count);
end;
end;
function TAsyncFile.GetOpen: Boolean;
begin
Result := Integer(FHandle) >= 0;
end;
procedure TAsyncFile.Close;
begin
FileClose(FHandle);
end;
procedure TAsyncFile.Seek(Position: Integer);
begin
FPosition := Position;
FileSeek(FHandle, Position, soFromBeginning);
end;
initialization
Files := Tlist.Create;
finalization
Cleanup;
end.
There is nothing built in to the RTL/VCL that offers asynchronous I/O for files. Incidentally the support in Delphi Prism is down to the .net framework rather than being language based.
You can either code directly against the Win32 API (that's not much fun) or hunt around for a Delphi wrapper to that API. Off the top of my head, I don't know any Delphi wrappers of asynchronous file I/O but they must exist.

How to make MessageDlg centered on owner form

I'd like that MessageDlg appear centered on its parent form.
Any suggestions on how to accomplish this in Delphi 2010?
I found the code below here: http://delphi.about.com/od/formsdialogs/l/aa010304a.htm but it's not working for me. The pop-up still is not centered on the owner form. (It's not clear to me how the method would actually know the owner form...)
function TForm1.MessageDlg(const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; HelpCtx: Integer): Integer;
begin
with CreateMessageDialog(Msg, DlgType, Buttons) do
try
Position := poOwnerFormCenter;
Result := ShowModal
finally
Free
end
end;
The dialog doesn't have a relationship with the instance of TForm1. It would not be hard to set the position of the form manually, but I bet someone who is more familiar with this area of the VCL will know how to do it a cleaner way.
Personally I never use the Position property and use my own code to position all my forms because I've never been satisfied with the performance of the Position property.
UPDATE: You can change the owner of the dialog using Self.InsertComponent(Dialog). You'd have to store your dialog into a local variable, say, Dialog, for this to work:
function TForm1.MessageDlg(const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; HelpCtx: Integer): Integer;
var
Dialog: TForm;
begin
Dialog := CreateMessageDialog(Msg, DlgType, Buttons);
try
Self.InsertComponent(Dialog);
Dialog.Position := poOwnerFormCenter;
Result := Dialog.ShowModal
finally
Dialog.Free
end
end;
You can do
function MessageDlg(const AOwner: TForm; const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; HelpCtx: Integer = 0): Integer;
begin
with CreateMessageDialog(Msg, DlgType, Buttons) do
try
Left := AOwner.Left + (AOwner.Width - Width) div 2;
Top := AOwner.Top + (AOwner.Height - Height) div 2;
Result := ShowModal;
finally
Free;
end
end;
and call it like
procedure TForm1.FormClick(Sender: TObject);
begin
MessageDlg(Self, 'This is a test', mtInformation, [mbOK]);
end;
However, I would personally not do this, because the dialog shown by CreateMessageDialog is not a native Windows dialog. Compare the visual result with the native stuff:
procedure TForm1.FormClick(Sender: TObject);
begin
case MessageBox(Handle, PChar('This is a test. Do you wish to do something?'), PChar('A Silly Example'), MB_ICONQUESTION or MB_YESNO) of
ID_YES:
MessageBox(Handle, PChar('Great!'), PChar('A Silly Example'), MB_ICONINFORMATION or MB_OK);
ID_NO:
MessageBox(Handle, PChar('OK, well, I cannot force you...'), PChar('A Silly Example'), MB_ICONINFORMATION or MB_OK);
end;
end;
At least in Windows 7 with the Aero theme enabled, the native dialog looks much better. However, it seems, this cannot be centered over any particular form. Instead, the dialog is centered on the current monitor. But this is also the default behaviour in Windows (try Notepad, WordPad, or Paint), so why do you need this new behaviour?
Why limit this desire to message dialogs? Like David Heffernan commented:
Native dialogs always win!
With the following unit(s), you can center any native dialog, such as: MessageBox, TFindDialog, TOpenDialog, TFontDialog, TPrinterSetupDialog, etc... The main unit provides two routines, both with some optional parameters:
function ExecuteCentered(Dialog: TCommonDialog;
WindowToCenterIn: HWND = 0): Boolean;
function MsgBox(const Text: String; Flags: Cardinal = DefFlags;
const Caption: String = DefCaption;
WindowToCenterIn: HWND = 0): Integer;
Wherelse you would use OpenDialog1.Execute and let Windows decide where to show the dialog, you now use ExecuteCentered(OpenDialog1) and the dialog is centered in the screen's active form:
To show message dialogs, use MsgBox, a wrapper around Application.MessageBox (which in turn is a wrapper around Windows.MessageBox). Some examples:
MsgBox('Hello world!');
MsgBox('Cancel saving?', MB_YESNO or MB_ICONQUESTION or MB_DEFBUTTON2);
MsgBox('Please try again.', MB_OK, 'Error');
MsgBox('I''m centered in the toolbar.', MB_OK, 'Fun!', Toolbar1.Handle);
The units:
unit AwDialogs;
interface
uses
Dialogs, Forms, Windows, Controls, Messages, AwHookInstance, Math, MultiMon;
const
DefCaption = 'Application.Title';
DefFlags = MB_OK;
procedure CenterWindow(WindowToStay, WindowToCenter: HWND);
function GetTopWindow: HWND;
function ExecuteCentered(Dialog: TCommonDialog;
WindowToCenterIn: HWND = 0): Boolean;
function MsgBox(const Text: String; Flags: Cardinal = DefFlags;
const Caption: String = DefCaption;
WindowToCenterIn: HWND = 0): Integer;
implementation
procedure CenterWindow(WindowToStay, WindowToCenter: HWND);
var
R1: TRect;
R2: TRect;
Monitor: HMonitor;
MonInfo: TMonitorInfo;
MonRect: TRect;
X: Integer;
Y: Integer;
begin
GetWindowRect(WindowToStay, R1);
GetWindowRect(WindowToCenter, R2);
Monitor := MonitorFromWindow(WindowToStay, MONITOR_DEFAULTTONEAREST);
MonInfo.cbSize := SizeOf(MonInfo);
GetMonitorInfo(Monitor, #MonInfo);
MonRect := MonInfo.rcWork;
with R1 do
begin
X := (Right - Left - R2.Right + R2.Left) div 2 + Left;
Y := (Bottom - Top - R2.Bottom + R2.Top) div 2 + Top;
end;
X := Max(MonRect.Left, Min(X, MonRect.Right - R2.Right + R2.Left));
Y := Max(MonRect.Top, Min(Y, MonRect.Bottom - R2.Bottom + R2.Top));
SetWindowPos(WindowToCenter, 0, X, Y, 0, 0, SWP_NOACTIVATE or
SWP_NOOWNERZORDER or SWP_NOSIZE or SWP_NOZORDER);
end;
function GetTopWindow: HWND;
begin
Result := GetLastActivePopup(Application.Handle);
if (Result = Application.Handle) or not IsWindowVisible(Result) then
Result := Screen.ActiveCustomForm.Handle;
end;
{ TAwCommonDialog }
type
TAwCommonDialog = class(TObject)
private
FCenterWnd: HWND;
FDialog: TCommonDialog;
FHookProc: TFarProc;
FWndHook: HHOOK;
procedure HookProc(var Message: THookMessage);
function Execute: Boolean;
end;
function TAwCommonDialog.Execute: Boolean;
begin
try
Application.NormalizeAllTopMosts;
FHookProc := MakeHookInstance(HookProc);
FWndHook := SetWindowsHookEx(WH_CALLWNDPROCRET, FHookProc, 0,
GetCurrentThreadID);
Result := FDialog.Execute;
finally
if FWndHook <> 0 then
UnhookWindowsHookEx(FWndHook);
if FHookProc <> nil then
FreeHookInstance(FHookProc);
Application.RestoreTopMosts;
end;
end;
procedure TAwCommonDialog.HookProc(var Message: THookMessage);
var
Data: PCWPRetStruct;
Parent: HWND;
begin
with Message do
if nCode < 0 then
Result := CallNextHookEx(FWndHook, nCode, wParam, lParam)
else
Result := 0;
if Message.nCode = HC_ACTION then
begin
Data := PCWPRetStruct(Message.lParam);
if (FDialog.Handle <> 0) and (Data.message = WM_SHOWWINDOW) then
begin
Parent := GetWindowLong(FDialog.Handle, GWL_HWNDPARENT);
if ((Data.hwnd = FDialog.Handle) and (Parent = Application.Handle)) or
((Data.hwnd = FDialog.Handle) and (FDialog is TFindDialog)) or
(Data.hwnd = Parent) then
begin
CenterWindow(FCenterWnd, Data.hwnd);
SetWindowPos(Data.hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or
SWP_NOSIZE or SWP_NOACTIVATE or SWP_NOOWNERZORDER);
UnhookWindowsHookEx(FWndHook);
FWndHook := 0;
FreeHookInstance(FHookProc);
FHookProc := nil;
end;
end;
end;
end;
function ExecuteCentered(Dialog: TCommonDialog;
WindowToCenterIn: HWND = 0): Boolean;
begin
with TAwCommonDialog.Create do
try
if WindowToCenterIn = 0 then
FCenterWnd := GetTopWindow
else
FCenterWnd := WindowToCenterIn;
FDialog := Dialog;
Result := Execute;
finally
Free;
end;
end;
{ TAwMessageBox }
type
TAwMessageBox = class(TObject)
private
FCaption: String;
FCenterWnd: HWND;
FFlags: Cardinal;
FHookProc: TFarProc;
FText: String;
FWndHook: HHOOK;
function Execute: Integer;
procedure HookProc(var Message: THookMessage);
end;
function TAwMessageBox.Execute: Integer;
begin
try
try
Application.NormalizeAllTopMosts;
FHookProc := MakeHookInstance(HookProc);
FWndHook := SetWindowsHookEx(WH_CALLWNDPROCRET, FHookProc, 0,
GetCurrentThreadID);
Result := Application.MessageBox(PChar(FText), PChar(FCaption), FFlags);
finally
if FWndHook <> 0 then
UnhookWindowsHookEx(FWndHook);
if FHookProc <> nil then
FreeHookInstance(FHookProc);
Application.RestoreTopMosts;
end;
except
Result := 0;
end;
end;
procedure TAwMessageBox.HookProc(var Message: THookMessage);
var
Data: PCWPRetStruct;
Title: array[0..255] of Char;
begin
with Message do
if nCode < 0 then
Result := CallNextHookEx(FWndHook, nCode, wParam, lParam)
else
Result := 0;
if Message.nCode = HC_ACTION then
begin
Data := PCWPRetStruct(Message.lParam);
if Data.message = WM_INITDIALOG then
begin
FillChar(Title, SizeOf(Title), 0);
GetWindowText(Data.hwnd, #Title, SizeOf(Title));
if String(Title) = FCaption then
begin
CenterWindow(FCenterWnd, Data.hwnd);
SetWindowPos(Data.hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or
SWP_NOSIZE or SWP_NOACTIVATE or SWP_NOOWNERZORDER);
UnhookWindowsHookEx(FWndHook);
FWndHook := 0;
FreeHookInstance(FHookProc);
FHookProc := nil;
end;
end;
end;
end;
function MsgBox(const Text: String; Flags: Cardinal = DefFlags;
const Caption: String = DefCaption;
WindowToCenterIn: HWND = 0): Integer;
begin
with TAwMessageBox.Create do
try
if Caption = DefCaption then
FCaption := Application.Title
else
FCaption := Caption;
if WindowToCenterIn = 0 then
FCenterWnd := GetTopWindow
else
FCenterWnd := WindowToCenterIn;
FFlags := Flags;
FText := Text;
Result := Execute;
finally
Free;
end;
end;
end.
unit AwHookInstance;
interface
uses
Windows;
type
THookMessage = packed record
nCode: Integer;
wParam: WPARAM;
lParam: LPARAM;
Result: LRESULT;
end;
THookMethod = procedure(var Message: THookMessage) of object;
function MakeHookInstance(Method: THookMethod): Pointer;
procedure FreeHookInstance(HookInstance: Pointer);
implementation
const
InstanceCount = 313;
type
PHookInstance = ^THookInstance;
THookInstance = packed record
Code: Byte;
Offset: Integer;
case Integer of
0: (Next: PHookInstance);
1: (Method: THookMethod);
end;
PInstanceBlock = ^TInstanceBlock;
TInstanceBlock = packed record
Next: PInstanceBlock;
Code: array[1..2] of Byte;
HookProcPtr: Pointer;
Instances: array[0..InstanceCount] of THookInstance;
end;
var
InstBlockList: PInstanceBlock;
InstFreeList: PHookInstance;
function StdHookProc(nCode: Integer; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall; assembler;
{ In ECX = Address of method pointer }
{ Out EAX = Result }
asm
XOR EAX,EAX
PUSH EAX
PUSH LParam
PUSH WParam
PUSH nCode
MOV EDX,ESP
MOV EAX,[ECX].Longint[4]
CALL [ECX].Pointer
ADD ESP,12
POP EAX
end;
function CalcJmpOffset(Src, Dest: Pointer): Longint;
begin
Result := Longint(Dest) - (Longint(Src) + 5);
end;
function MakeHookInstance(Method: THookMethod): Pointer;
const
BlockCode: array[1..2] of Byte = ($59 { POP ECX }, $E9 { JMP StdHookProc });
PageSize = 4096;
var
Block: PInstanceBlock;
Instance: PHookInstance;
begin
if InstFreeList = nil then
begin
Block := VirtualAlloc(nil, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Block^.Next := InstBlockList;
Move(BlockCode, Block^.Code, SizeOf(BlockCode));
Block^.HookProcPtr := Pointer(CalcJmpOffset(#Block^.Code[2], #StdHookProc));
Instance := #Block^.Instances;
repeat
Instance^.Code := $E8; { CALL NEAR PTR Offset }
Instance^.Offset := CalcJmpOffset(Instance, #Block^.Code);
Instance^.Next := InstFreeList;
InstFreeList := Instance;
Inc(Longint(Instance), SizeOf(THookInstance));
until Longint(Instance) - Longint(Block) >= SizeOf(TInstanceBlock);
InstBlockList := Block;
end;
Result := InstFreeList;
Instance := InstFreeList;
InstFreeList := Instance^.Next;
Instance^.Method := Method;
end;
procedure FreeHookInstance(HookInstance: Pointer);
begin
if HookInstance <> nil then
begin
PHookInstance(HookInstance)^.Next := InstFreeList;
InstFreeList := HookInstance;
end;
end;
end.
Legal notice: These units are written by me in this Dutch topic. The original versions are from Mark van Renswoude, see NLDMessageBox.
Here's the code I currently use to show a centered dialog over the active form:
function MessageDlgCenter(const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons): Integer;
var R: TRect;
begin
if not Assigned(Screen.ActiveForm) then
begin
Result := MessageDlg(Msg, DlgType, Buttons, 0);
end else
begin
with CreateMessageDialog(Msg, DlgType, Buttons) do
try
GetWindowRect(Screen.ActiveForm.Handle, R);
Left := R.Left + ((R.Right - R.Left) div 2) - (Width div 2);
Top := R.Top + ((R.Bottom - R.Top) div 2) - (Height div 2);
Result := ShowModal;
finally
Free;
end;
end;
end;

How to "scan" the full list of currently-installed VCL components

I still haven't found a truly satisfactory answer to this question, and am now considering rolling my own. I have ModelMaker and GExperts, and neither seems to load the comprehensive class-hierarchy I am looking for. As well, I don't think the folks at DevExpress will fork over the CDK code which compiles a full class list to inherit from... ;-)
SO...
If ALL I want to do is build a self-referencing table of all registered component classes (or even all classes including non-components, if that's just as easy/possible), what would be the best way to go about doing that?
Note: I don't really need property / method details; JUST a complete list of class names (and parent names) I can store to a table and put in a treeview. Anything beyond that, though, is more than welcome as bonus info. :-)
Update later:
One answer that shows up in my "recent" section on SO, but not here on the question (maybe they erased it?), was this:"u may want to take a look on code of Component Search, it may help you to enumrate all components installed." Is that code available? Is so, where is it hiding? Would be interesting to study.
Unfortunately, the code implementing the RegisterClass mechanism is hidden in Classes implementation section.
If you need this for getting the list of components installed in the IDE, you can write a design package, install it into the IDE and use IOTAPackageServices in ToolsAPI unit. This will give you the list of installed packages and their components.
Note: You'll have to add designide.dcp to your 'requires' clause to be able to use Delphi's internal units like ToolsAPI.
A bit more work but a more generic way would be to enumerate all loaded modules. You can call GetPackageInfo (SysUtils) on a package module to enumerate contained unit names and required packages. However this will not give you a list of classes contained in the package.
You could enumerate the package's list of exported functions (e.g. with TJclPeImage in the JCL) and search for those named like this:
#<unit_name>#<class_name>#
for example: '#System#TObject#'.
By calling GetProcAddress with the function name you get the TClass reference. From there you can walk the hierarchy using ClassParent. This way you can enumerate all classes in all packages loaded in a process running a Delphi executable compiled with runtime packages (Delphi IDE, too).
Another idea is to scan for type information which is on top of the list of exported functions so you can skip enumerating further. The type infos are exported with names starting with prefix '#$xp$'. Here's an example:
unit PackageUtils;
interface
uses
Windows, Classes, SysUtils, Contnrs, TypInfo;
type
TDelphiPackageList = class;
TDelphiPackage = class;
TDelphiProcess = class
private
FPackages: TDelphiPackageList;
function GetPackageCount: Integer;
function GetPackages(Index: Integer): TDelphiPackage;
public
constructor Create; virtual;
destructor Destroy; override;
procedure Clear; virtual;
function FindPackage(Handle: HMODULE): TDelphiPackage;
procedure Reload; virtual;
property PackageCount: Integer read GetPackageCount;
property Packages[Index: Integer]: TDelphiPackage read GetPackages;
end;
TDelphiPackageList = class(TObjectList)
protected
function GetItem(Index: Integer): TDelphiPackage;
procedure SetItem(Index: Integer; APackage: TDelphiPackage);
public
function Add(APackage: TDelphiPackage): Integer;
function Extract(APackage: TDelphiPackage): TDelphiPackage;
function Remove(APackage: TDelphiPackage): Integer;
function IndexOf(APackage: TDelphiPackage): Integer;
procedure Insert(Index: Integer; APackage: TDelphiPackage);
function First: TDelphiPackage;
function Last: TDelphiPackage;
property Items[Index: Integer]: TDelphiPackage read GetItem write SetItem; default;
end;
TDelphiPackage = class
private
FHandle: THandle;
FInfoTable: Pointer;
FTypeInfos: TList;
procedure CheckInfoTable;
procedure CheckTypeInfos;
function GetDescription: string;
function GetFileName: string;
function GetInfoName(NameType: TNameType; Index: Integer): string;
function GetShortName: string;
function GetTypeInfoCount(Kinds: TTypeKinds): Integer;
function GetTypeInfos(Kinds: TTypeKinds; Index: Integer): PTypeInfo;
public
constructor Create(AHandle: HMODULE; AInfoTable: Pointer = nil);
destructor Destroy; override;
property Description: string read GetDescription;
property FileName: string read GetFileName;
property Handle: THandle read FHandle;
property ShortName: string read GetShortName;
property TypeInfoCount[Kinds: TTypeKinds]: Integer read GetTypeInfoCount;
property TypeInfos[Kinds: TTypeKinds; Index: Integer]: PTypeInfo read GetTypeInfos;
end;
implementation
uses
RTLConsts, SysConst,
PSAPI, ImageHlp;
{ Package info structures copied from SysUtils.pas }
type
PPkgName = ^TPkgName;
TPkgName = packed record
HashCode: Byte;
Name: array[0..255] of Char;
end;
PUnitName = ^TUnitName;
TUnitName = packed record
Flags : Byte;
HashCode: Byte;
Name: array[0..255] of Char;
end;
PPackageInfoHeader = ^TPackageInfoHeader;
TPackageInfoHeader = packed record
Flags: Cardinal;
RequiresCount: Integer;
{Requires: array[0..9999] of TPkgName;
ContainsCount: Integer;
Contains: array[0..9999] of TUnitName;}
end;
TEnumModulesCallback = function (Module: HMODULE; Data: Pointer = nil): Boolean;
TEnumModulesProc = function (Callback: TEnumModulesCallback; Data: Pointer = nil): Boolean;
const
STypeInfoPrefix = '#$xp$';
var
EnumModules: TEnumModulesProc = nil;
function PackageInfoTable(Module: HMODULE): PPackageInfoHeader; forward;
function AddPackage(Module: HMODULE; Data: {TDelphiPackageList} Pointer): Boolean;
var
InfoTable: Pointer;
begin
Result := False;
if (Module <> HInstance) then
begin
InfoTable := PackageInfoTable(Module);
if Assigned(InfoTable) then
TDelphiPackageList(Data).Add(TDelphiPackage.Create(Module, InfoTable));
end;
end;
function GetPackageDescription(Module: HMODULE): string;
var
ResInfo: HRSRC;
ResData: HGLOBAL;
begin
Result := '';
ResInfo := FindResource(Module, 'DESCRIPTION', RT_RCDATA);
if ResInfo <> 0 then
begin
ResData := LoadResource(Module, ResInfo);
if ResData <> 0 then
try
Result := PWideChar(LockResource(ResData));
UnlockResource(ResData);
finally
FreeResource(ResData);
end;
end;
end;
function EnumModulesPS(Callback: TEnumModulesCallback; Data: Pointer = nil): Boolean;
var
ProcessHandle: THandle;
SizeNeeded: Cardinal;
P, ModuleHandle: PDWORD;
I: Integer;
begin
Result := False;
ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, GetCurrentProcessId);
if ProcessHandle = 0 then
RaiseLastOSError;
try
SizeNeeded := 0;
EnumProcessModules(ProcessHandle, nil, 0, SizeNeeded);
if SizeNeeded = 0 then
Exit;
P := AllocMem(SizeNeeded);
try
if EnumProcessModules(ProcessHandle, P, SizeNeeded, SizeNeeded) then
begin
ModuleHandle := P;
for I := 0 to SizeNeeded div SizeOf(DWORD) - 1 do
begin
if Callback(ModuleHandle^, Data) then
Exit;
Inc(ModuleHandle);
end;
Result := True;
end;
finally
FreeMem(P);
end;
finally
CloseHandle(ProcessHandle);
end;
end;
function EnumModulesTH(Callback: TEnumModulesCallback; Data: Pointer = nil): Boolean;
begin
Result := False;
// todo win9x?
end;
function PackageInfoTable(Module: HMODULE): PPackageInfoHeader;
var
ResInfo: HRSRC;
Data: THandle;
begin
Result := nil;
ResInfo := FindResource(Module, 'PACKAGEINFO', RT_RCDATA);
if ResInfo <> 0 then
begin
Data := LoadResource(Module, ResInfo);
if Data <> 0 then
try
Result := LockResource(Data);
UnlockResource(Data);
finally
FreeResource(Data);
end;
end;
end;
{ TDelphiProcess private }
function TDelphiProcess.GetPackageCount: Integer;
begin
Result := FPackages.Count;
end;
function TDelphiProcess.GetPackages(Index: Integer): TDelphiPackage;
begin
Result := FPackages[Index];
end;
{ TDelphiProcess public }
constructor TDelphiProcess.Create;
begin
inherited Create;
FPackages := TDelphiPackageList.Create;
Reload;
end;
destructor TDelphiProcess.Destroy;
begin
FPackages.Free;
inherited Destroy;
end;
procedure TDelphiProcess.Clear;
begin
FPackages.Clear;
end;
function TDelphiProcess.FindPackage(Handle: HMODULE): TDelphiPackage;
var
I: Integer;
begin
Result := nil;
for I := 0 to FPackages.Count - 1 do
if FPackages[I].Handle = Handle then
begin
Result := FPackages[I];
Break;
end;
end;
procedure TDelphiProcess.Reload;
begin
Clear;
if Assigned(EnumModules) then
EnumModules(AddPackage, FPackages);
end;
{ TDelphiPackageList protected }
function TDelphiPackageList.GetItem(Index: Integer): TDelphiPackage;
begin
Result := TDelphiPackage(inherited GetItem(Index));
end;
procedure TDelphiPackageList.SetItem(Index: Integer; APackage: TDelphiPackage);
begin
inherited SetItem(Index, APackage);
end;
{ TDelphiPackageList public }
function TDelphiPackageList.Add(APackage: TDelphiPackage): Integer;
begin
Result := inherited Add(APackage);
end;
function TDelphiPackageList.Extract(APackage: TDelphiPackage): TDelphiPackage;
begin
Result := TDelphiPackage(inherited Extract(APackage));
end;
function TDelphiPackageList.First: TDelphiPackage;
begin
Result := TDelphiPackage(inherited First);
end;
function TDelphiPackageList.IndexOf(APackage: TDelphiPackage): Integer;
begin
Result := inherited IndexOf(APackage);
end;
procedure TDelphiPackageList.Insert(Index: Integer; APackage: TDelphiPackage);
begin
inherited Insert(Index, APackage);
end;
function TDelphiPackageList.Last: TDelphiPackage;
begin
Result := TDelphiPackage(inherited Last);
end;
function TDelphiPackageList.Remove(APackage: TDelphiPackage): Integer;
begin
Result := inherited Remove(APackage);
end;
{ TDelphiPackage private }
procedure TDelphiPackage.CheckInfoTable;
begin
if not Assigned(FInfoTable) then
FInfoTable := PackageInfoTable(Handle);
if not Assigned(FInfoTable) then
raise EPackageError.CreateFmt(SCannotReadPackageInfo, [ExtractFileName(GetModuleName(Handle))]);
end;
procedure TDelphiPackage.CheckTypeInfos;
var
ExportDir: PImageExportDirectory;
Size: DWORD;
Names: PDWORD;
I: Integer;
begin
if not Assigned(FTypeInfos) then
begin
FTypeInfos := TList.Create;
try
Size := 0;
ExportDir := ImageDirectoryEntryToData(Pointer(Handle), True, IMAGE_DIRECTORY_ENTRY_EXPORT, Size);
if not Assigned(ExportDir) then
Exit;
Names := PDWORD(DWORD(Handle) + DWORD(ExportDir^.AddressOfNames));
for I := 0 to ExportDir^.NumberOfNames - 1 do
begin
if StrLIComp(PChar(DWORD(Handle) + Names^), STypeInfoPrefix, StrLen(STypeInfoPrefix)) <> 0 then
Break;
FTypeInfos.Add(GetProcAddress(Handle, PChar(DWORD(Handle) + Names^)));
Inc(Names);
end;
except
FreeAndNil(FTypeInfos);
raise;
end;
end;
end;
function TDelphiPackage.GetDescription: string;
begin
Result := GetPackageDescription(Handle);
end;
function TDelphiPackage.GetFileName: string;
begin
Result := GetModuleName(FHandle);
end;
function TDelphiPackage.GetInfoName(NameType: TNameType; Index: Integer): string;
var
P: Pointer;
Count: Integer;
I: Integer;
begin
Result := '';
CheckInfoTable;
Count := PPackageInfoHeader(FInfoTable)^.RequiresCount;
P := Pointer(Cardinal(FInfoTable) + SizeOf(TPackageInfoHeader));
case NameType of
ntContainsUnit:
begin
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PPkgName(P)^.Name) + 2);
Count := Integer(P^);
P := Pointer(Cardinal(P) + SizeOf(Integer));
if (Index >= 0) and (Index < Count) then
begin
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PUnitName(P)^.Name) + 3);
Result := PUnitName(P)^.Name;
end;
end;
ntRequiresPackage:
if (Index >= 0) and (Index < Count) then
begin
for I := 0 to Index - 1 do
P := Pointer(Cardinal(P) + StrLen(PPkgName(P)^.Name) + 2);
Result := PPkgName(P)^.Name;
end;
ntDcpBpiName:
if PPackageInfoHeader(FInfoTable)^.Flags and pfPackageModule <> 0 then
begin
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PPkgName(P)^.Name) + 2);
Count := Integer(P^);
P := Pointer(Cardinal(P) + SizeOf(Integer));
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PUnitName(P)^.Name) + 3);
Result := PPkgName(P)^.Name;
end;
end;
end;
function TDelphiPackage.GetShortName: string;
begin
Result := GetInfoName(ntDcpBpiName, 0);
end;
function TDelphiPackage.GetTypeInfoCount(Kinds: TTypeKinds): Integer;
var
I: Integer;
begin
CheckTypeInfos;
Result := 0;
for I := 0 to FTypeInfos.Count - 1 do
if (Kinds = []) or (PTypeInfo(FTypeInfos[I])^.Kind in Kinds) then
Inc(Result);
end;
function TDelphiPackage.GetTypeInfos(Kinds: TTypeKinds; Index: Integer): PTypeInfo;
var
I, J: Integer;
begin
CheckTypeInfos;
Result := nil;
J := -1;
for I := 0 to FTypeInfos.Count - 1 do
if (Kinds = []) or (PTypeInfo(FTypeInfos[I])^.Kind in Kinds) then
begin
Inc(J);
if J = Index then
begin
Result := FTypeInfos[I];
Break;
end;
end;
end;
{ TDelphiPackage public }
constructor TDelphiPackage.Create(AHandle: HMODULE; AInfoTable: Pointer = nil);
begin
inherited Create;
FHandle := AHandle;
FInfoTable := AInfoTable;
FTypeInfos := nil;
end;
destructor TDelphiPackage.Destroy;
begin
FTypeInfos.Free;
inherited Destroy;
end;
initialization
case Win32Platform of
VER_PLATFORM_WIN32_WINDOWS:
EnumModules := EnumModulesTH;
VER_PLATFORM_WIN32_NT:
EnumModules := EnumModulesPS;
else
EnumModules := nil;
end;
finalization
end.
Unit of the test design package installed in the IDE:
unit Test;
interface
uses
SysUtils, Classes,
ToolsAPI;
type
TTestWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
private
{ IOTAWizard }
procedure Execute;
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
{ IOTAMenuWizard }
function GetMenuText: string;
end;
implementation
uses
TypInfo,
PackageUtils;
function AncestryStr(AClass: TClass): string;
begin
Result := '';
if not Assigned(AClass) then
Exit;
Result := AncestryStr(AClass.ClassParent);
if Result <> '' then
Result := Result + '\';
Result := Result + AClass.ClassName;
end;
procedure ShowMessage(const S: string);
begin
with BorlandIDEServices as IOTAMessageServices do
AddTitleMessage(S);
end;
{ TTestWizard }
procedure TTestWizard.Execute;
var
Process: TDelphiProcess;
I, J: Integer;
Package: TDelphiPackage;
PInfo: PTypeInfo;
PData: PTypeData;
begin
Process := TDelphiProcess.Create;
for I := 0 to Process.PackageCount - 1 do
begin
Package := Process.Packages[I];
for J := 0 to Package.TypeInfoCount[[tkClass]] - 1 do
begin
PInfo := Package.TypeInfos[[tkClass], J];
PData := GetTypeData(PInfo);
ShowMessage(Format('%s: %s.%s (%s)', [Package.ShortName, PData^.UnitName, PInfo^.Name, AncestryStr(PData^.ClassType)]));
end;
end;
end;
function TTestWizard.GetIDString: string;
begin
Result := 'TOndrej.TestWizard';
end;
function TTestWizard.GetName: string;
begin
Result := 'Test';
end;
function TTestWizard.GetState: TWizardState;
begin
Result := [wsEnabled];
end;
function TTestWizard.GetMenuText: string;
begin
Result := 'Test';
end;
var
Index: Integer = -1;
initialization
with BorlandIDEServices as IOTAWizardServices do
Index := AddWizard(TTestWizard.Create);
finalization
if Index <> -1 then
with BorlandIDEServices as IOTAWizardServices do
RemoveWizard(Index);
end.
You have to add designide to your requires clause. When you install this design package a new menu item Test should appear under Delphi's Help menu. Clicking it should display all loaded classes in the Messages window.
Have you tried Delphi's own class browser?
The browser gets loaded with shortcut CTRL-SHIFT-B. I believe you can access its options by right clicking in the browser. Here you have the option to show only the classes in your project or all known classes.
I haven't checked but I expect every descendant from TComponent, including installed components to be visible below the TComponent node. Use CTRL-F to search for a particular class.
Edit: according to this Delphi Wiki page, CTRL+SHIFT+B is only available in Delphi5. I don't have Delphi 2007 to check for this but if you can not find a class browser in your version, I'd suspect there isn't any.

Resources