{**************************************************************************************************} { } { 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 Ü/ü ($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 Ü/ü ($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 "Ü" ($DC) and "ü" ($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.