AlonsoYSal_FactuGES2/Source/Cliente/VCLFixPack.pas
2019-11-18 10:36:42 +00:00

2831 lines
88 KiB
ObjectPascal
Raw Permalink Blame History

{**************************************************************************************************}
{ }
{ VCLFixPack unit - Unoffical bug fixes for Delphi/C++Builder }
{ Version 1.2 (2009-03-03) }
{ }
{ The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }
{ you may not use this file except in compliance with the License. You may obtain a copy of the }
{ License at http://www.mozilla.org/MPL/ }
{ }
{ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF }
{ ANY KIND, either express or implied. See the License for the specific language governing rights }
{ and limitations under the License. }
{ }
{ The Original Code is VCLFixPack.pas. }
{ }
{ The Initial Developer of the Original Code is Andreas Hausladen (Andreas.Hausladen@gmx.de). }
{ Portions created by Andreas Hausladen are Copyright (C) 2008 Andreas Hausladen. }
{ All Rights Reserved. }
{ }
{**************************************************************************************************}
{$IFNDEF CONDITIONALEXPRESSIONS}
Delphi5_is_not_supported
{$ENDIF}
{$A8,B-,C+,D-,E-,F-,G+,H+,I+,J-,K-,L+,M-,N-,O+,P+,Q-,R-,S-,T-,U-,V+,W-,X+,Y+,Z1}
{ If you define VCLFIXPACK_DEBUG the patches are compiled with debug information. }
{$IFDEF VCLFIXPACK_DEBUG} {$D+} {$ENDIF}
{ If you use Delphi 6/7/2005 Personal you must disable the VCLFIXPACK_DB_SUPPORT define. }
{$DEFINE VCLFIXPACK_DB_SUPPORT}
unit VCLFixPack;
{
Usage
=====
Add the unit to the .dpr file's uses-list.
C++Builder user can add the file to the project (Menu Project/Add to project)
Example
=======
uses
FastMM4, // optional memory manager
VCLFixPack,
Forms,
Unit1 in 'Unit1.pas';
Fixes the following bug
=======================
- [2006-2009]
QC #68647: Infinite loop in Forms.GetNonToolWindowPopupParent
(http://qc.codegear.com/wc/qcmain.aspx?d=68647)
- [2007-2009]
QC #68740: Lost focus after TOpenDialog when MainFormOnTaskBar is set
(http://qc.codegear.com/wc/qcmain.aspx?d=68740)
- [2005-2009]
QC #59963: Closing non-modal forms after a task switch can deactivate the application
(http://qc.codegear.com/wc/qcmain.aspx?d=59963)
- [2009]
QC #66892: Closing forms deactivates the application (missing "stdcall")
(http://qc.codegear.com/wc/qcmain.aspx?d=66892)
- [6-2007]
Control resize bugfix for kernel stack overflow due to WH_CALLWNDPROC hook
- [6-2007]
QC #59654: TActionList access already released FActions field
(http://qc.codegear.com/wc/qcmain.aspx?d=59654)
- [6-2007]
QC #54286 : Parent-PopupMenu overrides standard context menu (edit, memo, combobox, ...)
(http://qc.codegear.com/wc/qcmain.aspx?d=54286)
- [2006-2007]
QC #50097: ObjAuto access violation on XEON (Data Execution Prevention bug)
(http://qc.codegear.com/wc/qcmain.aspx?d=50097)
- [6-2009]
Classes.MakeObjectInstance memory leak fix
(for usage in a DLL)
- [2007]
QC #58938: MainForm Minimize minimizes in the background
(http://qc.codegear.com/wc/qcmain.aspx?d=58938)
- [6-2009]
QC #64484: SysUtils.Abort can raise an AccessViolation
(http://qc.codegear.com/wc/qcmain.aspx?d=64484)
- [2007]
QC #58939: No taskbar button when starting from ShellLink with Show=Minimized
(http://qc.codegear.com/wc/qcmain.aspx?d=58939)
- [6-2009]
QC #35001: MDIChild's active control focus is not set correctly
(http://qc.codegear.com/wc/qcmain.aspx?d=35001)
- [7-2009]
QC #56252: TPageControl flickers a lot with active theming
(http://qc.codegear.com/wc/qcmain.aspx?d=56252)
QC #68730: TLabel is not painted on a themed, double-buffered TTabSheet in Vista
(http://qc.codegear.com/wc/qcmain.aspx?d=68730)
TLabels on TTabSheet are not painted (themes) if a TWinControl like TMemo is on the TTabSheet (TWinControl.PaintWindow bug)
- [7-2009]
Grid flickers with active theming (DBGrid, StringGrid and DrawGrid only, no derived classes)
- [2009]
QC #69112: TSpeedButton is painted as a black rectangle on a double buffered panel on a sheet of glass.
- [2009]
QC #69294: TProgressBar fails with PBS_MARQUEE and disabled Themes (Vista)
http://qc.codegear.com/wc/qcmain.aspx?d=69294
- [Windows Vista]
Workaround for Windows Vista CompareString bug
(Workaround is disabled by default, define "VistaCompareStringFix" to activate it)
- [2007-2009]
QC #52439: DbNavigator paints incorrectly when flat=true in themed mode
- [2009]
QC #70441: ToUpper and ToLower modify a Const argument
- [2009]
QC #69752: ToUpper and ToLower with NullString
- [2009]
QC #69875: StringBuilder.Replace is incorrect
QC #67564: Error in TStringBuilder.Replace
Changlog:
2009-03-03:
Fixed: Rewritten patch for QC #59963 (AppDeActivateZOrderFix) to fix the cause instead of the symptom
Added: QC #52439: DbNavigator paints incorrectly when flat=true in themed mode
Added: QC #70441: ToUpper and ToLower modify a Const argument
Added: QC #69752: ToUpper and ToLower with NullString
Added: QC #69875, #67564: StringBuilder.Replace is incorrect
+ a much faster implementation
2009-01-25:
Fixed: DBGrid ScrollBar gab wasn't painted correctly in BiDiMode <> bdLeftToRight
Fixed: TTabSheet could throw an access violation if no PageControl was assigned to it
Changed: Rewritten TaskModalDialog bugfix
Added: QC #69294: TProgressBar fails with PBS_MARQUEE and disabled Themes (Vista)
}
interface
{ ---------------------------------------------------------------------------- }
{$DEFINE DBTextColorBugFix} // Delphi 6+
{$IF CompilerVersion >= 18.0} // Delphi 2006+
{$DEFINE GetNonToolWindowPopupParentFix}
{$IFEND}
{$IF CompilerVersion >= 18.5} // Delphi 2007+
{$DEFINE TaskModalDialogFix}
{$IFEND}
{$IF CompilerVersion >= 16.0} // Delphi 2005
{$DEFINE AppDeActivateZOrderFix}
{$IFEND}
{$IF CompilerVersion = 20.0} // Delphi 2009
{$DEFINE HideStackTrashingFix}
{$IFEND}
{$IF CompilerVersion < 20.0} // Delphi 6-2007
{$DEFINE ControlResizeFix}
{ The OPTIMIZED_RESIZE_REDRAW option is experimental. It speeds up the resizing of forms
by not redrawing each control when it is realigned but by invalidating them all after
one align round is done. }
{.$DEFINE OPTIMIZED_RESIZE_REDRAW}
{$IFEND}
{$IF CompilerVersion < 20.0} // Delphi 6-2007
{$DEFINE ActionListAVFix}
{$IFEND}
{$IF CompilerVersion < 20.0} // Delphi 6-2007
{$DEFINE ContextMenuFix}
{$IFEND}
{$IF (CompilerVersion >= 18.0) and (CompilerVersion < 20.0)} // Delphi 2006-2007
{$DEFINE ObjAutoDEPFix}
{$IFEND}
{$DEFINE MkObjInstLeakFix} // Delphi 6+
{$DEFINE SysUtilsAbortFix} // Delphi 6+
{$IF CompilerVersion = 18.5} // Delphi 2007
{$DEFINE AppMinimizeFix}
{$IFEND}
{$IF CompilerVersion = 18.5} // Delphi 2007
{$DEFINE CmdShowMinimizeFix}
{$IFEND}
{$DEFINE MDIChildFocusFix} // Delphi 6+
{$IF CompilerVersion >= 15} // Delphi 7+
{$DEFINE PageControlPaintingFix}
{$IFEND}
{$IF CompilerVersion >= 15} // Delphi 7+
{$DEFINE GridFlickerFix}
{$IFEND}
{$IF CompilerVersion = 20.0} // Delphi 2009
{$DEFINE SpeedButtonGlassFix}
{$IFEND}
{$IF CompilerVersion = 20.0} // Delphi 2009
{$DEFINE VistaProgressBarMarqueeFix}
{$IFEND}
{$IF CompilerVersion = 20.0} // Delphi 2009
{$DEFINE CharacterFix}
{$IFEND}
{$IF CompilerVersion = 20.0} // Delphi 2009
{$DEFINE StringBuilderFix}
{$IFEND}
{$IF (CompilerVersion >= 18.5) and (CompilerVersion <= 20.0)} // Delphi 2007-2009
{$IFDEF VCLFIXPACK_DB_SUPPORT}
{$DEFINE DBNavigatorFix}
{$ENDIF VCLFIXPACK_DB_SUPPORT}
{$IFEND}
{**************************************************************************************************}
{ Workaround for Windows Vista CompareString bug. }
{ The <20>/<2F> ($DC/$FC) and the UE/ue are treated equal in all locales, but they aren't equal. There }
{ was a bugfix intended for Vista SP1 but it was removed before SP1 was released. }
{ Windows 2008 Server still includes this bugfix but Vista will never get this bugfix. }
{ Microsoft: new versions are for correctness; service packs are for consistency and compatibility }
{**************************************************************************************************}
{ WARNING: This bugfix can slow down CompareString }
{.$DEFINE VistaCompareStringFix}
implementation
{$IF CompilerVersion >= 18.0}
{$DEFINE DELPHI2006_UP}
{$IFEND}
{$IF CompilerVersion >= 17.0}
{$DEFINE DELPHI2005_UP}
{$IFEND}
uses
Windows, Messages, SysUtils, Classes, TypInfo, ActnList, SysConst,
{$IFDEF ObjAutoDEPFix}
ObjAuto,
{$ENDIF ObjAutoDEPFix}
{$IF CompilerVersion >= 15.0}
Themes,
{$IFEND}
{$IF CompilerVersion >= 20.0}
Character,
{$IFEND}
{$IFDEF VCLFIXPACK_DB_SUPPORT}
DBGrids, DBCtrls,
{$ENDIF VCLFIXPACK_DB_SUPPORT}
Graphics, Controls, Forms, Dialogs, StdCtrls, Grids, ComCtrls, Buttons,
CommCtrl;
{ ---------------------------------------------------------------------------- }
{ Helper functions, shared }
type
TOpenWinControl = class(TWinControl);
TOpenCustomForm = class(TCustomForm);
TOpenCommonDialog = class(TCommonDialog);
TOpenCustomActionList = class(TCustomActionList);
TOpenComponent = class(TComponent);
TOpenCustomCombo = class(TCustomCombo);
TJumpOfs = Integer;
PPointer = ^Pointer;
type
PXRedirCode = ^TXRedirCode;
TXRedirCode = packed record
Jump: Byte;
Offset: TJumpOfs;
end;
PWin9xDebugThunk = ^TWin9xDebugThunk;
TWin9xDebugThunk = packed record
PUSH: Byte;
Addr: Pointer;
JMP: TXRedirCode;
end;
PAbsoluteIndirectJmp = ^TAbsoluteIndirectJmp;
TAbsoluteIndirectJmp = packed record
OpCode: Word; //$FF25(Jmp, FF /4)
Addr: PPointer;
end;
var
TaskActiveWindow: HWND;
TaskFirstWindow: HWND;
TaskFirstTopMost: HWND;
function DoFindWindow(Window: HWND; Param: LPARAM): Bool; stdcall;
begin
if (Window <> TaskActiveWindow) and (Window <> Application.Handle) and
IsWindowVisible(Window) and IsWindowEnabled(Window) then
begin
if GetWindowLong(Window, GWL_EXSTYLE) and WS_EX_TOPMOST = 0 then
begin
if TaskFirstWindow = 0 then
TaskFirstWindow := Window;
end else
begin
if TaskFirstTopMost = 0 then
TaskFirstTopMost := Window;
end;
end;
Result := True;
end;
function FindTopMostWindow(ActiveWindow: HWND): HWND;
begin
TaskActiveWindow := ActiveWindow;
TaskFirstWindow := 0;
TaskFirstTopMost := 0;
EnumThreadWindows(GetCurrentThreadID, @DoFindWindow, 0);
if TaskFirstWindow <> 0 then
Result := TaskFirstWindow
else
Result := TaskFirstTopMost;
end;
{ Hooking }
function GetActualAddr(Proc: Pointer): Pointer;
function IsWin9xDebugThunk(AAddr: Pointer): Boolean;
begin
Result := (AAddr <> nil) and
(PWin9xDebugThunk(AAddr).PUSH = $68) and
(PWin9xDebugThunk(AAddr).JMP.Jump = $E9);
end;
begin
if Proc <> nil then
begin
if (Win32Platform <> VER_PLATFORM_WIN32_NT) and IsWin9xDebugThunk(Proc) then
Proc := PWin9xDebugThunk(Proc).Addr;
if (PAbsoluteIndirectJmp(Proc).OpCode = $25FF) then
Result := PAbsoluteIndirectJmp(Proc).Addr^
else
Result := Proc;
end
else
Result := nil;
end;
procedure HookProc(Proc, Dest: Pointer; var BackupCode: TXRedirCode);
var
n: DWORD;
Code: TXRedirCode;
begin
Proc := GetActualAddr(Proc);
Assert(Proc <> nil);
if ReadProcessMemory(GetCurrentProcess, Proc, @BackupCode, SizeOf(BackupCode), n) then
begin
Code.Jump := $E9;
Code.Offset := PAnsiChar(Dest) - PAnsiChar(Proc) - SizeOf(Code);
WriteProcessMemory(GetCurrentProcess, Proc, @Code, SizeOf(Code), n);
end;
end;
procedure UnhookProc(Proc: Pointer; var BackupCode: TXRedirCode);
var
n: Cardinal;
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;
procedure ReplaceVmtField(AClass: TClass; OldProc, NewProc: Pointer);
type
PVmt = ^TVmt;
TVmt = array[0..MaxInt div SizeOf(Pointer) - 1] of Pointer;
var
I: Integer;
Vmt: PVmt;
n: Cardinal;
P: Pointer;
begin
OldProc := GetActualAddr(OldProc);
NewProc := GetActualAddr(NewProc);
I := vmtSelfPtr div SizeOf(Pointer);
Vmt := Pointer(AClass);
while (I < 0) or (Vmt[I] <> nil) do
begin
P := Vmt[I];
if (P <> OldProc) and (Integer(P) > $10000) and not IsBadReadPtr(P, 6) then
P := GetActualAddr(P);
if P = OldProc then
begin
WriteProcessMemory(GetCurrentProcess, @Vmt[I], @NewProc, SizeOf(NewProc), n);
Exit;
end;
Inc(I);
end;
end;
function GetDynamicMethod(AClass: TClass; Index: Integer): Pointer;
asm
call System.@FindDynaClass
end;
procedure DebugLog(const S: string);
begin
OutputDebugString(PChar('VCLFixPack patch installed: ' + S));
end;
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #68647: Infinite loop in Forms.GetNonToolWindowPopupParent }
{$IFDEF GetNonToolWindowPopupParentFix}
{
Forms.pas.4712: Result := GetParent(WndParent); <= must be "Result := GetParent(Result);"
56 push esi <= must be "push ebx"
E8181EFAFF call GetParent
8BD8 mov ebx,eax
Forms.pas.4711: while (Result <> 0) and (GetWindowLong(Result, GWL_EXSTYLE) and WS_EX_TOOLWINDOW = WS_EX_TOOLWINDOW) do
85DB test ebx,ebx
}
procedure FixGetNonToolWindowPopupParent;
var
P: PAnsiChar;
Len: Integer;
Buf: Byte;
n: Cardinal;
begin
P := GetActualAddr(@TOpenCustomForm.CreateParams);
Dec(P);
Len := 0;
while Len < 112 do
begin
if //(P[0] = #$56) and // push esi
(P[1] = #$E8) and // call GetParent
(P[6] = #$8B) and (P[7] = #$D8) and // mov ebx,eax
(P[8] = #$85) and (P[9] = #$DB) then // test ebx,ebx
begin
if P[0] = #$56 then // push esi
begin
Buf := $53; // push esi (WndParent) => push ebx (Result)
WriteProcessMemory(GetCurrentProcess, P, @Buf, 1, n);
DebugLog('GetNonToolWindowPopupParentFix');
Exit;
end else
if P[0] = #$53 then // push ebx => already fixed, abort search
Exit;
end;
Dec(P);
Inc(Len);
end;
end;
{$ENDIF GetNonToolWindowPopupParentFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #68740: Lost focus after TOpenDialog when MainFormOnTaskBar is set }
{$IFDEF TaskModalDialogFix}
var
DialogsTaskModalDialogHook: TXRedirCode;
DialogsTaskModalDialogCritSect: TRTLCriticalSection;
function TCommonDialog_TaskModalDialog(Instance: TObject; DialogFunc: Pointer; var DialogData): Bool;
var
FocusWindow: HWND;
Func: function(Instance: TObject; DialogFunc: Pointer; var DialogData): Bool;
begin
EnterCriticalSection(DialogsTaskModalDialogCritSect);
try
UnhookProc(@TOpenCommonDialog.TaskModalDialog, DialogsTaskModalDialogHook);
try
FocusWindow := GetFocus;
try
Func := @TOpenCommonDialog.TaskModalDialog;
Result := Func(Instance, DialogFunc, DialogData);
finally
SetFocus(FocusWindow);
end;
finally
HookProc(@TOpenCommonDialog.TaskModalDialog, @TCommonDialog_TaskModalDialog, DialogsTaskModalDialogHook);
end;
finally
LeaveCriticalSection(DialogsTaskModalDialogCritSect);
end;
end;
procedure InitTaskModalDialogFix;
begin
InitializeCriticalSection(DialogsTaskModalDialogCritSect);
HookProc(@TOpenCommonDialog.TaskModalDialog, @TCommonDialog_TaskModalDialog, DialogsTaskModalDialogHook);
DebugLog('FixTaskModalDialog');
end;
procedure FiniTaskModalDialogFix;
begin
UnhookProc(@TOpenCommonDialog.TaskModalDialog, DialogsTaskModalDialogHook);
DeleteCriticalSection(DialogsTaskModalDialogCritSect);
end;
{$ENDIF TaskModalDialogFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #59963: Closing non-modal forms after a task switch can deactivate the application }
{$IFDEF AppDeActivateZOrderFix}
{
// Release
0047B1FD F6401C08 test byte ptr [eax+$1c],$08
0047B201 7522 jnz $0047b225 << replace by $90 $90 => nop nop
0047B203 8BC3 mov eax,ebx
0047B205 8B150C824500 mov edx,[$0045820c]
0047B20B E85C8BF8FF call @IsClass
0047B210 84C0 test al,al
0047B212 7509 jnz $0047b21d
0047B214 83BBCC01000000 cmp dword ptr [ebx+$000001cc],$00
0047B21B 7408 jz $0047b225
0047B21D 8B45FC mov eax,[ebp-$04]
0047B220 E8DFFDFFFF call TWinControl.UpdateShowing
0047B225 5E pop esi
0047B226 5B pop ebx
0047B227 59 pop ecx
0047B228 5D pop ebp
0047B229 C3 ret
}
procedure InitAppDeActivateZOrderFix;
var
P: PAnsiChar;
Len: Integer;
Buf: Word;
n: Cardinal;
begin
P := GetActualAddr(@TWinControl.UpdateControlState);
Len := 0;
while Len < 200 do
begin
if (P[0] = #$F6) and (P[1] = #$40) and (P[2] = #$1C) and (P[3] = #$08) and // test byte ptr [eax+$1c],$08
(P[4] = #$75) and (P[5] = #$22) and // jnz +$22
(P[6] = #$8B) and (P[7] = #$C3) and // mov eax,ebx
(P[8] = #$8B) then // mov edx,[TCustomForm]
begin
Buf := $9090; // nop nop
WriteProcessMemory(GetCurrentProcess, @P[4], @Buf, SizeOf(Buf), n);
DebugLog('AppDeActivateZOrderFix');
Exit;
end
else
if (P[0] = #$59) and (P[0] = #$5D) and (P[1] = #$C3) then // function end reached
Break;
Inc(P);
Inc(Len);
end;
end;
procedure FiniAppDeActivateZOrderFix;
begin
end;
{$ENDIF AppDeActivateZOrderFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #66892: Closing forms deactivates the application (missing "stdcall") }
{$IFDEF HideStackTrashingFix}
var
FindTopMostWindowHook: TXRedirCode;
FindTopMostWindowProc: Pointer;
{
Forms.pas.1880: begin
53 push ebx
Forms.pas.1881: TaskActiveWindow := ActiveWindow;
A3D8784700 mov [$004778d8],eax
Forms.pas.1882: TaskFirstWindow := 0;
33C0 xor eax,eax
A3DC784700 mov [$004778dc],eax
Forms.pas.1883: TaskFirstTopMost := 0;
33C0 xor eax,eax
A3E0784700 mov [$004778e0],eax
Forms.pas.1884: EnumProc := @DoFindWindow;
BBBCB64500 mov ebx,$0045b6bc
Forms.pas.1885: EnumThreadWindows(GetCurrentThreadID, EnumProc, 0);
6A00 push $00
53 push ebx
E8DBCBFAFF call GetCurrentThreadId
50 push eax
E821D1FAFF call EnumThreadWindows
}
function GetAddrOfFindTopMostWindow: Pointer;
var
P: PByte;
Len: Integer;
NeedsFix: Boolean;
begin
NeedsFix := False;
P := GetActualAddr(@EnableTaskWindows);
Len := 0;
while Len < 2048 do
begin
{ DoFindWindow "begin" }
if (P[0] = $53) and
(P[1] = $8B) and (P[2] = $D8) then
begin
if (P[3] = $3B) and (P[4] = $1D) and
(P[9] = $74) and ({Release}(P[10] = $4D) or {Debug}(P[10] = $4F)) then
begin
NeedsFix := True;
end
end
else { FindTopMostWindow "begin", Release & Debug }
if (P[0] = $53) and
(P[1] = $A3) then
begin
if (P[6] = $33) and (P[7] = $C0) and
(P[8] = $A3) and
(P[13] = $33) and (P[14] = $C0) and
(P[15] = $A3) and
(P[20] = $BB) and
(P[25] = $6A) and (P[26] = $00) and
(P[27] = $53) then
begin
Result := nil;
if NeedsFix then
Result := P;
Exit;
end;
end;
Inc(P);
Inc(Len);
end;
Result := nil;
end;
{$ENDIF HideStackTrashingFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ Control resize bugfix for kernel stack overflow due to WH_CALLWNDPROC hook }
{$IFDEF ControlResizeFix}
{2008-05-25:
- Added code to detect endless resizing controls.
- Added experimental OPTIMIZED_RESIZE_REDRAW option for faster form resizing }
var
WinControl_AlignControlProc, WinControl_WMSize, WinControl_SetBounds: Pointer;
BackupAlignControl, BackupWMSize, BackupSetBounds: TXRedirCode;
type
TControlResizeFixWinControl = class(TWinControl)
private
procedure AlignControl(AControl: TControl);
procedure HandleAlignControls(AControl: TControl; var R: TRect);
protected
procedure WMSize(var Message: TWMSize); message WM_SIZE;
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
end;
{$IFNDEF DELPHI2005_UP}
TD5WinControlPrivate = class(TControl)
public
FAlignLevel: Word;
end;
{$ENDIF ~DELPHI2005_UP}
threadvar
AlignControlList: TList;
procedure TControlResizeFixWinControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
var
WindowPlacement: TWindowPlacement;
begin
if (ALeft <> Left) or (ATop <> Top) or
(AWidth <> Width) or (AHeight <> Height) then
begin
if HandleAllocated and not IsIconic(WindowHandle) then
begin
if AlignControlList <> nil then
SetWindowPos(WindowHandle, 0, ALeft, ATop, AWidth, AHeight,
SWP_NOZORDER or SWP_NOACTIVATE or SWP_DEFERERASE)
else
SetWindowPos(WindowHandle, 0, ALeft, ATop, AWidth, AHeight,
SWP_NOZORDER or SWP_NOACTIVATE);
end
else
begin
PInteger(@Left)^ := ALeft;
PInteger(@Top)^ := ATop;
PInteger(@Width)^ := AWidth;
PInteger(@Height)^ := AHeight;
if HandleAllocated then
begin
WindowPlacement.Length := SizeOf(WindowPlacement);
GetWindowPlacement(WindowHandle, @WindowPlacement);
WindowPlacement.rcNormalPosition := BoundsRect;
SetWindowPlacement(WindowHandle, @WindowPlacement);
end;
end;
UpdateBoundsRect(Rect(Left, Top, Left + Width, Top + Height));
RequestAlign;
end;
end;
procedure TControlResizeFixWinControl.HandleAlignControls(AControl: TControl; var R: TRect);
function AlignWork: Boolean;
var
I: Integer;
begin
Result := True;
for I := ControlCount - 1 downto 0 do
if (Controls[I].Align <> alNone) or
(Controls[I].Anchors <> [akLeft, akTop]) then
Exit;
Result := False;
end;
var
OwnAlignControlList, TempAlignControlList: TList;
ResizeList: TList;
ResizeCounts: TList; // of Integer
Ctrl: TWinControl;
I, Index: Integer;
begin
if AlignWork then
begin
OwnAlignControlList := nil;
try
if AlignControlList = nil then
begin
OwnAlignControlList := TList.Create;
AlignControlList := OwnAlignControlList;
end;
AlignControls(AControl, R);
if (OwnAlignControlList <> nil) and (OwnAlignControlList.Count > 0) then
begin
{ Convert recursion into an iteration to prevent the kernel stack overflow }
ResizeList := TList.Create;
ResizeCounts := TList.Create;
try
{ The controls in the OwnAlignControlList must be added to ResizeList in reverse order.
Otherwise the OnResize events aren't fired in correct order. }
AlignControlList := TList.Create;
try
repeat
try
for I := OwnAlignControlList.Count - 1 downto 0 do
begin
Ctrl := TWinControl(OwnAlignControlList[I]);
Index := ResizeList.IndexOf(Ctrl);
{ An endless resizing component was stopped by the kernel stack overflow bug.
So we must catch this condition to prevent an endless loop. }
if (Index = -1) or (Integer(ResizeCounts[Index]) < 30) then
begin
Ctrl.Realign;
if Index <> -1 then
ResizeCounts[Index] := Pointer(Integer(ResizeCounts[Index]) + 1);
ResizeCounts.Add(Pointer(0)); // keep index in sync
ResizeList.Add(Ctrl);
end
else if Index <> -1 then
begin
{$WARNINGS OFF}
if DebugHook <> 0 then
{$WARNINGS ON}
OutputDebugString(PChar(Format('The component "%s" of class %s has an endless resize loop', [Ctrl.Name, Ctrl.ClassName])));
end;
end;
finally
OwnAlignControlList.Clear;
{ Switch lists }
TempAlignControlList := AlignControlList;
AlignControlList := OwnAlignControlList;
OwnAlignControlList := TempAlignControlList;
end;
until (OwnAlignControlList.Count = 0) {or EndlessResizeDetection};
finally
{ Let another AlignControlList handle any alignment that comes from the
OnResize method. }
FreeAndNil(AlignControlList);
end;
{ Fire Resize events }
for I := ResizeList.Count - 1 downto 0 do
begin
Ctrl := TWinControl(ResizeList[I]);
if not (csLoading in Ctrl.ComponentState) then
TOpenWinControl(Ctrl).Resize;
end;
finally
ResizeCounts.Free;
ResizeList.Free;
end;
{$IFDEF OPTIMIZED_RESIZE_REDRAW}
Invalidate;
{$ENDIF OPTIMIZED_RESIZE_REDRAW}
end;
finally
if OwnAlignControlList <> nil then
begin
AlignControlList := nil;
FreeAndNil(OwnAlignControlList);
end;
end;
end
else
AlignControls(AControl, R);
end;
procedure TControlResizeFixWinControl.WMSize(var Message: TWMSize);
begin
{$IFDEF DELPHI2005_UP}
UpdateBounds;
{$IFDEF DELPHI2006_UP}
UpdateExplicitBounds;
{$ENDIF DELPHI2006_UP}
{$ELSE}
if HandleAllocated then
Perform(WM_MOVE, 0, LPARAM(Left and $0000ffff) or (Top shl 16)); // calls the private UpdateBounds
{$ENDIF DELPHI2005_UP}
DefaultHandler(Message);
if AlignControlList <> nil then
begin
if AlignControlList.IndexOf(Self) = -1 then
AlignControlList.Add(Self)
end
else
begin
Realign;
if not (csLoading in ComponentState) then
Resize;
end;
end;
procedure TControlResizeFixWinControl.AlignControl(AControl: TControl);
var
Rect: TRect;
begin
if not HandleAllocated or (csDestroying in ComponentState) then
Exit;
{$IFDEF DELPHI2005_UP}
if AlignDisabled then
{$ELSE}
if TD5WinControlPrivate(Self).FAlignLevel <> 0 then
{$ENDIF DELPHI2005_UP}
ControlState := ControlState + [csAlignmentNeeded]
else
begin
DisableAlign;
try
Rect := GetClientRect;
HandleAlignControls(AControl, Rect);
finally
ControlState := ControlState - [csAlignmentNeeded];
EnableAlign;
end;
end;
end;
function GetAlignControlProc: Pointer;
var
P: PByteArray;
Offset: Integer;
MemInfo: TMemoryBasicInformation;
begin
P := GetActualAddr(@TWinControl.Realign);
if (P <> nil) and (VirtualQuery(P, MemInfo, SizeOf(MemInfo)) = SizeOf(MemInfo)) then
begin
if (MemInfo.AllocationProtect <> PAGE_NOACCESS) then
begin
Offset := 0;
while Offset < $40 do
begin
if ((P[0] = $33) and (P[1] = $D2)) or // xor edx,edx
((P[0] = $31) and (P[1] = $D2)) then // xor edx,edx
begin
if P[2] = $E8 then // call TWinControl.AlignControl
begin
Inc(PByte(P), 2);
Result := PAnsiChar(P) + 5 + PInteger(PAnsiChar(P) + 1)^;
Exit;
end
else if (P[2] = $8B) and (P[3] = $45) and (P[4] = $FC) and // mov eax,[ebp-$04]
(P[5] = $E8) then // call TWinControl.AlignControl
begin
Inc(PByte(P), 5);
Result := PAnsiChar(P) + 5 + PInteger(PAnsiChar(P) + 1)^;
Exit;
end;
end;
Inc(PByte(P));
Inc(Offset);
end;
end;
end;
Result := nil;
end;
procedure InitControlResizeFix;
begin
WinControl_AlignControlProc := GetAlignControlProc;
WinControl_WMSize := GetDynamicMethod(TWinControl, WM_SIZE);
WinControl_SetBounds := @TOpenWinControl.SetBounds;
if (WinControl_AlignControlProc <> nil) and (WinControl_WMSize <> nil) then
begin
DebugLog('ControlResizeFix');
{ Redirect the original function to the bug fixed version }
HookProc(WinControl_AlignControlProc, @TControlResizeFixWinControl.AlignControl, BackupAlignControl);
HookProc(WinControl_WMSize, @TControlResizeFixWinControl.WMSize, BackupWMSize);
{$IFDEF OPTIMIZED_RESIZE_REDRAW}
HookProc(WinControl_SetBounds, @TControlResizeFixWinControl.SetBounds, BackupSetBounds);
{$ENDIF OPTIMIZED_RESIZE_REDRAW}
end;
end;
procedure FiniControlResizeFix;
begin
{ Restore the original function }
UnhookProc(WinControl_AlignControlProc, BackupAlignControl);
UnhookProc(WinControl_WMSize, BackupWMSize);
UnhookProc(WinControl_SetBounds, BackupSetBounds);
end;
{$ENDIF ControlResizeFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #59654: TActionList access already released FActions field }
{$IFDEF ActionListAVFix}
var
HookTCustomActionList_Notification: TXRedirCode;
type
TCustomActionListFix = class(TCustomActionList)
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
end;
procedure TCustomActionListFix.Notification(AComponent: TComponent; Operation: TOperation);
var
P: procedure(Instance: TComponent; AComponent: TComponent; Operation: TOperation);
begin
{ inherited: }
P := @TOpenComponent.Notification;
P(Self, AComponent, Operation);
if Operation = opRemove then
begin
if AComponent = Images then
Images := nil
else if {<*}not (csDestroying in ComponentState) and{*>} (AComponent is TContainedAction) then
RemoveAction(TContainedAction(AComponent));
end;
end;
procedure InitActionListAVFix;
begin
DebugLog('ActionListAVFix');
HookProc(@TOpenCustomActionList.Notification, @TCustomActionListFix.Notification, HookTCustomActionList_Notification);
end;
procedure FiniActionListAVFix;
begin
UnhookProc(@TOpenCustomActionList.Notification, HookTCustomActionList_Notification);
end;
{$ENDIF ActionListAVFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #54286 : Parent-PopupMenu overrides standard context menu (edit, memo, combobox, ...) }
{$IFDEF ContextMenuFix}
type
TContextMenuFixWinControl = class(TWinControl)
public
procedure DefaultHandler(var Message); override;
end;
var
RM_GetObjectInstance: DWORD;
BackupDefaultHandler: TXRedirCode;
procedure TContextMenuFixWinControl.DefaultHandler(var Message);
type
TDefHandler = procedure(Self: TControl; var Message);
begin
if HandleAllocated then
begin
with TMessage(Message) do
begin
{ Here was the WM_CONTEXTMENU Code that is not necessary because
DefWndProc will send this message to the parent control. }
{ Keep the odd bahavior for grids because everybody seems to be used to it. }
if (Msg = WM_CONTEXTMENU) and (Parent <> nil) and (Parent is TCustomGrid) then
begin
Result := Parent.Perform(Msg, WParam, LParam);
if Result <> 0 then Exit;
end;
case Msg of
WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC:
Result := SendMessage(LParam, CN_BASE + Msg, WParam, LParam);
CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:
begin
SetTextColor(WParam, ColorToRGB(Font.Color));
SetBkColor(WParam, ColorToRGB(Brush.Color));
Result := Brush.Handle;
end;
else
if Msg = RM_GetObjectInstance then
Result := LRESULT(Self)
else
Result := CallWindowProc(DefWndProc, Handle, Msg, WParam, LParam);
end;
if Msg = WM_SETTEXT then
SendDockNotification(Msg, WParam, LParam);
end;
end
else
//inherited DefaultHandler(Message);
TDefHandler(@TControl.DefaultHandler)(Self, Message);
end;
procedure InitContextMenuFix;
var
HInst: HMODULE;
begin
HInst := FindHInstance(Pointer(TWinControl)); // get the HInstance of the module that contains Controls.pas
RM_GetObjectInstance := RegisterWindowMessage(PChar(Format('ControlOfs%.8X%.8X', [HInst, GetCurrentThreadId])));
DebugLog('ContextMenuFix');
{ Redirect the original function to the bug fixed version }
HookProc(@TWinControl.DefaultHandler, @TContextMenuFixWinControl.DefaultHandler, BackupDefaultHandler);
end;
procedure FiniContextMenuFix;
begin
UnhookProc(@TWinControl.DefaultHandler, BackupDefaultHandler);
end;
{$ENDIF ContextMenuFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #50097: ObjAuto access violation on XEON (Data Execution Prevention bug) }
{$IFDEF ObjAutoDEPFix}
type
PParameterInfos = ^TParameterInfos;
TParameterInfos = array[0..255] of ^PTypeInfo;
TMethodHandlerInstance = class
protected
MethodHandler: IMethodHandler;
TypeData: PTypeData;
ParamInfos: PParameterInfos;
Return: array[0..2] of Byte;
ParamOffsets: array of Word;
end;
{ procedure TMethodHandlerInstance.RegisterStub;
50 push eax
51 push ecx
52 push edx
89E2 mov edx,esp
E866FDFFFF call TMethodHandlerInstance.Handler
89442404 mov [esp+$04],eax
58 pop eax
58 pop eax
59 pop ecx
8D4910 lea ecx,[ecx+$10]
FFE1 jmp ecx
C3 ret }
type
PRegisterStubRec = ^TRegisterStubRec;
TRegisterStubRec = packed record
Push3Reg: array[0..2] of Byte;
MovEdxEsp: Word;
CallHandler: array[0..4] of Byte;
SaveRetValue: LongWord;
Pop3Reg: array[0..2] of Byte;
case Integer of
0: (LeaEcxEcx: Word;
Off: Byte;
JmpEcx: Word);
1: (Jmp: Byte;
Offset: TJumpOfs);
end;
var
RegisterStub: PRegisterStubRec;
OrgCode: array[0..4] of Byte;
procedure RegisterStubRet;
asm
lea ecx, [ecx].TMethodHandlerInstance.Return
cmp byte ptr [ecx], $C2
jne @@Leave
movzx ecx, word ptr [ecx+$01]
add ecx, 3
and ecx, $FC
add esp, ecx
{ restore return address }
neg ecx
mov ecx, [esp+ecx] // load return address
push ecx // restore return address
@@Leave:
end;
procedure InitObjAutoDEPFix;
var
Func: TTypeData;
M: TMethod;
OldProtect: Cardinal;
begin
FillChar(Func, SizeOf(Func), 0);
Func.MethodKind := mkProcedure;
Func.ParamCount := 0;
M := CreateMethodPointer(nil, @Func);
RegisterStub := M.Code;
ReleaseMethodPointer(M);
if (RegisterStub.Push3Reg[0] = $50) and
(RegisterStub.Push3Reg[1] = $51) and
(RegisterStub.Push3Reg[2] = $52) and
(RegisterStub.MovEdxEsp = $E289) and
(RegisterStub.SaveRetValue = $04244489) and
(RegisterStub.Pop3Reg[0] = $58) and
(RegisterStub.Pop3Reg[1] = $58) and
(RegisterStub.Pop3Reg[2] = $59) and
(RegisterStub.LeaEcxEcx = $498D) and
(RegisterStub.JmpEcx = $E1FF) then
begin
if VirtualProtect(@RegisterStub.Jmp, SizeOf(TXRedirCode), PAGE_EXECUTE_READWRITE, OldProtect) then
begin
DebugLog('ObjAutoDEPFix');
Move(RegisterStub.Jmp, OrgCode[0], SizeOf(OrgCode));
RegisterStub.Jmp := $E9;
RegisterStub.Offset := TJumpOfs(@RegisterStubRet) - (TJumpOfs(@RegisterStub.Jmp) + SizeOf(TXRedirCode));
VirtualProtect(@RegisterStub.Jmp, SizeOf(TXRedirCode), OldProtect, OldProtect);
FlushInstructionCache(GetCurrentProcess, @RegisterStub.Jmp, SizeOf(TXRedirCode));
end;
end
else
RegisterStub := nil;
end;
procedure FiniObjAutoDEPFix;
var
OldProtect: Cardinal;
begin
if RegisterStub <> nil then
begin
if VirtualProtect(@RegisterStub.Jmp, SizeOf(TXRedirCode), PAGE_EXECUTE_READWRITE, OldProtect) then
begin
Move(OrgCode[0], RegisterStub.Jmp, SizeOf(OrgCode));
VirtualProtect(@RegisterStub.Jmp, SizeOf(TXRedirCode), OldProtect, OldProtect);
FlushInstructionCache(GetCurrentProcess, @RegisterStub.Jmp, SizeOf(TXRedirCode));
end;
end;
end;
{$ENDIF ObjAutoDEPFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ Classes.MakeObjectInstance memory leak fix }
{$IFDEF MkObjInstLeakFix}
{ Limitation:
The memory is only released if there is no dangling object instance in the
memory block. }
var
UnRegisterModuleClassesHook: TXRedirCode;
MkObjInstLeakHooked: Boolean;
procedure HookedUnRegisterModuleClasses(Module: HMODULE);
forward;
const
InstanceCount = 313;
PageSize = 4096;
{ Object instance management }
type
PPObjectInstance = ^PObjectInstance;
PObjectInstance = ^TObjectInstance;
TObjectInstance = packed record
Code: Byte;
Offset: Integer;
case Integer of
0: (Next: PObjectInstance);
1: (Method: TWndMethod);
end;
PPInstanceBlock = ^PInstanceBlock;
PInstanceBlock = ^TInstanceBlock;
TInstanceBlock = packed record
Next: PInstanceBlock;
Code: array[1..2] of Byte;
WndProcPtr: Pointer;
Instances: array[0..InstanceCount] of TObjectInstance;
end;
(*
procedure FreeObjectInstance(ObjectInstance: Pointer);
begin
if ObjectInstance <> nil then
begin
// 85C0 test eax,eax
// 740E jz $0041b2f6
PObjectInstance(ObjectInstance)^.Next := InstFreeList;
// 8B15E8594600 mov edx,[$004659e8]
// 895005 mov [eax+$05],edx
InstFreeList := ObjectInstance;
// A3E8594600 mov [$004659e8],eax
end;
end;
// C3 ret
*)
type
TParamRec = packed record
Op: Word;
Off: Byte;
end;
PFreeObjInstRec = ^TFreeObjInstRec;
TFreeObjInstRec = packed record
TestEaxEax: Word;
Jz1: Word;
AssignToNext: packed record
MovEdx: Word;
Address: Cardinal;
MovEaxOffEdx: Word;
Off: Byte;
end;
AssignToInstFreeList: packed record
MovMemToEax: Byte;
Address: Cardinal;
end;
Ret: Byte;
end;
PFreeObjInstNoOptRec = ^TFreeObjInstNoOptRec;
TFreeObjInstNoOptRec = packed record
PushEbp: Byte; // $55
MovEbpEsp: Word; // $8B EC
PushEcx: Byte; // $51
MovOffReg: TParamRec; // $89 45 FC
Cmp: packed record
Op: Word; // $83 7D
Off: Byte; // $FC
Value: Byte; // $00
end;
Jz1: Word; // $74 14
LoadParam1: TParamRec; // $8B 45 FC
AssignToNext: packed record
MovEdx: Word; // $8B 15
Address: Cardinal;
end;
end;
procedure GetObjectInstancePointers(out InstFreeListP: PPObjectInstance; out InstBlockListP: PPInstanceBlock);
var
FreeObjInst: PFreeObjInstRec;
FreeObjInstNoOpt: PFreeObjInstNoOptRec;
begin
InstFreeListP := nil;
InstBlockListP := nil;
FreeObjInst := GetActualAddr(@Classes.FreeObjectInstance);
FreeObjInstNoOpt := Pointer(FreeObjInst);
if (FreeObjInst.TestEaxEax = $C085) and (FreeObjInst.Jz1 = $0E74) and
(FreeObjInst.AssignToNext.MovEdx = $158B) and
(FreeObjInst.AssignToNext.MovEaxOffEdx = $5089) and (FreeObjInst.AssignToNext.Off = $05) and
(FreeObjInst.AssignToInstFreeList.MovMemToEax = $A3) and
(FreeObjInst.Ret = $C3) then
begin
InstFreeListP := PPObjectInstance(FreeObjInst.AssignToNext.Address);
InstBlockListP := PPInstanceBlock(FreeObjInst.AssignToNext.Address - SizeOf(Pointer));
end
else
if (FreeObjInstNoOpt.PushEbp = $55) and (FreeObjInstNoOpt.MovEbpEsp = $EC8B) and
(FreeObjInstNoOpt.PushEcx = $51) and
(FreeObjInstNoOpt.MovOffReg.Op = $4589) and (FreeObjInstNoOpt.MovOffReg.Off = $FC) and
(FreeObjInstNoOpt.Cmp.Op = $7D83) and (FreeObjInstNoOpt.Cmp.Off = $FC) and (FreeObjInstNoOpt.Cmp.Value = $00) and
(FreeObjInstNoOpt.Jz1 = $1474) and
(FreeObjInstNoOpt.LoadParam1.Op = $458B) and (FreeObjInstNoOpt.LoadParam1.Off = $FC) and
(FreeObjInstNoOpt.AssignToNext.MovEdx = $158B) then
begin
InstFreeListP := PPObjectInstance(FreeObjInstNoOpt.AssignToNext.Address);
InstBlockListP := PPInstanceBlock(FreeObjInstNoOpt.AssignToNext.Address - SizeOf(Pointer));
end;
end;
procedure CleanupInstFreeList(var InstFreeList: PObjectInstance; BlockStart, BlockEnd: PAnsiChar);
var
Prev, Next, Item: PObjectInstance;
begin
Prev := nil;
Item := InstFreeList;
while Item <> nil do
begin
Next := Item.Next;
if (PAnsiChar(Item) >= BlockStart) and (PAnsiChar(Item) <= BlockEnd) then
begin
Item := Prev;
if Prev = nil then
InstFreeList := Next
else
Prev.Next := Next;
end;
Prev := Item;
Item := Next;
end;
end;
function CalcFreeInstBlockItems(Item: PObjectInstance; Block: PInstanceBlock): Integer;
var
I: Integer;
begin
Result := 0;
while Item <> nil do
begin
for I := High(Block.Instances) downto 0 do
begin
if @Block.Instances[I] = Item then
begin
Inc(Result);
Break;
end;
end;
Item := Item.Next;
end;
end;
procedure ReleaseObjectInstanceBlocks;
var
InstFreeListP: PPObjectInstance;
InstBlockListP: PPInstanceBlock;
NextBlock, Block, PrevBlock: PInstanceBlock;
FreeCount: Integer;
begin
GetObjectInstancePointers(InstFreeListP, InstBlockListP);
if (InstFreeListP = nil) or (InstBlockListP = nil) then
begin
OutputDebugString('Cannot apply Classes.FreeObjectInstance memory leak-fix');
Exit;
end;
Block := InstBlockListP^;
PrevBlock := nil;
while Block <> nil do
begin
NextBlock := Block.Next;
{ Obtain the number of free items in the InstanceBlock }
FreeCount := CalcFreeInstBlockItems(InstFreeListP^, Block);
{ Release memory if the InstanceBlock contains only "free" items }
if FreeCount = Length(Block.Instances) then
begin
{ Remove all InstFreeList items that refer to the InstanceBlock }
CleanupInstFreeList(InstFreeListP^, PAnsiChar(Block), PAnsiChar(Block) + SizeOf(TInstanceBlock) - 1);
VirtualFree(Block, 0, MEM_RELEASE);
Block := PrevBlock;
if PrevBlock = nil then
InstBlockListP^ := NextBlock
else
PrevBlock.Next := NextBlock;
end;
{ Next InstanceBlock }
PrevBlock := Block;
Block := NextBlock;
end;
{ Maybe the finalization was executed before the Application object was destroyed.
Classes.Finalization calls UnRegisterModuleClasses(). We hook into that function
to execute our finalization in the Classes.Finalization. }
if (InstBlockListP^ <> nil) and not MkObjInstLeakHooked and
(FindHInstance(GetActualAddr(@UnRegisterModuleClasses)) = HInstance) then
begin
MkObjInstLeakHooked := True;
HookProc(@UnRegisterModuleClasses, @HookedUnRegisterModuleClasses, UnRegisterModuleClassesHook);
end;
end;
procedure HookedUnRegisterModuleClasses(Module: HMODULE);
begin
UnhookProc(@UnRegisterModuleClasses, UnRegisterModuleClassesHook);
try
UnRegisterModuleClasses(Module);
if Module = HInstance then
ReleaseObjectInstanceBlocks;
finally
HookProc(@UnRegisterModuleClasses, @HookedUnRegisterModuleClasses, UnRegisterModuleClassesHook);
end;
end;
{$ENDIF MkObjInstLeakFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #58938: MainForm Minimize minimizes in the background }
{$IFDEF AppMinimizeFix}
var
AppMinimizePatchAddress: ^TJumpOfs;
OrgAppMinimizeSetActiveWindowOffset: TJumpOfs;
function ApplicationMinimizeSetActiveWindow(hWnd: HWND): HWND; stdcall;
begin
if Application.MainFormOnTaskBar then
Result := GetActiveWindow
else
Result := SetActiveWindow(hWnd);
end;
{
Forms.pas.7850: NormalizeTopMosts;
8BC3 mov eax,ebx
E84AF4FFFF call TApplication.NormalizeTopMosts
Forms.pas.7851: SetActiveWindow(FHandle); // WM_ACTIVATEAPP can set AppIconic to False
8B4330 mov eax,[ebx+$30]
50 push eax
E87D1AFAFF call SetActiveWindow
Forms.pas.7852: AppIconic := True; // Set AppIconic here just to be safe
C605A8EC4B0001 mov byte ptr [$004beca8],$01
}
procedure InitAppMinimizeFix;
var
P: PAnsiChar;
Len: Integer;
NewOffset: TJumpOfs;
n: Cardinal;
begin
P := GetActualAddr(@TApplication.Minimize);
Len := 0;
while Len < 64 do
begin
if (P[0] = #$8B) and (P[1] = #$C3) and // mov eax,ebx
(P[2] = #$E8) and // call TApplication.NormalizeTopMosts
(P[7] = #$8B) and (P[8] = #$43) and (P[9] = #$30) and // mov eax,[ebx+$30]
(P[10] = #$50) and // push eax
(P[11] = #$E8) and // call SetActiveWindow
(P[16] = #$C6) and (P[17] = #$05) and (P[22] = #$01) then // mov byte ptr [$004beca8],$01
begin
DebugLog('AppMinimizeFix');
AppMinimizePatchAddress := Pointer(P + 12);
OrgAppMinimizeSetActiveWindowOffset := AppMinimizePatchAddress^;
NewOffset := PAnsiChar(@ApplicationMinimizeSetActiveWindow) - PAnsiChar(AppMinimizePatchAddress) - SizeOf(TJumpOfs);
WriteProcessMemory(GetCurrentProcess, AppMinimizePatchAddress, @NewOffset, SizeOf(NewOffset), n);
Exit;
end;
Inc(P);
Inc(Len);
end;
end;
procedure FiniAppMinimizeFix;
var
n: Cardinal;
begin
if AppMinimizePatchAddress <> nil then
WriteProcessMemory(GetCurrentProcess, AppMinimizePatchAddress, @OrgAppMinimizeSetActiveWindowOffset, SizeOf(TJumpOfs), n);
end;
{$ENDIF AppMinimizeFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #64484: SysUtils.Abort can raise an AccessViolation }
{$IFDEF SysUtilsAbortFix}
var
SysUtilsAbortHook: TXRedirCode;
procedure SysUtilsAbort;
{ No dependency on EBP register }
procedure ThrowException(ReturnAddr: Pointer);
begin
raise EAbort.CreateRes(@SOperationAborted) at ReturnAddr;
end;
asm
pop eax
jmp ThrowException
end;
procedure InitSysUtilsAbortFix;
begin
DebugLog('SysUtilsAbortFix');
HookProc(@SysUtils.Abort, @SysUtilsAbort, SysUtilsAbortHook);
end;
procedure FiniSysUtilsAbortFix;
begin
UnhookProc(@SysUtils.Abort, SysUtilsAbortHook);
end;
{$ENDIF SysUtilsAbortFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #58939: No taskbar button when starting from shelllink with Show=Minimized }
{$IFDEF CmdShowMinimizeFix}
var
ApplicationRunHook: TXRedirCode;
InitialMainFormState: ^TWindowState;
procedure ApplicationRun(App: TApplication);
begin
UnhookProc(@TApplication.Run, ApplicationRunHook);
ApplicationRunHook.Jump := 0;
{$WARNINGS OFF}
if (CmdShow = SW_SHOWMINNOACTIVE) and (InitialMainFormState <> nil) then
{$WARNINGS ON}
InitialMainFormState^ := wsMinimized;
App.Run;
end;
{
Forms.pas.8214: if (FMainForm.FWindowState = wsMinimized) or (InitialMainFormState = wsMinimized) then
8B45FC mov eax,[ebp-$04]
8B4044 mov eax,[eax+$44]
80B87302000001 cmp byte ptr [eax+$00000273],$01
7409 jz $004669a5
803DC8784B0001 cmp byte ptr [$004b78c8],$01
751E jnz $004669c3
Forms.pas.8216: Minimize;
8B45FC mov eax,[ebp-$04]
E8FFF5FFFF call TApplication.Minimize
Forms.pas.8217: if (InitialMainFormState = wsMinimized) then
803DC8784B0001 cmp byte ptr [$004b78c8],$01
7514 jnz $004669ca
}
function FindInitialMainFormState: Pointer;
var
P: PAnsiChar;
Len: Integer;
begin
P := GetActualAddr(@TApplication.Run);
Len := 0;
while Len < 128 do
begin
if (P[0] = #$74) and (P[1] = #$09) and // jz $004669a5
(P[2] = #$80) and (P[3] = #$3D) and (P[8] = #$01) and // cmp byte ptr [$004b78c8],$01
(P[9] = #$75) and (P[10] = #$1E) and // jnz $004669c3
(P[11] = #$8B) and (P[12] = #$45) and (P[13] = #$FC) then // mov eax,[ebp-$04]
begin
DebugLog('CmdShowMinimizeFix');
Result := PPointer(P + 4)^;
Exit;
end;
Inc(P);
Inc(Len);
end;
Result := nil;
end;
procedure InitCmdShowMinimizeFix;
begin
InitialMainFormState := FindInitialMainFormState;
if InitialMainFormState <> nil then
HookProc(@TApplication.Run, @ApplicationRun, ApplicationRunHook);
end;
procedure FiniCmdShowMinimizeFix;
begin
UnhookProc(@TApplication.Run, ApplicationRunHook);
end;
{$ENDIF CmdShowMinimizeFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #35001: MDIChild's active control focus is not set correctly }
{$IFDEF MDIChildFocusFix}
var
CustomFormFocusControlHook: TXRedirCode;
procedure CustomFormFocusControl(Self: TOpenCustomForm; Control: TWinControl);
var
WasActive: Boolean;
begin
with Self do
begin
WasActive := Active;
{ Synchronize Windows's focus with VCL's focus }
if WasActive and (FormStyle = fsMDIChild) and (Control <> nil) and (Control = ActiveControl) and
Control.HandleAllocated and not Control.Focused then
begin
Windows.SetFocus(Control.Handle);
end;
ActiveControl := Control;
if not WasActive then
SetFocus;
end;
end;
procedure InitMDIChildFocusFix;
begin
DebugLog('MDIChildFocusFix');
HookProc(@TOpenCustomForm.FocusControl, @CustomFormFocusControl, CustomFormFocusControlHook);
end;
procedure FiniMDIChildFocusFix;
begin
UnhookProc(@TOpenCustomForm.FocusControl, CustomFormFocusControlHook);
end;
{$ENDIF MDIChildFocusFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #56252: TPageControl flickers a lot with active theming }
{ QC #68730: TLabel is not painted on a themed, double-buffered TTabSheet in Vista }
{ TLabels on TTabSheet are not painted (themes) if a TWinControl like TMemo is on the TTabSheet (TWinControl.PaintWindow bug) }
{$IFDEF PageControlPaintingFix}
type
TFlickerlessPageControl = class(TPageControl)
procedure NewWndProc(var Msg: TMessage);
end;
TFlickerlessTabSheet = class(TTabSheet)
protected
procedure NewCreateParams(var Params: TCreateParams);
procedure WMPrintClient(var Message: TWMPrintClient); message WM_PRINTCLIENT;
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure NewWndProc(var Msg: TMessage);
end;
{ TFlickerlessPageControl }
procedure TFlickerlessPageControl.NewWndProc(var Msg: TMessage);
begin
case Msg.Msg of
WM_SIZE:
begin
inherited WndProc(Msg);
{ Update the page control immediately to prevent flicker }
if ThemeServices.ThemesEnabled then
RedrawWindow(Handle, nil, 0, RDW_UPDATENOW or RDW_ERASE);
end;
else
inherited WndProc(Msg); // "inherited" is required here, otherwise this would be an endless recursion
end;
end;
{ TFlickerlessTabSheet }
procedure TFlickerlessTabSheet.WMEraseBkgnd(var Message: TWMEraseBkgnd);
var
R: TRect;
begin
if (PageControl <> nil) and (PageControl.Style = tsTabs) and ThemeServices.ThemesEnabled then
begin
{ Paint the TabSheet background without filling the gray color from the parent.
And fill it in WM_ERASEBKGND where it belongs instead of WM_PRINTCLIENT. }
GetWindowRect(Handle, R);
OffsetRect(R, -R.Left, -R.Top);
ThemeServices.DrawElement(Message.DC, ThemeServices.GetElementDetails(ttBody), R);
Message.Result := 1;
end
else
inherited;
end;
procedure TFlickerlessTabSheet.WMPrintClient(var Message: TWMPrintClient);
begin
{ Fixes "Labels are not painted if themes are enabled" bug }
{ Description see QC #3850 / RAID #159864, same as in TFrame.
A better solution would be to change TWinControl.PaintWindow to
use WM_PRINTCLIENT if it is called from a WM_PRINTCLIENT handler
and to use WM_PAINT only if it is called from a WM_PAINT handler. }
DefaultHandler(Message);
PaintControls(Message.DC, nil);
end;
procedure TFlickerlessTabSheet.NewCreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
if ThemeServices.ThemesEnabled then
Params.WindowClass.style := Params.WindowClass.style and not (CS_VREDRAW or CS_HREDRAW);
end;
procedure TFlickerlessTabSheet.NewWndProc(var Msg: TMessage);
begin
{ Instead of hooking the DMT we simply call the replacement handlers directly }
case Msg.Msg of
WM_ERASEBKGND:
WMEraseBkgnd(TWMEraseBkgnd(Msg));
WM_PRINTCLIENT:
WMPrintClient(TWMPrintClient(Msg));
WM_SIZE:
begin
inherited WndProc(Msg);
if ThemeServices.ThemesEnabled then
RedrawWindow(Handle, nil, 0, RDW_UPDATENOW or RDW_INVALIDATE or RDW_ERASE or RDW_ALLCHILDREN);
end;
else
inherited WndProc(Msg); // "inherited" is required here, otherwise this would be an endless recursion
end;
end;
procedure InitPageControlPaintingFix;
begin
DebugLog('PageControlPaintingFix');
ReplaceVmtField(TTabSheet, @TFlickerlessTabSheet.WndProc, @TFlickerlessTabSheet.NewWndProc);
ReplaceVmtField(TTabSheet, @TFlickerlessTabSheet.CreateParams, @TFlickerlessTabSheet.NewCreateParams);
ReplaceVmtField(TPageControl, @TFlickerlessPageControl.WndProc, @TFlickerlessPageControl.NewWndProc);
end;
procedure FiniPageControlPaintingFix;
begin
ReplaceVmtField(TTabSheet, @TFlickerlessTabSheet.NewWndProc, @TFlickerlessTabSheet.WndProc);
ReplaceVmtField(TTabSheet, @TFlickerlessTabSheet.NewCreateParams, @TFlickerlessTabSheet.CreateParams);
ReplaceVmtField(TPageControl, @TFlickerlessPageControl.NewWndProc, @TFlickerlessPageControl.WndProc);
end;
{$ENDIF PageControlPaintingFix}
{---------------------------------------------------------------------------}
{---------------------------------------------------------------------------}
{$IFDEF GridFlickerFix}
type
{$IFDEF VCLFIXPACK_DB_SUPPORT}
TFlickerlessDBGrid = class(TDBGrid)
protected
procedure NewWndProc(var Msg: TMessage);
end;
{$ENDIF VCLFIXPACK_DB_SUPPORT}
TFlickerlessStringGrid = class(TStringGrid)
protected
procedure NewWndProc(var Msg: TMessage);
end;
TFlickerlessDrawGrid = class(TDrawGrid)
protected
procedure NewWndProc(var Msg: TMessage);
end;
{ TFlickerlessDBGrid }
procedure GridWMEraseBkgnd(Grid: TCustomGrid; var Message: TWMEraseBkgnd);
var
R: TRect;
Size: TSize;
begin
{ Fill the area between the two scroll bars. }
Size.cx := GetSystemMetrics(SM_CXVSCROLL);
Size.cy := GetSystemMetrics(SM_CYHSCROLL);
if {Grid.BiDiMode <> bdLeftToRight}Grid.UseRightToLeftAlignment then
R := Bounds(0, Grid.Height - Size.cy, Size.cx, Size.cy)
else
R := Bounds(Grid.Width - Size.cx, Grid.Height - Size.cy, Size.cx, Size.cy);
FillRect(Message.DC, R, Grid.Brush.Handle);
Message.Result := 1;
end;
{$IFDEF VCLFIXPACK_DB_SUPPORT}
procedure TFlickerlessDBGrid.NewWndProc(var Msg: TMessage);
{var
Rect: TRect;}
begin
if Msg.Msg = WM_ERASEBKGND then
GridWMEraseBkgnd(Self, TWMEraseBkgnd(Msg))
else if Msg.Msg = WM_PAINT then
begin
{if UseRightToLeftAlignment then
Begin
Rect.TopLeft := ClientRect.TopLeft;
Rect.BottomRight := ClientRect.BottomRight;
InvalidateRect(Handle, @Rect, False);
end;}
inherited WndProc(Msg);
end
else
inherited WndProc(Msg);
end;
{$ENDIF VCLFIXPACK_DB_SUPPORT}
procedure TFlickerlessStringGrid.NewWndProc(var Msg: TMessage);
begin
if Msg.Msg = WM_ERASEBKGND then
GridWMEraseBkgnd(Self, TWMEraseBkgnd(Msg))
else
inherited WndProc(Msg);
end;
procedure TFlickerlessDrawGrid.NewWndProc(var Msg: TMessage);
begin
if Msg.Msg = WM_ERASEBKGND then
GridWMEraseBkgnd(Self, TWMEraseBkgnd(Msg))
else
inherited WndProc(Msg);
end;
procedure InitGridFlickerFix;
begin
DebugLog('GridFlickerFix');
{$IFDEF VCLFIXPACK_DB_SUPPORT}
ReplaceVmtField(TDBGrid, @TFlickerlessDBGrid.WndProc, @TFlickerlessDBGrid.NewWndProc);
{$ENDIF VCLFIXPACK_DB_SUPPORT}
ReplaceVmtField(TStringGrid, @TFlickerlessStringGrid.WndProc, @TFlickerlessStringGrid.NewWndProc);
ReplaceVmtField(TDrawGrid, @TFlickerlessDrawGrid.WndProc, @TFlickerlessDrawGrid.NewWndProc);
end;
procedure FiniGridFlickerFix;
begin
{$IFDEF VCLFIXPACK_DB_SUPPORT}
ReplaceVmtField(TDBGrid, @TFlickerlessDBGrid.NewWndProc, @TFlickerlessDBGrid.WndProc);
{$ENDIF VCLFIXPACK_DB_SUPPORT}
ReplaceVmtField(TStringGrid, @TFlickerlessStringGrid.NewWndProc, @TFlickerlessStringGrid.WndProc);
ReplaceVmtField(TDrawGrid, @TFlickerlessDrawGrid.NewWndProc, @TFlickerlessDrawGrid.WndProc);
end;
{$ENDIF GridFlickerFix}
{---------------------------------------------------------------------------}
{ ---------------------------------------------------------------------------- }
{ QC #69112: TSpeedButton is painted as a black rectangle on a double buffered panel on a sheet of glass. }
{$IFDEF SpeedButtonGlassFix}
type
TGlassableSpeedButton = class(TSpeedButton)
procedure NewWndProc(var Msg: TMessage);
end;
function ControlInGlassPaint(AControl: TControl): Boolean;
var
Parent: TWinControl;
begin
Result := csGlassPaint in AControl.ControlState;
if Result then
begin
{ Control could be on a double buffered control. In that case the csGlassPaint flag
shouldn't be set. }
Parent := AControl.Parent;
while (Parent <> nil) and not Parent.DoubleBuffered and not (Parent is TCustomForm) do
Parent := Parent.Parent;
Result := (Parent = nil) or not Parent.DoubleBuffered or (Parent is TCustomForm);
end;
end;
{ TGlassableSpeedButton }
procedure TGlassableSpeedButton.NewWndProc(var Msg: TMessage);
begin
if (Msg.Msg = WM_PAINT) and (csGlassPaint in ControlState) and
not ControlInGlassPaint(Self) then
begin
ControlState := ControlState - [csGlassPaint];
try
inherited WndProc(Msg);
finally
ControlState := ControlState + [csGlassPaint];
end;
end
else
inherited WndProc(Msg);
end;
procedure InitSpeedButtonGlassFix;
begin
DebugLog('SpeedButtonGlassFix');
ReplaceVmtField(TSpeedButton, @TGlassableSpeedButton.WndProc, @TGlassableSpeedButton.NewWndProc);
{$IFDEF VCLFIXPACK_DB_SUPPORT}
ReplaceVmtField(TNavButton, @TGlassableSpeedButton.WndProc, @TGlassableSpeedButton.NewWndProc);
{$ENDIF VCLFIXPACK_DB_SUPPORT}
end;
procedure FiniSpeedButtonGlassFix;
begin
ReplaceVmtField(TSpeedButton, @TGlassableSpeedButton.NewWndProc, @TGlassableSpeedButton.WndProc);
{$IFDEF VCLFIXPACK_DB_SUPPORT}
ReplaceVmtField(TNavButton, @TGlassableSpeedButton.NewWndProc, @TGlassableSpeedButton.WndProc);
{$ENDIF VCLFIXPACK_DB_SUPPORT}
end;
{$ENDIF SpeedButtonGlassFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #69294: TProgressBar fails with PBS_MARQUEE and disabled Themes }
{$IFDEF VistaProgressBarMarqueeFix}
type
TVistaProgressBarMarqueeFix = class(TProgressBar)
protected
procedure SetMarqueeInterval(Value: Integer);
procedure CreateParamsFix(var Params: TCreateParams);
procedure CreateWndFix;
end;
var
SetMarqueeIntervalHook: TXRedirCode;
SetMarqueeInterval: Pointer;
procedure TVistaProgressBarMarqueeFix.SetMarqueeInterval(Value: Integer);
var
MarqueeEnabled: Boolean;
begin
PInteger(@MarqueeInterval)^ := Value;
if (Style = pbstMarquee) and HandleAllocated then
begin
MarqueeEnabled := Style = pbstMarquee;
SendMessage(Handle, PBM_SETMARQUEE, WPARAM(MarqueeEnabled), LPARAM(MarqueeInterval));
end;
end;
procedure TVistaProgressBarMarqueeFix.CreateParamsFix(var Params: TCreateParams);
begin
inherited CreateParams(Params);
if Style = pbstMarquee then
Params.Style := Params.Style or PBS_MARQUEE;
end;
procedure TVistaProgressBarMarqueeFix.CreateWndFix;
var
MarqueeEnabled: Boolean;
begin
inherited CreateWnd;
MarqueeEnabled := Style = pbstMarquee;
SendMessage(Handle, PBM_SETMARQUEE, WPARAM(THandle(MarqueeEnabled)), LPARAM(MarqueeInterval));
end;
procedure InitVistaProgressBarMarqueeFix;
var
PropInfo: PPropInfo;
begin
if CheckWin32Version(6, 0) then
begin
ReplaceVmtField(TProgressBar, @TVistaProgressBarMarqueeFix.CreateParams, @TVistaProgressBarMarqueeFix.CreateParamsFix);
ReplaceVmtField(TProgressBar, @TVistaProgressBarMarqueeFix.CreateWnd, @TVistaProgressBarMarqueeFix.CreateWndFix);
PropInfo := GetPropInfo(TProgressBar, 'MarqueeInterval');
if (PropInfo <> nil) and (PropInfo.SetProc <> nil) and
not (Byte(DWORD_PTR(PropInfo.SetProc) shr 24) in [$FF, $FE]) then
begin
SetMarqueeInterval := PropInfo.SetProc;
HookProc(SetMarqueeInterval, @TVistaProgressBarMarqueeFix.SetMarqueeInterval, SetMarqueeIntervalHook);
end;
DebugLog('VistaProgressBarMarqueeFix');
end;
end;
procedure FiniVistaProgressBarMarqueeFix;
begin
if CheckWin32Version(6, 0) then
begin
ReplaceVmtField(TProgressBar, @TVistaProgressBarMarqueeFix.CreateParamsFix, @TVistaProgressBarMarqueeFix.CreateParams);
ReplaceVmtField(TProgressBar, @TVistaProgressBarMarqueeFix.CreateWndFix, @TVistaProgressBarMarqueeFix.CreateWnd);
UnhookProc(SetMarqueeInterval, SetMarqueeIntervalHook);
end;
end;
{$ENDIF VistaProgressBarMarqueeFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #52439: DbNavigator paints incorrectly when flat=true in themed mode }
{$IFDEF DBNavigatorFix}
type
TOpenDBNavigator = class(TDBNavigator)
public
procedure SetFlatFixed(Value: Boolean);
end;
var
DBNavigatorSetFlat: Pointer;
DBNavigatorSetFlatHook: TXRedirCode;
procedure TOpenDBNavigator.SetFlatFixed(Value: Boolean);
var
I: TNavigateBtn;
begin
if Flat <> Value then
begin
Boolean(Pointer(@Flat)^) := Value; // FFlat := Value
for I := Low(Buttons) to High(Buttons) do
Buttons[I].Flat := Value;
if Flat then
ControlStyle := ControlStyle - [csOpaque]
else
ControlStyle := ControlStyle + [csOpaque];
end;
end;
procedure InitDBNavigatorFix;
var
Info: PPropInfo;
begin
Info := GetPropInfo(TDBNavigator, 'Flat');
if (Info <> nil) and (Info.SetProc <> nil) then
begin
DBNavigatorSetFlat := Info.SetProc;
HookProc(DBNavigatorSetFlat, @TOpenDBNavigator.SetFlatFixed, DBNavigatorSetFlatHook);
DebugLog('DBNavigatorFix');
end;
end;
procedure FiniDBNavigatorFix;
begin
UnhookProc(DBNavigatorSetFlat, DBNavigatorSetFlatHook);
end;
{$ENDIF DBNavigatorFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #70441: ToUpper and ToLower modify a Const argument
QC #69752: ToUpper and ToLower with NullString }
{$IFDEF CharacterFix}
var
TCharacter_ToLowerHook, TCharacter_ToUpperHook: TXRedirCode;
function TCharacter_ToLower(const S: string): string;
var
Len: Integer;
begin
if S <> '' then
begin
Len := Length(S);
SetLength(Result, Len);
if LCMapString(GetThreadLocale, LCMAP_LOWERCASE, PChar(S), Len, PChar(Result), Len) = 0 then
RaiseLastOSError;
end
else
Result := S;
end;
function TCharacter_ToUpper(const S: string): string;
var
Len: Integer;
begin
if S <> '' then
begin
Len := Length(S);
SetLength(Result, Len);
if LCMapString(GetThreadLocale, LCMAP_UPPERCASE, PChar(S), Len, PChar(Result), Len) = 0 then
RaiseLastOSError;
end
else
Result := S;
end;
procedure InitCharacterFix;
begin
DebugLog('CharacterFix');
HookProc(@TCharacter.ToLower, @TCharacter_ToLower, TCharacter_ToLowerHook);
HookProc(@TCharacter.ToUpper, @TCharacter_ToUpper, TCharacter_ToUpperHook);
end;
procedure FiniCharacterFix;
begin
UnhookProc(@TCharacter.ToLower, TCharacter_ToLowerHook);
UnhookProc(@TCharacter.ToUpper, TCharacter_ToUpperHook);
end;
{$ENDIF CharacterFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ QC #69875: StringBuilder.Replace is incorrect
+ a much faster implementation }
{$IFDEF StringBuilderFix}
var
TStringBuilder_ReplaceHook: TXRedirCode;
type
TStringBuilderFix = class(TStringBuilder)
public
function Replace(const OldValue, NewValue: string; StartIndex, Count: Integer): TStringBuilder;
end;
function TStringBuilderFix.Replace(const OldValue, NewValue: string;
StartIndex, Count: Integer): TStringBuilder;
procedure OffsetChars(Data: PChar; Offset: Integer; EndP: PChar); inline;
begin
Move(Data^, PChar(Data + Offset)^, (EndP - Data) * SizeOf(Char));
end;
var
P, EndP, F: PChar;
FirstChar: Char;
OldValueLen, NewValueLen, DataLen: Integer;
FoundCount, I: Integer;
StackStart: Pointer;
StackP: ^PChar;
SizeChange: Integer;
NewLength: Integer;
begin
{ Bounds checking }
DataLen := System.Length(FData);
if StartIndex + Count >= DataLen then
Count := DataLen - StartIndex;
if (Count <= 0) or (StartIndex < 0) or (StartIndex >= DataLen) or (OldValue = '') then
Exit(Self);
OldValueLen := System.Length(OldValue);
NewValueLen := System.Length(NewValue);
SizeChange := NewValueLen - OldValueLen;
{ Start stack position-buffer }
asm mov StackStart, esp end;
FoundCount := 0;
FirstChar := PChar(Pointer(OldValue))^;
P := PChar(@FData[StartIndex]);
while Count > 0 do
begin
while (Count > 0) and (P^ <> FirstChar) do
begin
Inc(P);
Dec(Count);
end;
if Count > 0 then
begin
if (OldValueLen = 1) or (StrLComp(P + 1, PChar(Pointer(OldValue)) + 1, OldValueLen - 1) = 0) then
begin
if SizeChange = 0 then
begin
{ Replace inplace }
Move(NewValue[1], P^, OldValueLen * SizeOf(Char));
end
else
begin
{ Save position to the stack and proceed }
asm push P end;
Inc(FoundCount);
end;
Inc(P, OldValueLen - 1);
Dec(Count, OldValueLen - 1);
end;
end;
Inc(P);
Dec(Count);
end;
NewLength := FLength + SizeChange * FoundCount;
if FoundCount > 0 then { Expand }
begin
{ Offset the data from right to left }
if SizeChange > 0 then
begin
{ Resize FData to the new length }
F := @FData[0];
if NewLength > System.Length(FData) then
SetLength(FData, NewLength);
EndP := PChar(@FData[FLength - 1]) + 1;
while FoundCount > 0 do
begin
asm pop P end; { take the last position from the stack }
P := PChar(@FData[0]) + (P - F);
{ Offset all chars right to the OldValue by FoundCount*SizeChange }
OffsetChars(P + OldValueLen, FoundCount * SizeChange, EndP);
EndP := P;
{ Put the NewValue into the buffer }
Move(NewValue[1], PChar(P + (FoundCount - 1) * SizeChange)^, NewValueLen * SizeOf(Char));
Dec(FoundCount);
end;
end
else { SizeChange < 0, Shrink }
begin
{ Offset the data from left to right }
{ Push the terminator to the stack; the loop uses the "next position" as EndP }
EndP := PChar(@FData[FLength - 1]) + 1;
asm push EndP end;
StackP := Pointer(INT_PTR(StackStart) - SizeOf(Pointer));
I := 0;
while FoundCount > 0 do
begin
P := StackP^;
Dec(StackP);
EndP := StackP^;
{ Offset all chars right to the OldValue by FoundCount*SizeChange }
OffsetChars(@P[OldValueLen], (I + 1) * SizeChange, EndP);
{ Put the NewValue into the buffer }
if NewValue <> '' then
Move(NewValue[1], P[I * SizeChange], NewValueLen * SizeOf(Char));
Inc(I);
Dec(FoundCount);
end;
{if NewLength > System.Length(FData) then
SetLength(FData, NewLength);}
end;
end;
{ Release stack memory }
asm mov esp, StackStart end;
FLength := NewLength;
Result := Self;
end;
procedure InitStringBuilderFix;
var
Proc: function(const OldValue: string; const NewValue: string; StartIndex: Integer; Count: Integer): TStringBuilder of object;
begin
DebugLog('StringBuilderFix');
Proc := TStringBuilder(nil).Replace;
HookProc(TMethod(Proc).Code, @TStringBuilderFix.Replace, TStringBuilder_ReplaceHook);
end;
procedure FiniStringBuilderFix;
var
Proc: function(const OldValue: string; const NewValue: string; StartIndex: Integer; Count: Integer): TStringBuilder of object;
begin
Proc := TStringBuilder(nil).Replace;
UnhookProc(TMethod(Proc).Code, TStringBuilder_ReplaceHook);
end;
{$ENDIF StringBuilderFix}
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ Workaround for Windows Vista CompareString bug }
{$IFDEF VistaCompareStringFix}
{**************************************************************************************************}
{ }
{ CompareString Fix }
{ }
{ The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }
{ you may not use this file except in compliance with the License. You may obtain a copy of the }
{ License at http://www.mozilla.org/MPL/ }
{ }
{ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF }
{ ANY KIND, either express or implied. See the License for the specific language governing rights }
{ and limitations under the License. }
{ }
{ The Original Code is CompareStringFix.pas. }
{ }
{ The Initial Developer of the Original Code is Andreas Hausladen. }
{ Portions created by Andreas Hausladen are Copyright (C) 2008 Andreas Hausladen. }
{ All Rights Reserved. }
{ }
{ Contributor(s): }
{ Apr 18, 2008 A. Garrels - When parameter Locale or user default LCID matches (German-Germany) }
{ and sort order equals SORT_GERMAN_PHONE_BOOK the patch must not be applied. }
{ }
{**************************************************************************************************}
{ This unit contains a workaround for a Windows Vista bug. }
{ The <20>/<2F> ($DC/$FC) and the UE/ue are treated equal in all locales, but they aren't equal. There }
{ was a bugfix intended for Vista SP1 but it was removed before SP1 was released. }
{ Windows 2008 Server still includes this bugfix but Vista will never get this bugfix. }
{ Microsoft: new versions are for correctness; service packs are for consistency and compatibility }
{**************************************************************************************************}
function _CompareStringA(Locale: LCID; dwCmpFlags: DWORD; lpString1: PAnsiChar;
cchCount1: Integer; lpString2: PAnsiChar; cchCount2: Integer): Integer; stdcall;
external kernel32 name 'CompareStringA';
function _CompareStringW(Locale: LCID; dwCmpFlags: DWORD; lpString1: PWideChar;
cchCount1: Integer; lpString2: PWideChar; cchCount2: Integer): Integer; stdcall;
external kernel32 name 'CompareStringW';
var
CompareStringAProc: function(Locale: LCID; dwCmpFlags: DWORD; lpString1: PAnsiChar;
cchCount1: Integer; lpString2: PAnsiChar; cchCount2: Integer): Integer; stdcall;
CompareStringWProc: function(Locale: LCID; dwCmpFlags: DWORD; lpString1: PWideChar;
cchCount1: Integer; lpString2: PWideChar; cchCount2: Integer): Integer; stdcall;
CompareStringFixRequired: Boolean;
const
MaxCompareStringFixBuffer = 2047;
var
CachedUserDefaultLCID: LCID = 0;
CachedSystemDefaultLCID: LCID = 0;
function IsGermanPhonebookSortOrder(ALcid: LCID): Boolean; {$IF CompilerVersion >= 18.0} inline; {$IFEND}
begin
if ALcid <> 0 then
begin
if ALcid = LOCALE_USER_DEFAULT then
begin
if CachedUserDefaultLCID = 0 then
CachedUserDefaultLCID := GetUserDefaultLCID();
ALcid := CachedUserDefaultLCID;
end
else if ALcid = LOCALE_SYSTEM_DEFAULT then
begin
if CachedSystemDefaultLCID = 0 then
CachedSystemDefaultLCID := GetSystemDefaultLCID();
ALcid := CachedSystemDefaultLCID;
end;
Result := (Word(ALcid) = $0407) and (ALcid shr 16 and $0F = SORT_GERMAN_PHONE_BOOK);
end
else
Result := False;
end;
function GetUmlautFixedString(P: PWideChar; var Count: Integer; Buf: PWideChar): PWideChar;
const
CombiningDiaresis = $308;
var
ValidCount, EndIndex, EndCount, I, CollationCount, Size, Cnt: Integer;
Ch: WideChar;
Source, Dest: PWideChar;
begin
ValidCount := -1;
CollationCount := 0;
EndIndex := -1;
Source := P;
{ Test for the affected code points }
if Count = -1 then
begin
Ch := Source^;
while Ch <> #0 do
begin
while Ch <> #0 do
begin
case Ch of
#$00DC, #$00FC:
Break;
end;
Inc(Source);
Ch := Source^;
end;
if Ch <> #0 then
begin
I := Source - P;
if ValidCount = -1 then
ValidCount := I;
EndIndex := I + 1;
Inc(CollationCount);
Inc(Source);
Ch := Source^;
end;
end;
Count := Source - P;
end
else
begin
Cnt := Count;
while Cnt > 0 do
begin
while Cnt <> 0 do
begin
case Source^ of
#$00DC, #$00FC:
Break;
end;
Inc(Source);
Dec(Cnt);
end;
if Cnt <> 0 then
begin
I := Source - P;
if ValidCount = -1 then
ValidCount := I;
EndIndex := I + 1;
Inc(CollationCount);
Inc(Source);
Dec(Cnt);
end;
end;
end;
if CollationCount > 0 then
begin
{ Re-encode the string with combining diaresis }
EndCount := Count - EndIndex;
if EndCount < 4 then
begin
{ Move() isn't faster if there are too less code points to copy }
EndIndex := Count;
EndCount := 0;
end;
{ Allocate enough memory or use the stack based buffer if the string fits
into it. }
Inc(Count, CollationCount);
Size := (Count + 1) * SizeOf(WideChar);
if Count < MaxCompareStringFixBuffer then
Result := Buf
else
GetMem(Result, Size);
{ Copy untouched code points }
if ValidCount >= 4 then
Move(P^, Result^, ValidCount * SizeOf(WideChar))
else
ValidCount := 0;
{ Copy code points and replace "<22>" ($DC) and "<22>" ($FC) with "U"/"u" + combining diaresis }
Source := P + ValidCount;
Dest := Result + ValidCount;
for I := ValidCount to EndIndex - 1 do
begin
Ch := Source^;
case Ch of
#$00DC:
begin
PLongint(Dest)^ := (CombiningDiaresis shl 16) or $55;
Inc(Dest);
end;
#$00FC:
begin
PLongint(Dest)^ := (CombiningDiaresis shl 16) or $75;
Inc(Dest);
end;
else
Dest^ := Ch;
end;
Inc(Dest);
Inc(Source);
end;
{ Copy remaining untouched code points }
if EndCount > 0 then
begin
Move(Source^, Dest^, EndCount * SizeOf(WideChar));
Inc(Dest, EndCount);
end;
Dest^ := #0;
end
else
Result := P;
end;
function CompareStringW(Locale: LCID; dwCmpFlags: DWORD; lpString1: PWideChar;
cchCount1: Integer; lpString2: PWideChar; cchCount2: Integer): Integer; stdcall;
var
String1, String2: PWideChar;
// Stack allocation is much faster than heap allocation
Buf1, Buf2: array[0..MaxCompareStringFixBuffer] of WideChar;
begin
if (lpString1 <> nil) and (lpString2 <> nil) and (cchCount1 <> 0) and (cchCount2 <> 0) and
(lpString1 <> lpString2) and
not IsGermanPhonebookSortOrder(Locale) then
begin
String1 := GetUmlautFixedString(lpString1, cchCount1, Buf1);
String2 := GetUmlautFixedString(lpString2, cchCount2, Buf2);
Result := CompareStringWProc(Locale, dwCmpFlags, String1, cchCount1, String2, cchCount2);
if (String1 <> lpString1) and (String1 <> @Buf1[0]) then
FreeMem(String1);
if (String2 <> lpString2) and (String2 <> @Buf2[0]) then
FreeMem(String2);
end
else
Result := CompareStringWProc(Locale, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2);
end;
function CompareStringA(Locale: LCID; dwCmpFlags: DWORD; lpString1: PAnsiChar;
cchCount1: Integer; lpString2: PAnsiChar; cchCount2: Integer): Integer; stdcall;
function ContainsProblematicUmlaut(P: PAnsiChar; Count: Integer): Boolean;
begin
Result := True;
while Count > 0 do
begin
if P^ in [#$DC, #$FC] then
Exit;
Inc(P);
Dec(Count);
end;
Result := False;
end;
var
String1, String2: WideString;
begin
if (lpString1 <> nil) and (lpString2 <> nil) and (cchCount1 <> 0) and (cchCount2 <> 0) and
(lpString1 <> lpString2) and
not IsGermanPhonebookSortOrder(Locale) then
begin
case GetACP of
{1250,} 1252{, 1254, 1257, 1258}:
begin
if cchCount1 = -1 then
cchCount1 := StrLen(lpString1);
if cchCount2 = -1 then
cchCount2 := StrLen(lpString2);
if ContainsProblematicUmlaut(lpString1, cchCount1) or
ContainsProblematicUmlaut(lpString2, cchCount2) then
begin
SetString(String1, lpString1, cchCount1);
SetString(String2, lpString2, cchCount2);
Result := CompareStringW(Locale, dwCmpFlags, PWideChar(String1), Length(String1),
PWideChar(String2), Length(String2));
Exit;
end;
end;
end;
end;
Result := CompareStringAProc(Locale, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2);
end;
type
TJumpItem = packed record
Code: TXRedirCode;
Jump: Byte;
Offset: Integer;
end;
PJumpTable = ^TJumpTable;
TJumpTable = array[0..1] of TJumpItem;
var
JumpTable: PJumpTable;
procedure PatchWinAPI(Proc, Dest: Pointer; var JumpItem: TJumpItem);
var
n: DWORD;
Code: TXRedirCode;
begin
Proc := GetActualAddr(Proc);
Assert(Proc <> nil);
if ReadProcessMemory(GetCurrentProcess, Proc, @JumpItem.Code, SizeOf(JumpItem.Code), n) then
begin
JumpItem.Jump := $E9;
JumpItem.Offset := Integer(Proc) - Integer(@JumpItem) - SizeOf(JumpItem.Code);
Code.Jump := $E9;
Code.Offset := Integer(Dest) - Integer(Proc) - SizeOf(Code);
WriteProcessMemory(GetCurrentProcess, Proc, @Code, SizeOf(Code), n);
end;
end;
procedure UnpatchWinAPI(Proc: Pointer; const JumpItem: TJumpItem);
var
n: Cardinal;
begin
if JumpItem.Code.Jump <> 0 then
begin
Proc := GetActualAddr(Proc);
Assert(Proc <> nil);
WriteProcessMemory(GetCurrentProcess, Proc, @JumpItem.Code, SizeOf(JumpItem.Code), n);
end;
end;
procedure InitCompareStringFix;
const
CSTR_EQUAL = 2;
begin
{ Only Vista is affected, Windows 2008 Server is not affected }
if (Win32Platform = VER_PLATFORM_WIN32_NT) and
(Win32MajorVersion = 6) and (Win32MinorVersion = 0) then
begin
CompareStringFixRequired := _CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, #$FC, 1, 'ue', 2) = CSTR_EQUAL;
if CompareStringFixRequired then
begin
OutputDebugStringW('Installing CompareString workaround for Windows Vista');
JumpTable := VirtualAlloc(nil, SizeOf(TJumpTable), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PatchWinAPI(@_CompareStringA, @CompareStringA, JumpTable[0]);
CompareStringAProc := @JumpTable[0];
PatchWinAPI(@_CompareStringW, @CompareStringW, JumpTable[1]);
CompareStringWProc := @JumpTable[1];
end;
end;
end;
procedure FiniCompareStringFix;
begin
if CompareStringFixRequired then
begin
UnpatchWinAPI(@_CompareStringA, JumpTable[0]);
UnpatchWinAPI(@_CompareStringW, JumpTable[1]);
VirtualFree(JumpTable, 0, MEM_RELEASE);
JumpTable := nil;
end;
end;
{$ENDIF VistaCompareStringFix}
{ ---------------------------------------------------------------------------- }
initialization
{$IFDEF GetNonToolWindowPopupParentFix}
FixGetNonToolWindowPopupParent;
{$ENDIF GetNonToolWindowPopupParentFix}
{$IFDEF TaskModalDialogFix}
InitTaskModalDialogFix;
{$ENDIF TaskModalDialogFix}
{$IFDEF AppDeActivateZOrderFix}
InitAppDeActivateZOrderFix;
{$ENDIF AppDeActivateZOrderFix}
{$IFDEF HideStackTrashingFix}
FindTopMostWindowProc := GetAddrOfFindTopMostWindow;
if FindTopMostWindowProc <> nil then
begin
DebugLog('HideStackTrashingFix');
HookProc(FindTopMostWindowProc, @FindTopMostWindow, FindTopMostWindowHook);
end;
{$ENDIF HideStackTrashingFix}
{$IFDEF ControlResizeFix}
InitControlResizeFix;
{$ENDIF ControlResizeFix}
{$IFDEF ActionListAVFix}
InitActionListAVFix;
{$ENDIF ActionListAVFix}
{$IFDEF ContextMenuFix}
InitContextMenuFix;
{$ENDIF ContextMenuFix}
{$IFDEF ObjAutoDEPFix}
InitObjAutoDEPFix;
{$ENDIF ObjAutoDEPFix}
{$IFDEF AppMinimizeFix}
InitAppMinimizeFix;
{$ENDIF AppMinimizeFix}
{$IFDEF SysUtilsAbortFix}
InitSysUtilsAbortFix;
{$ENDIF SysUtilsAbortFix}
{$IFDEF CmdShowMinimizeFix}
InitCmdShowMinimizeFix;
{$ENDIF CmdShowMinimizeFix}
{$IFDEF MDIChildFocusFix}
InitMDIChildFocusFix;
{$ENDIF MDIChildFocusFix}
{$IFDEF PageControlPaintingFix}
InitPageControlPaintingFix;
{$ENDIF PageControlPaintingFix}
{$IFDEF GridFlickerFix}
InitGridFlickerFix;
{$ENDIF GridFlickerFix}
{$IFDEF SpeedButtonGlassFix}
InitSpeedButtonGlassFix;
{$ENDIF SpeedButtonGlassFix}
{$IFDEF VistaProgressBarMarqueeFix}
InitVistaProgressBarMarqueeFix;
{$ENDIF VistaProgressBarMarqueeFix}
{$IFDEF DBNavigatorFix}
InitDBNavigatorFix;
{$ENDIF DBNavigatorFix}
{$IFDEF CharacterFix}
InitCharacterFix;
{$ENDIF CharacterFix}
{$IFDEF StringBuilderFix}
InitStringBuilderFix;
{$ENDIF StringBuilderFix}
{$IFDEF VistaCompareStringFix}
InitCompareStringFix;
{$ENDIF VistaCompareStringFix}
finalization
// In revers order
{$IFDEF VistaCompareStringFix}
FiniCompareStringFix;
{$ENDIF VistaCompareStringFix}
{$IFDEF StringBuilderFix}
FiniStringBuilderFix;
{$ENDIF StringBuilderFix}
{$IFDEF CharacterFix}
FiniCharacterFix;
{$ENDIF CharacterFix}
{$IFDEF DBNavigatorFix}
FiniDBNavigatorFix;
{$ENDIF DBNavigatorFix}
{$IFDEF VistaProgressBarMarqueeFix}
FiniVistaProgressBarMarqueeFix;
{$ENDIF VistaProgressBarMarqueeFix}
{$IFDEF SpeedButtonGlassFix}
FiniSpeedButtonGlassFix;
{$ENDIF SpeedButtonGlassFix}
{$IFDEF GridFlickerFix}
FiniGridFlickerFix;
{$ENDIF GridFlickerFix}
{$IFDEF PageControlPaintingFix}
FiniPageControlPaintingFix;
{$ENDIF PageControlPaintingFix}
{$IFDEF MDIChildFocusFix}
FiniMDIChildFocusFix;
{$ENDIF MDIChildFocusFix}
{$IFDEF CmdShowMinimizeFix}
FiniCmdShowMinimizeFix;
{$ENDIF CmdShowMinimizeFix}
{$IFDEF SysUtilsAbortFix}
FiniSysUtilsAbortFix;
{$ENDIF SysUtilsAbortFix}
{$IFDEF AppMinimizeFix}
FiniAppMinimizeFix;
{$ENDIF AppMinimizeFix}
{$IFDEF ObjAutoDEPFix}
FiniObjAutoDEPFix;
{$ENDIF ObjAutoDEPFix}
{$IFDEF ContextMenuFix}
FiniContextMenuFix;
{$ENDIF ContextMenuFix}
{$IFDEF ActionListAVFix}
FiniActionListAVFix;
{$ENDIF ActionListAVFix}
{$IFDEF ControlResizeFix}
FiniControlResizeFix;
{$ENDIF ControlResizeFix}
{$IFDEF HideStackTrashingFix}
if FindTopMostWindowProc <> nil then
UnhookProc(FindTopMostWindowProc, FindTopMostWindowHook);
{$ENDIF HideStackTrashingFix}
{$IFDEF TaskModalDialogFix}
FiniTaskModalDialogFix;
{$ENDIF TaskModalDialogFix}
{$IFDEF AppDeActivateZOrderFix}
FiniAppDeActivateZOrderFix;
{$ENDIF AppDeActivateZOrderFix}
{$IFDEF MkObjInstLeakFix}
MkObjInstLeakHooked := False;
ReleaseObjectInstanceBlocks;
{$ENDIF MkObjInstLeakFix}
end.