I have an old Delphi VCL project build in Rad Studio 2007. This version contains a bug in the TScreen.FindMonitor method. I am trying to fix this using a seperate helper class like stated in Is there a runtime patch for AV in TMonitor.GetBoundsRect?.
The only problem is that I can't get this to work. Delphi can't compile and gives error "Cannot access private symbol TScreen.FindMonitor".
Also tried using a WITH self DO statement, casting self to TScreen, casting to pointer and using MethodAddress, but nothing seems to work.
My code
unit PatchTScreen;
interface
implementation
uses
Types, MultiMon, Windows, Forms;
type
TFindMonitorMethod = function(Handle: HMONITOR): TMonitor of object;
TScreenHelper = class helper for TScreen
private
function FindMonitorAddress: Pointer;
function PatchedFindMonitorAddress: Pointer;
function PatchedFindMonitor(Handle: HMONITOR): TMonitor;
end;
function TScreenHelper.FindMonitorAddress: Pointer;
var
MethodPtr: TFindMonitorMethod;
begin
MethodPtr := Self.FindMonitor;
Result := TMethod(MethodPtr).Code;
end;
function TScreenHelper.PatchedFindMonitorAddress: Pointer;
var
MethodPtr: TFindMonitorMethod;
begin
MethodPtr := Self.PatchedFindMonitor;
Result := TMethod(MethodPtr).Code;
end;
function TScreenHelper.PatchedFindMonitor(Handle: HMONITOR): TMonitor;
function DoFindMonitor: TMonitor;
var
I: Integer;
begin
Result := nil;
for I := 0 to MonitorCount - 1 do
if Monitors[I].Handle = Handle then
begin
Result := Monitors[I];
Break;
end;
end;
begin
Result := DoFindMonitor;
if Result = nil then
begin
// If we didn't find the monitor, rebuild the list (it may have changeed)
Self.GetMonitors;
Result := DoFindMonitor;
end;
end;
procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
OldProtect: DWORD;
begin
if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
begin
Move(NewCode, Address^, Size);
FlushInstructionCache(GetCurrentProcess, Address, Size);
VirtualProtect(Address, Size, OldProtect, #OldProtect);
end;
end;
type
PInstruction = ^TInstruction;
TInstruction = packed record
Opcode: Byte;
Offset: Integer;
end;
procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
NewCode: TInstruction;
begin
NewCode.Opcode := $E9;//jump relative
NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;
initialization
RedirectProcedure(
TScreen(nil).FindMonitorAddress, // safe to use nil, don't need to instantiate an object
TScreen(nil).PatchedFindMonitorAddress // likewise
);
end.
The compile error occures in TScreenHelper.FindMonitorAddress.
The only way I have been able to fix this issue is by changing the original Delphi code of TScreen.FindMonitor in Forms.pas and recomipling the unit with my project. But this is not an actual solution I would like to use, because other developers must make the same changes etc.
Related
The extended RTTI has the GetDeclaredProperties function which is exactly what i need, however i faced problems if i use the extended RTTI in multi-threading.
Therefore, i used GetPropList, but this gives me a list of all properties - not only published in the current class (or explicit stated).
i.e.
TBaseSettings = class(TPersistent)
published
property Charset: string read FCharset write FCharset;
end;
TBasicSettings = class(TBaseSettings)
published
property forums: Variant read fforums write fforums;
end;
TConcreteSettings = class(TBasicSettings)
published
property forums; // <-- make it explicit visible: OK
property prefix: Variant read fprefix write fprefix; // <-- OK
end;
I don't want to read the Charset property.
My first guess was to use a modified version of https://stackoverflow.com/a/1565686 to check for inheritance, but actually the forums property is also inherited.
Maybe this is not possible with the classic RTTI? I use Delphi 2010.
In case it's convenient to have your code calling GetDeclaredPropList in a similar way to calling GetPropList, see below.
Edit: I've rewritten the code in Delphi 7 and I believe it should work in Delphi 2010, too (which I don't have at hand).
type
PPropData = ^TPropData;
function AfterString(P: Pointer): Pointer;
begin
Result := Pointer(NativeUInt(P) + (PByte(P)^ + 1));
end;
function GetPropData(TypeData: PTypeData): PPropData;
begin
Result := AfterString(#TypeData^.UnitName);
end;
function NextPropInfo(PropInfo: PPropInfo): PPropInfo;
begin
Result := AfterString(#PropInfo^.Name);
end;
procedure GetDeclaredPropInfos(TypeInfo: PTypeInfo; PropList: PPropList);
var
TypeData: PTypeData;
PropData: PPropData;
PropInfo: PPropInfo;
I: Integer;
begin
TypeData := GetTypeData(TypeInfo);
PropData := GetPropData(TypeData);
FillChar(PropList^, Sizeof(PPropInfo) * PropData^.PropCount, 0);
PropInfo := PPropInfo(#PropData^.PropList);
for I := 0 to PropData^.PropCount - 1 do
begin
PropList^[I] := PropInfo;
PropInfo := NextPropInfo(PropInfo);
end;
end;
function GetDeclaredPropList(TypeInfo: PTypeInfo; out PropList: PPropList): Integer; overload;
begin
Result := GetPropData(GetTypeData(TypeInfo))^.PropCount;
if Result > 0 then
begin
GetMem(PropList, Result * SizeOf(Pointer));
GetDeclaredPropInfos(TypeInfo, PropList);
end;
end;
function GetDeclaredPropList(AObject: TObject; out PropList: PPropList): Integer; overload;
begin
Result := GetDeclaredPropList(PTypeInfo(AObject.ClassInfo), PropList);
end;
// example usage:
var
I, Count: Integer;
PropList: PPropList;
PropInfo: PPropInfo;
begin
Count := GetDeclaredPropList(TypeInfo(TConcreteSettings), PropList);
try
for I := 0 to Count - 1 do
begin
PropInfo := PropList^[I];
Writeln(PropInfo^.Name);
end;
finally
FreeMem(PropList);
end;
end.
var
TypeData: PTypeData;
PropData: PPropData;
PropInfo: PPropInfo;
I: Integer;
begin
TypeData := GetTypeData(TypeInfo(TConcreteSettings));
PropData := GetPropData(TypeData);
if Assigned(PropData) then
begin
PropInfo := #PropData^.PropList;
for I := 0 to PropData^.PropCount - 1 do
begin
Writeln(PropInfo^.Name);
PropInfo := NextPropInfo(PropInfo);
end;
end;
end;
For implementation of GetPropData and NextPropInfo see my other answer above.
The following is a known bug in Delphi 7 and 2007 (and possibly other versions)
Does TMonitor.GetBoundsRect have an access violation bug in Delphi 2007 triggered by VNC?
There is an answer on how to fix it by recompiling forms.pas but I'd rather not recompile RTL units. Has anybody created a runtime patch for it e.g. using the technique also used in Andy Hausladen's VclFixpack?
(And if yes, would you please share it with us?)
You can do this with a detour. For instance, the code given in this answer: https://stackoverflow.com/a/8978266/505088 will suffice. Or you could opt for any other detouring library.
Beyond that, you need to crack the class to gain access to the private members. After all, GetBoundsRect is private. You can crack the class with a class helper. Again, one of my answers shows how to do that: https://stackoverflow.com/a/10156682/505088
Put the two together, and you have your answer.
unit PatchTScreen;
interface
implementation
uses
Types, MultiMon, Windows, Forms;
type
TScreenHelper = class helper for TScreen
function FindMonitorAddress: Pointer;
function PatchedFindMonitorAddress: Pointer;
function PatchedFindMonitor(Handle: HMONITOR): TMonitor;
end;
function TScreenHelper.FindMonitorAddress: Pointer;
var
MethodPtr: function(Handle: HMONITOR): TMonitor of object;
begin
MethodPtr := Self.FindMonitor;
Result := TMethod(MethodPtr).Code;
end;
function TScreenHelper.PatchedFindMonitorAddress: Pointer;
var
MethodPtr: function(Handle: HMONITOR): TMonitor of object;
begin
MethodPtr := Self.PatchedFindMonitor;
Result := TMethod(MethodPtr).Code;
end;
function TScreenHelper.PatchedFindMonitor(Handle: HMONITOR): TMonitor;
var
I: Integer;
begin
Result := nil;
for I := 0 to MonitorCount - 1 do
if Monitors[I].Handle = Handle then
begin
Result := Monitors[I];
// break;
Exit;
end;
//if we get here, the Monitors array has changed, so we need to clear and reinitialize it
for i := 0 to MonitorCount-1 do
TMonitor(Monitors[i]).Free;
fMonitors.Clear;
EnumDisplayMonitors(0, nil, #EnumMonitorsProc, LongInt(FMonitors));
for I := 0 to MonitorCount - 1 do
if Monitors[I].Handle = Handle then
begin
Result := Monitors[I];
Exit;
end;
end;
procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
OldProtect: DWORD;
begin
if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
begin
Move(NewCode, Address^, Size);
FlushInstructionCache(GetCurrentProcess, Address, Size);
VirtualProtect(Address, Size, OldProtect, #OldProtect);
end;
end;
type
PInstruction = ^TInstruction;
TInstruction = packed record
Opcode: Byte;
Offset: Integer;
end;
procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
NewCode: TInstruction;
begin
NewCode.Opcode := $E9;//jump relative
NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;
initialization
RedirectProcedure(
TScreen(nil).FindMonitorAddress, // safe to use nil, don't need to instantiate an object
TScreen(nil).PatchedFindMonitorAddress // likewise
);
end.
Without class helpers, as is the case in Delphi 7, you might be best recompiling the VCL unit in question. That is simple and robust.
If you can't bring yourself to do that then you need to find the function address. I'd do that by disassembling the code at runtime and following it to a known call to the function. This technique is well demonstrated by madExcept.
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);
As we know, working with TBitmap's pixels (Bitmap.Canvas.Pixels[X,Y]) is very slow in the out-of-box VCL. This has been caused by getter and setter of Pixels property inherited from TCanvas, which encapsulates general WinGDI DC object and is not specific to MemDC of bitmap.
For the DIB section-based bitmaps (bmDIB) a well-known workaround exists, however I do not see the way to integrate the proper getter/setter in the VCL TBitmap class (besides direct modification of library code, which proven to be real pain in the stern when it comes to compiling against different VCL versions).
Please advise if there is some hackish way to reach TBitmapCanvas class and inject overriden methods into it.
I'm sure it could be done more elegantly, but here's what you ask for implemented using a class helper to crack the private members:
unit BitmapCanvasCracker;
interface
uses
SysUtils, Windows, Graphics;
implementation
procedure Fail;
begin
raise EAssertionFailed.Create('Fixup failed.');
end;
procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
OldProtect: DWORD;
begin
if not VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then begin
Fail;
end;
Move(NewCode, Address^, Size);
FlushInstructionCache(GetCurrentProcess, nil, 0);
if not VirtualProtect(Address, Size, OldProtect, #OldProtect) then begin
Fail;
end;
end;
type
PInstruction = ^TInstruction;
TInstruction = packed record
Opcode: Byte;
Offset: Integer;
end;
procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
NewCode: TInstruction;
begin
NewCode.Opcode := $E9;//jump relative
NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;
type
TBitmapCanvas = class(TCanvas)
// you need to implement this class
end;
type
TBitmapHelper = class helper for TBitmap
function NewGetCanvas: TCanvas;
class procedure Patch;
end;
function TBitmapHelper.NewGetCanvas: TCanvas;
begin
if Self.FCanvas = nil then
begin
Self.HandleNeeded;
if Self.FCanvas = nil then
begin
Self.FCanvas := TBitmapCanvas.Create;
Self.FCanvas.OnChange := Self.Changed;
Self.FCanvas.OnChanging := Self.Changing;
end;
end;
Result := Self.FCanvas;
end;
class procedure TBitmapHelper.Patch;
begin
RedirectProcedure(#TBitmap.GetCanvas, #TBitmap.NewGetCanvas);
end;
initialization
TBitmap.Patch;
end.
Include this unit in your project and the TBitmap class will be patched so that its GetCanvas method redirects to NewGetCanvas and allows you to implement your own TCanvas subclass.
I don't think the code will work if you are using runtime packages but to sort that out you just need to use more capable hooking code.
Another weird glitch with VCL styles:
Changing a form's Icon updates only its taskbar button, the Icon in the caption doesn't update unless you use RecreateWnd. (when using VCL styles)
ImageList3.GetIcon(0,Form1.Icon);
Is there a way to fix it without having to use RecreateWnd? (which can actually create other issues)
It's (yet another) bug in VCL styles. The TFormStyleHook.GetIconFast function is returning a stale icon handle. I'd fix it by replacing TFormStyleHook.GetIconFast with TFormStyleHook.GetIcon. Add this to one of your units and all is well again.
procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
OldProtect: DWORD;
begin
if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
begin
Move(NewCode, Address^, Size);
FlushInstructionCache(GetCurrentProcess, Address, Size);
VirtualProtect(Address, Size, OldProtect, #OldProtect);
end;
end;
type
PInstruction = ^TInstruction;
TInstruction = packed record
Opcode: Byte;
Offset: Integer;
end;
procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
NewCode: TInstruction;
begin
NewCode.Opcode := $E9;//jump relative
NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;
type
TFormStyleHookHelper = class helper for TFormStyleHook
function GetIconFastAddress: Pointer;
function GetIconAddress: Pointer;
end;
function TFormStyleHookHelper.GetIconFastAddress: Pointer;
var
MethodPtr: function: TIcon of object;
begin
MethodPtr := Self.GetIconFast;
Result := TMethod(MethodPtr).Code;
end;
function TFormStyleHookHelper.GetIconAddress: Pointer;
var
MethodPtr: function: TIcon of object;
begin
MethodPtr := Self.GetIcon;
Result := TMethod(MethodPtr).Code;
end;
initialization
RedirectProcedure(
Vcl.Forms.TFormStyleHook(nil).GetIconFastAddress,
Vcl.Forms.TFormStyleHook(nil).GetIconAddress
);