1077 lines
31 KiB
ObjectPascal
1077 lines
31 KiB
ObjectPascal
{-----------------------------------------------------------------------------
|
|
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/MPL-1.1.html
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
|
|
the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is: JvWndProcHook.PAS, released on 2002-11-01.
|
|
|
|
The Initial Developer of the Original Code is Peter Thörnqvist [peter3 at sourceforge dot net]
|
|
Portions created by Peter Thörnqvist are Copyright (C) 2002 Peter Thörnqvist.
|
|
All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
Remko Bonte <remkobonte att myrealbox dott com>
|
|
|
|
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
|
|
located at http://jvcl.sourceforge.net
|
|
|
|
Known Issues:
|
|
* (rb) object naming could be improved, for example
|
|
TJvWndProcHook -> TJvHookController
|
|
TJvWndProcHook.FHookInfos -> TJvHookController.Items
|
|
TJvHookInfos -> TJvHookItem, TJvHookInfo, TJvHook
|
|
TJvHookInfo -> TJvHookData
|
|
-----------------------------------------------------------------------------}
|
|
// $Id: JvWndProcHook.pas 10613 2006-05-19 19:21:43Z jfudickar $
|
|
|
|
unit JvWndProcHook;
|
|
|
|
{$I jvcl.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
{$IFDEF UNITVERSIONING}
|
|
JclUnitVersioning,
|
|
{$ENDIF UNITVERSIONING}
|
|
Windows, Messages, SysUtils, Controls, Forms, Classes,
|
|
JvComponentBase;
|
|
|
|
type
|
|
TJvControlHook = function(var Msg: TMessage): Boolean of object;
|
|
TJvHookMessageEvent = procedure(Sender: TObject; var Msg: TMessage;
|
|
var Handled: Boolean) of object;
|
|
|
|
TJvHookOrder = (hoBeforeMsg, hoAfterMsg);
|
|
|
|
TJvWindowHook = class(TJvComponent)
|
|
private
|
|
FActive: Boolean;
|
|
FControl: TControl;
|
|
FBeforeMessage: TJvHookMessageEvent;
|
|
FAfterMessage: TJvHookMessageEvent;
|
|
procedure SetActive(Value: Boolean);
|
|
procedure SetControl(Value: TControl);
|
|
function IsForm: Boolean;
|
|
function NotIsForm: Boolean;
|
|
procedure ReadForm(Reader: TReader);
|
|
procedure WriteForm(Writer: TWriter);
|
|
procedure SetAfterMessage(const Value: TJvHookMessageEvent);
|
|
procedure SetBeforeMessage(const Value: TJvHookMessageEvent);
|
|
protected
|
|
procedure DefineProperties(Filer: TFiler); override;
|
|
function DoAfterMessage(var Msg: TMessage): Boolean; dynamic;
|
|
function DoBeforeMessage(var Msg: TMessage): Boolean; dynamic;
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure HookControl;
|
|
procedure UnHookControl;
|
|
published
|
|
property Active: Boolean read FActive write SetActive default True;
|
|
property Control: TControl read FControl write SetControl stored NotIsForm;
|
|
property BeforeMessage: TJvHookMessageEvent read FBeforeMessage write SetBeforeMessage;
|
|
property AfterMessage: TJvHookMessageEvent read FAfterMessage write SetAfterMessage;
|
|
end;
|
|
|
|
function RegisterWndProcHook(AControl: TControl; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean; overload;
|
|
function RegisterWndProcHook(AHandle: THandle; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean; overload;
|
|
function UnRegisterWndProcHook(AControl: TControl; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean; overload;
|
|
function UnRegisterWndProcHook(AHandle: THandle; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean; overload;
|
|
procedure ReleaseObj(AObject: TObject);
|
|
|
|
{$IFDEF UNITVERSIONING}
|
|
const
|
|
UnitVersioning: TUnitVersionInfo = (
|
|
RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvWndProcHook.pas $';
|
|
Revision: '$Revision: 10613 $';
|
|
Date: '$Date: 2006-05-19 21:21:43 +0200 (ven., 19 mai 2006) $';
|
|
LogPath: 'JVCL\run'
|
|
);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
implementation
|
|
|
|
{$IFDEF CLR}
|
|
uses
|
|
Borland.Vcl.WinUtils;
|
|
{$ENDIF CLR}
|
|
|
|
type
|
|
{$IFDEF CLR}
|
|
TJvHookInfo = class(TObject)
|
|
Hook: TJvControlHook;
|
|
Next: TJvHookInfo;
|
|
end;
|
|
PJvHookInfo = TJvHookInfo;
|
|
|
|
THookInfoList = array of TJvHookInfo;
|
|
PHookInfoList = THookInfoList;
|
|
|
|
{$ELSE}
|
|
|
|
PJvHookInfo = ^TJvHookInfo;
|
|
TJvHookInfo = record
|
|
Hook: TJvControlHook;
|
|
Next: PJvHookInfo;
|
|
end;
|
|
|
|
PHookInfoList = ^THookInfoList;
|
|
THookInfoList = array [0..MaxInt div 4 - 1] of PJvHookInfo;
|
|
{$ENDIF CLR}
|
|
|
|
TJvWndProcHook = class;
|
|
|
|
TJvHookInfos = class(TObject)
|
|
private
|
|
FFirst: array [TJvHookOrder] of PJvHookInfo;
|
|
FLast: array [TJvHookOrder] of PJvHookInfo;
|
|
{ FStack is filled with HookInfos that are being processed in WindowProc
|
|
procedures. On entrance of the WindowProc the size increases, on exit it
|
|
decreases. Thus when a message is send inside a hook handler, the stack
|
|
size increases.
|
|
|
|
We use a stack to be able to register and unregister hooks inside hook
|
|
handlers, see \dev\DUnit for some examples.
|
|
|
|
The odd members in the stack are hoBeforeMsg hooks, the even members in
|
|
the list are hoAfterMsg hooks
|
|
}
|
|
FStack: PHookInfoList;
|
|
FStackCapacity: Integer;
|
|
FStackCount: Integer;
|
|
FHandle: THandle;
|
|
FControl: TControl;
|
|
FControlDestroyed: Boolean;
|
|
FOldWndProc: TWndMethod;
|
|
FOldWndProcHandle: TFarProc;
|
|
{$IFDEF CLR}
|
|
FOldWndProcHandleInst: TFNWndProc;
|
|
{$ENDIF CLR}
|
|
FHooked: Boolean;
|
|
FController: TJvWndProcHook;
|
|
procedure SetController(const Value: TJvWndProcHook);
|
|
protected
|
|
procedure WindowProc(var Msg: TMessage);
|
|
procedure HookControl;
|
|
procedure UnHookControl;
|
|
procedure IncDepth;
|
|
procedure DecDepth;
|
|
public
|
|
constructor Create(AControl: TControl); overload;
|
|
constructor Create(AHandle: THandle); overload;
|
|
destructor Destroy; override;
|
|
procedure Add(const Order: TJvHookOrder; Hook: TJvControlHook);
|
|
procedure Delete(const Order: TJvHookOrder; Hook: TJvControlHook);
|
|
procedure ControlDestroyed;
|
|
property Control: TControl read FControl;
|
|
{ Prevent calls to WndProcHook by using property Controller;
|
|
TJvHookInfos may live longer than WndProcHook }
|
|
property Controller: TJvWndProcHook read FController write SetController;
|
|
property Handle: THandle read FHandle;
|
|
end;
|
|
|
|
TJvWndProcHook = class(TComponent)
|
|
private
|
|
FHookInfos: TList;
|
|
protected
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
function IndexOf(AControl: TControl): Integer; overload;
|
|
function IndexOf(AHandle: THandle): Integer; overload;
|
|
function Find(AControl: TControl): TJvHookInfos; overload;
|
|
function Find(AHandle: THandle): TJvHookInfos; overload;
|
|
|
|
procedure Remove(AHookInfos: TJvHookInfos);
|
|
procedure Add(AHookInfos: TJvHookInfos);
|
|
public
|
|
destructor Destroy; override;
|
|
function RegisterWndProc(AControl: TControl; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean; overload;
|
|
function RegisterWndProc(AHandle: THandle; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean; overload;
|
|
function UnRegisterWndProc(AControl: TControl; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean; overload;
|
|
function UnRegisterWndProc(AHandle: THandle; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean; overload;
|
|
end;
|
|
|
|
TJvReleaser = class({$IFDEF CLR} TControl {$ELSE} TObject {$ENDIF CLR})
|
|
private
|
|
FHandle: THandle;
|
|
FReleasing: TList;
|
|
function GetHandle: THandle;
|
|
procedure CMRelease(var Msg: TMessage); message CM_RELEASE;
|
|
procedure WndProc(var Msg: TMessage); {$IFDEF CLR}reintroduce;{$ENDIF}
|
|
public
|
|
constructor Create; {$IFDEF CLR}reintroduce;{$ENDIF} virtual;
|
|
destructor Destroy; override;
|
|
procedure DefaultHandler(var Msg); override;
|
|
class function Instance: TJvReleaser;
|
|
procedure Release(AObject: TObject);
|
|
property Handle: THandle read GetHandle;
|
|
end;
|
|
|
|
var
|
|
GJvWndProcHook: TJvWndProcHook = nil;
|
|
GReleaser: TJvReleaser = nil;
|
|
|
|
function WndProcHook: TJvWndProcHook;
|
|
begin
|
|
if GJvWndProcHook = nil then
|
|
GJvWndProcHook := TJvWndProcHook.Create(nil);
|
|
Result := GJvWndProcHook;
|
|
end;
|
|
|
|
function RegisterWndProcHook(AControl: TControl; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean;
|
|
begin
|
|
Result := WndProcHook.RegisterWndProc(AControl, Hook, Order);
|
|
end;
|
|
|
|
function RegisterWndProcHook(AHandle: THandle; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean;
|
|
begin
|
|
Result := WndProcHook.RegisterWndProc(AHandle, Hook, Order);
|
|
end;
|
|
|
|
function UnRegisterWndProcHook(AControl: TControl; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean;
|
|
begin
|
|
Result := WndProcHook.UnRegisterWndProc(AControl, Hook, Order);
|
|
end;
|
|
|
|
function UnRegisterWndProcHook(AHandle: THandle; Hook: TJvControlHook;
|
|
const Order: TJvHookOrder): Boolean;
|
|
begin
|
|
Result := WndProcHook.UnRegisterWndProc(AHandle, Hook, Order);
|
|
end;
|
|
|
|
procedure ReleaseObj(AObject: TObject);
|
|
begin
|
|
TJvReleaser.Instance.Release(AObject);
|
|
end;
|
|
|
|
//=== { TJvWndProcHook } =====================================================
|
|
|
|
procedure TJvWndProcHook.Add(AHookInfos: TJvHookInfos);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := FHookInfos.IndexOf(AHookInfos);
|
|
if I < 0 then
|
|
FHookInfos.Add(AHookInfos);
|
|
end;
|
|
|
|
destructor TJvWndProcHook.Destroy;
|
|
begin
|
|
if FHookInfos <> nil then
|
|
begin
|
|
while FHookInfos.Count > 0 do
|
|
{ If you free a hook info, it will remove itself from the list }
|
|
TJvHookInfos(FHookInfos[0]).Free;
|
|
|
|
FHookInfos.Free;
|
|
end;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TJvWndProcHook.Find(AHandle: THandle): TJvHookInfos;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := IndexOf(AHandle);
|
|
if I < 0 then
|
|
Result := nil
|
|
else
|
|
Result := TJvHookInfos(FHookInfos[I]);
|
|
end;
|
|
|
|
function TJvWndProcHook.Find(AControl: TControl): TJvHookInfos;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := IndexOf(AControl);
|
|
if I < 0 then
|
|
Result := nil
|
|
else
|
|
Result := TJvHookInfos(FHookInfos[I]);
|
|
end;
|
|
|
|
function TJvWndProcHook.IndexOf(AHandle: THandle): Integer;
|
|
begin
|
|
{ The following code introduces a problem:
|
|
|
|
The handle of a control may change (by a call to RecreateWnd for example)
|
|
thus you may find a Ctrl by calling FindControl(AHandle) in RegisterWndProcHook
|
|
and then it's possible to _not_ find the same control in UnRegisterWndProcHook,
|
|
thus hooks may be left open unwanted.
|
|
|
|
Maybe there is a better way to identify hooks than (Handle x Hook x Order) or
|
|
( Ctrl x Hook x Order ) (?)
|
|
}
|
|
|
|
{Ctrl := FindControl(AHandle);
|
|
if Ctrl <> nil then
|
|
begin
|
|
Result := IndexOf(Ctrl);
|
|
if Result >= 0 then
|
|
Exit;
|
|
end;}
|
|
|
|
Result := 0;
|
|
while (Result < FHookInfos.Count) and
|
|
(TJvHookInfos(FHookInfos[Result]).Handle <> AHandle) do
|
|
Inc(Result);
|
|
if Result = FHookInfos.Count then
|
|
Result := -1;
|
|
end;
|
|
|
|
function TJvWndProcHook.IndexOf(AControl: TControl): Integer;
|
|
begin
|
|
Result := 0;
|
|
while (Result < FHookInfos.Count) and
|
|
(TJvHookInfos(FHookInfos[Result]).Control <> AControl) do
|
|
Inc(Result);
|
|
if Result = FHookInfos.Count then
|
|
Result := -1;
|
|
end;
|
|
|
|
procedure TJvWndProcHook.Notification(AComponent: TComponent;
|
|
Operation: TOperation);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
inherited Notification(AComponent, Operation);
|
|
if (Operation = opRemove) and (FHookInfos <> nil) and (AComponent is TControl) then
|
|
begin
|
|
I := IndexOf(TControl(AComponent));
|
|
if I >= 0 then
|
|
{ Be careful because the TJvHookInfos object might be in it's
|
|
WindowProc procedure, for example when hooking a form and handling
|
|
a CM_RELEASE message. The TJvHookInfos object can't be destroyed then.
|
|
|
|
General rule must be that only TJvHookInfos can destroy itself, and
|
|
remove it from the TJvWndProcHook.FHookInfos list.
|
|
}
|
|
TJvHookInfos(FHookInfos[I]).ControlDestroyed;
|
|
end;
|
|
end;
|
|
|
|
function TJvWndProcHook.RegisterWndProc(AControl: TControl;
|
|
Hook: TJvControlHook; const Order: TJvHookOrder): Boolean;
|
|
var
|
|
HookInfos: TJvHookInfos;
|
|
begin
|
|
Result := False;
|
|
if not Assigned(AControl) or
|
|
(csDestroying in AControl.ComponentState) or not Assigned(Hook) then
|
|
Exit;
|
|
|
|
if FHookInfos = nil then
|
|
FHookInfos := TList.Create;
|
|
|
|
// find the control:
|
|
HookInfos := Find(AControl);
|
|
if not Assigned(HookInfos) then
|
|
begin
|
|
HookInfos := TJvHookInfos.Create(AControl);
|
|
HookInfos.Controller := Self;
|
|
AControl.FreeNotification(Self);
|
|
end;
|
|
HookInfos.Add(Order, Hook);
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
function TJvWndProcHook.RegisterWndProc(AHandle: THandle;
|
|
Hook: TJvControlHook; const Order: TJvHookOrder): Boolean;
|
|
var
|
|
HookInfos: TJvHookInfos;
|
|
begin
|
|
Result := False;
|
|
if not Assigned(Hook) then
|
|
Exit;
|
|
if FHookInfos = nil then
|
|
FHookInfos := TList.Create;
|
|
|
|
// find the control:
|
|
HookInfos := Find(AHandle);
|
|
if not Assigned(HookInfos) then
|
|
begin
|
|
HookInfos := TJvHookInfos.Create(AHandle);
|
|
HookInfos.Controller := Self;
|
|
end;
|
|
HookInfos.Add(Order, Hook);
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TJvWndProcHook.Remove(AHookInfos: TJvHookInfos);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := FHookInfos.IndexOf(AHookInfos);
|
|
if I >= 0 then
|
|
FHookInfos.Delete(I);
|
|
end;
|
|
|
|
function TJvWndProcHook.UnRegisterWndProc(AHandle: THandle;
|
|
Hook: TJvControlHook; const Order: TJvHookOrder): Boolean;
|
|
var
|
|
HookInfos: TJvHookInfos;
|
|
begin
|
|
Result := False;
|
|
if not Assigned(Hook) or not Assigned(FHookInfos) then
|
|
Exit;
|
|
// find the control:
|
|
HookInfos := Find(AHandle);
|
|
Result := Assigned(HookInfos);
|
|
if Result then
|
|
// Maybe delete HookInfos if HookInfos.FFirst.. = nil?
|
|
HookInfos.Delete(Order, Hook);
|
|
end;
|
|
|
|
function TJvWndProcHook.UnRegisterWndProc(AControl: TControl;
|
|
Hook: TJvControlHook; const Order: TJvHookOrder): Boolean;
|
|
var
|
|
HookInfos: TJvHookInfos;
|
|
begin
|
|
Result := False;
|
|
if not Assigned(AControl) or not Assigned(Hook) or not Assigned(FHookInfos) then
|
|
Exit;
|
|
// find the control:
|
|
HookInfos := Find(AControl);
|
|
Result := Assigned(HookInfos);
|
|
if Result then
|
|
// Maybe delete HookInfos if HookInfos.FFirst.. = nil?
|
|
HookInfos.Delete(Order, Hook);
|
|
end;
|
|
|
|
//=== { TJvHookInfos } =======================================================
|
|
|
|
procedure TJvHookInfos.Add(const Order: TJvHookOrder; Hook: TJvControlHook);
|
|
var
|
|
HookInfo: PJvHookInfo;
|
|
I: Integer;
|
|
begin
|
|
{$IFDEF CLR}
|
|
HookInfo := TJvHookInfo.Create;
|
|
{$ELSE}
|
|
New(HookInfo);
|
|
{$ENDIF CLR}
|
|
HookInfo.Hook := Hook;
|
|
HookInfo.Next := nil;
|
|
|
|
{ Some bookkeeping }
|
|
if FFirst[Order] = nil then
|
|
FFirst[Order] := HookInfo;
|
|
|
|
if FLast[Order] <> nil then
|
|
FLast[Order].Next := HookInfo;
|
|
|
|
FLast[Order] := HookInfo;
|
|
|
|
{ Update the stack }
|
|
if Order = hoBeforeMsg then
|
|
I := 0
|
|
else
|
|
I := 1;
|
|
while I < FStackCount * 2 do
|
|
begin
|
|
if FStack[I] = nil then
|
|
FStack[I] := HookInfo;
|
|
Inc(I, 2);
|
|
end;
|
|
|
|
HookControl;
|
|
end;
|
|
|
|
procedure TJvHookInfos.ControlDestroyed;
|
|
begin
|
|
if FControlDestroyed then
|
|
Exit;
|
|
|
|
{ This procedure is called when we get notified that the control we are hooking
|
|
is destroyed. We can get this notification from TJvWindowHook.Notification
|
|
or in TJvHookInfos.WindowProc.
|
|
|
|
Problem is that the control might be destroyed when we are in the
|
|
TJvHookInfos.WindowProc. This can occur for example with the CM_RELEASE
|
|
message for a TCustomForm. In this case we have to be extra careful to not
|
|
call destroyed components via HookInfo.Hook(Msg) etc. Also in that case
|
|
we can't free the TJvHookInfos yet, thus we use ReleaseObj.
|
|
}
|
|
|
|
FControlDestroyed := True;
|
|
FOldWndProc := nil;
|
|
FOldWndProcHandle := nil;
|
|
|
|
{ Remove this TJvHookInfos object from the HookInfo list of Controller }
|
|
Controller := nil;
|
|
ReleaseObj(Self);
|
|
end;
|
|
|
|
constructor TJvHookInfos.Create(AControl: TControl);
|
|
begin
|
|
inherited Create;
|
|
FControl := AControl;
|
|
{$IFNDEF CLR}
|
|
FillChar(FFirst, SizeOf(FFirst), 0);
|
|
FillChar(FLast, SizeOf(FLast), 0);
|
|
//FillChar(FStack, SizeOf(FStack), 0);
|
|
//FillChar(FStackCapacity, SizeOf(FStackCapacity), 0);
|
|
//FillChar(FStackCount, SizeOf(FStackCount), 0);
|
|
{$ENDIF !CLR}
|
|
end;
|
|
|
|
constructor TJvHookInfos.Create(AHandle: THandle);
|
|
begin
|
|
inherited Create;
|
|
FHandle := AHandle;
|
|
{$IFNDEF CLR}
|
|
FillChar(FFirst, SizeOf(FFirst), 0);
|
|
FillChar(FLast, SizeOf(FLast), 0);
|
|
//FillChar(FStack, SizeOf(FStack), 0);
|
|
//FillChar(FStackCapacity, SizeOf(FStackCapacity), 0);
|
|
//FillChar(FStackCount, SizeOf(FStackCount), 0);
|
|
{$ENDIF !CLR}
|
|
end;
|
|
|
|
procedure TJvHookInfos.DecDepth;
|
|
begin
|
|
if FStackCount > 0 then
|
|
Dec(FStackCount);
|
|
end;
|
|
|
|
procedure TJvHookInfos.Delete(const Order: TJvHookOrder; Hook: TJvControlHook);
|
|
var
|
|
HookInfo: PJvHookInfo;
|
|
PrevHookInfo: PJvHookInfo;
|
|
I: Integer;
|
|
begin
|
|
HookInfo := FFirst[Order];
|
|
PrevHookInfo := nil;
|
|
while (HookInfo <> nil) and
|
|
{$IFDEF CLR}
|
|
(@HookInfo.Hook <> @Hook) do
|
|
{$ELSE}
|
|
((TMethod(HookInfo.Hook).Code <> TMethod(Hook).Code) or
|
|
(TMethod(HookInfo.Hook).Data <> TMethod(Hook).Data)) do
|
|
{$ENDIF CLR}
|
|
{ This is unique: Code = the object whereto the method belongs
|
|
Data = identifies the method in the object }
|
|
begin
|
|
PrevHookInfo := HookInfo;
|
|
HookInfo := HookInfo.Next;
|
|
end;
|
|
|
|
if not Assigned(HookInfo) then
|
|
Exit;
|
|
|
|
// patch up the hole (this is the reason for this entire unit!)
|
|
if PrevHookInfo <> nil then
|
|
PrevHookInfo.Next := HookInfo.Next;
|
|
|
|
{ Bookkeeping }
|
|
if FLast[Order] = HookInfo then
|
|
FLast[Order] := PrevHookInfo;
|
|
if FFirst[Order] = HookInfo then
|
|
FFirst[Order] := HookInfo.Next;
|
|
|
|
{ Update the stack }
|
|
if Order = hoBeforeMsg then
|
|
I := 0
|
|
else
|
|
I := 1;
|
|
while I < FStackCount * 2 do
|
|
begin
|
|
if FStack[I] = HookInfo then
|
|
FStack[I] := HookInfo.Next;
|
|
Inc(I, 2);
|
|
end;
|
|
|
|
{$IFNDEF CLR} // in .NET TJvHookInfo does not implement IDisposible
|
|
Dispose(HookInfo);
|
|
{$ENDIF !CLR}
|
|
|
|
if (FFirst[hoBeforeMsg] = nil) and (FFirst[hoAfterMsg] = nil) then
|
|
{ Could also call ReleaseObj(Self). Now this object stays in memory until
|
|
the Control it was hooking will be destroyed. }
|
|
UnHookControl;
|
|
end;
|
|
|
|
destructor TJvHookInfos.Destroy;
|
|
var
|
|
HookInfo: PJvHookInfo;
|
|
Order: TJvHookOrder;
|
|
begin
|
|
{ Remove this TJvHookInfos object from the list of Controller,
|
|
Controller might already be set to nil (in ControlDestroyed) }
|
|
Controller := nil;
|
|
|
|
UnHookControl;
|
|
|
|
for Order := Low(TJvHookOrder) to High(TJvHookOrder) do
|
|
while FFirst[Order] <> nil do
|
|
begin
|
|
HookInfo := FFirst[Order];
|
|
FFirst[Order] := HookInfo.Next;
|
|
{$IFNDEF CLR} // in .NET TJvHookInfo does not implement IDisposible
|
|
Dispose(HookInfo);
|
|
{$ENDIF !CLR}
|
|
end;
|
|
{$IFDEF CLR}
|
|
FStack := nil;
|
|
{$ELSE}
|
|
FreeMem(FStack);
|
|
{$ENDIF CLR}
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TJvHookInfos.HookControl;
|
|
begin
|
|
if FHooked or FControlDestroyed then
|
|
Exit;
|
|
if FControl <> nil then
|
|
begin
|
|
FOldWndProc := FControl.WindowProc;
|
|
FOldWndProcHandle := nil;
|
|
FControl.WindowProc := WindowProc;
|
|
FHooked := True;
|
|
end
|
|
else
|
|
begin
|
|
FOldWndProc := nil;
|
|
FOldWndProcHandle := TFarProc(GetWindowLong(FHandle, GWL_WNDPROC));
|
|
{$IFDEF CLR}
|
|
FOldWndProcHandleInst := MakeObjectInstance(WindowProc);
|
|
SetWindowLong(FHandle, GWL_WNDPROC, FOldWndProcHandleInst);
|
|
{$ELSE}
|
|
SetWindowLong(FHandle, GWL_WNDPROC, Integer(MakeObjectInstance(WindowProc)));
|
|
{$ENDIF CLR}
|
|
FHooked := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvHookInfos.IncDepth;
|
|
begin
|
|
if FStackCount >= FStackCapacity then
|
|
begin
|
|
{ Upsize the stack }
|
|
Inc(FStackCapacity);
|
|
FStackCapacity := FStackCapacity * 2;
|
|
{$IFDEF CLR}
|
|
SetLength(FStack, 2 * FStackCapacity);
|
|
{$ELSE}
|
|
ReallocMem(FStack, 2 * FStackCapacity * SizeOf(Pointer));
|
|
{$ENDIF CLR}
|
|
end;
|
|
Inc(FStackCount);
|
|
end;
|
|
|
|
procedure TJvHookInfos.SetController(const Value: TJvWndProcHook);
|
|
begin
|
|
if Value <> FController then
|
|
begin
|
|
if Assigned(FController) then
|
|
FController.Remove(Self);
|
|
|
|
FController := Value;
|
|
|
|
if Assigned(FController) then
|
|
FController.Add(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvHookInfos.UnHookControl;
|
|
{$IFNDEF CLR}
|
|
var
|
|
Ptr: TFarProc;
|
|
{$ENDIF !CLR}
|
|
begin
|
|
if not FHooked or FControlDestroyed then
|
|
Exit;
|
|
if FControl <> nil then
|
|
begin
|
|
FControl.WindowProc := FOldWndProc;
|
|
FHooked := False;
|
|
end
|
|
else
|
|
begin
|
|
{$IFDEF CLR}
|
|
SetWindowLong(FHandle, GWL_WNDPROC, Integer(FOldWndProcHandle));
|
|
FreeObjectInstance(FOldWndProcHandleInst);
|
|
FOldWndProcHandleInst := nil;
|
|
{$ELSE}
|
|
Ptr := TFarProc(GetWindowLong(FHandle, GWL_WNDPROC));
|
|
SetWindowLong(FHandle, GWL_WNDPROC, Integer(FOldWndProcHandle));
|
|
FreeObjectInstance(Ptr);
|
|
{$ENDIF CLR}
|
|
FHooked := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvHookInfos.WindowProc(var Msg: TMessage);
|
|
var
|
|
TmpHookInfo: PJvHookInfo;
|
|
{ FStack[Index] is used to travel through the hook infos;
|
|
FStack[Index] points to the current hook info (and might be nil)
|
|
Note that the address of FStack may change due to ReallocMem calls in
|
|
IncDepth; thus we can't assign FStack[Index] to a local var.
|
|
}
|
|
Index: Integer;
|
|
begin
|
|
{ An object can now report for every possible message that he has
|
|
handled that message, thus preventing the original control from
|
|
handling the message; this is probably not a good idea in the case
|
|
of WM_DESTROY, WM_CLOSE etc. But that's the users responsibility,
|
|
I think }
|
|
|
|
Msg.Result := 0;
|
|
|
|
IncDepth;
|
|
// (rb) Don't know what the performance impact of a try..finally is.
|
|
try
|
|
{ The even members in the stack are hoBeforeMsg hooks }
|
|
Index := 2 * (FStackCount - 1);
|
|
FStack[Index] := FFirst[hoBeforeMsg];
|
|
while Assigned(FStack[Index]) do
|
|
begin
|
|
{ We retrieve the next hook info *before* the call to Hook(), because,
|
|
see (I) }
|
|
TmpHookInfo := FStack[Index];
|
|
FStack[Index] := FStack[Index].Next;
|
|
if TmpHookInfo.Hook(Msg) or FControlDestroyed then
|
|
Exit;
|
|
{ FStack[Index] may now be changed because of register/unregister calls
|
|
inside HookInfo.Hook(Msg). }
|
|
end;
|
|
|
|
{ Maybe only exit here (before the original control handles the message),
|
|
thus enabling all hooks to respond to the message? Otherwise if you
|
|
have 2 components of the same class, that hook a control, then only 1 will
|
|
get the message }
|
|
|
|
if Assigned(FOldWndProc) then
|
|
FOldWndProc(Msg)
|
|
else
|
|
if FOldWndProcHandle <> nil then
|
|
Msg.Result := CallWindowProc(FOldWndProcHandle, Handle, Msg.Msg,
|
|
Msg.WParam, Msg.LParam);
|
|
|
|
if FControlDestroyed then
|
|
Exit;
|
|
|
|
{ The odd members in the list are hoAftermsg hooks }
|
|
Index := 2 * FStackCount - 1;
|
|
FStack[Index] := FFirst[hoAfterMsg];
|
|
while Assigned(FStack[Index]) do
|
|
begin
|
|
TmpHookInfo := FStack[Index];
|
|
FStack[Index] := FStack[Index].Next;
|
|
if TmpHookInfo.Hook(Msg) or FControlDestroyed then
|
|
Exit;
|
|
end;
|
|
finally
|
|
DecDepth;
|
|
if (Control = nil) and (Msg.Msg = WM_DESTROY) then
|
|
// Handle is being destroyed: remove all hooks on this window
|
|
ControlDestroyed;
|
|
end;
|
|
|
|
{ (I)
|
|
HookInfos before HookInfos after
|
|
call to Hook() call to Hook()
|
|
|
|
|----------| If FStack[Index] point to A |----------|
|
|
-->| hook A | (arrow) and hook A deletes itself | hook B |<--
|
|
|----------| then after the call to Hook, |----------|
|
|
| hook B | FStack[Index] points to B. If we | hook C |
|
|
|----------| then call Next, FStack[Index] |----------|
|
|
| hook C | points to C (should be B)
|
|
|----------|
|
|
}
|
|
end;
|
|
|
|
//=== { TJvWindowHook } ======================================================
|
|
|
|
constructor TJvWindowHook.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FActive := True;
|
|
end;
|
|
|
|
procedure TJvWindowHook.DefineProperties(Filer: TFiler);
|
|
|
|
function DoWrite: Boolean;
|
|
begin
|
|
if Assigned(Filer.Ancestor) then
|
|
Result := IsForm <> TJvWindowHook(Filer.Ancestor).IsForm
|
|
else
|
|
Result := IsForm;
|
|
end;
|
|
|
|
begin
|
|
inherited DefineProperties(Filer);
|
|
Filer.DefineProperty('IsForm', ReadForm, WriteForm, DoWrite);
|
|
end;
|
|
|
|
destructor TJvWindowHook.Destroy;
|
|
begin
|
|
Active := False;
|
|
Control := nil;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TJvWindowHook.DoAfterMessage(var Msg: TMessage): Boolean;
|
|
begin
|
|
Result := False;
|
|
if Assigned(FAfterMessage) then
|
|
FAfterMessage(Self, Msg, Result);
|
|
end;
|
|
|
|
function TJvWindowHook.DoBeforeMessage(var Msg: TMessage): Boolean;
|
|
begin
|
|
Result := False;
|
|
if Assigned(FBeforeMessage) then
|
|
FBeforeMessage(Self, Msg, Result);
|
|
end;
|
|
|
|
procedure TJvWindowHook.HookControl;
|
|
begin
|
|
SetActive(True);
|
|
end;
|
|
|
|
function TJvWindowHook.IsForm: Boolean;
|
|
begin
|
|
Result := (Control <> nil) and ((Control = Owner) and (Owner is TCustomForm));
|
|
end;
|
|
|
|
procedure TJvWindowHook.Notification(AComponent: TComponent;
|
|
Operation: TOperation);
|
|
begin
|
|
inherited Notification(AComponent, Operation);
|
|
if Operation = opRemove then
|
|
begin
|
|
if AComponent = Control then
|
|
Control := nil
|
|
{ Correct? }
|
|
else
|
|
if (Owner = AComponent) or (Owner = nil) then
|
|
Control := nil;
|
|
end;
|
|
end;
|
|
|
|
function TJvWindowHook.NotIsForm: Boolean;
|
|
begin
|
|
{ Correct? }
|
|
Result := (Control <> nil) and not (Control is TCustomForm);
|
|
end;
|
|
|
|
procedure TJvWindowHook.ReadForm(Reader: TReader);
|
|
begin
|
|
if Reader.ReadBoolean then
|
|
if Owner is TCustomForm then
|
|
Control := TControl(Owner);
|
|
end;
|
|
|
|
procedure TJvWindowHook.SetActive(Value: Boolean);
|
|
begin
|
|
if FActive = Value then
|
|
Exit;
|
|
|
|
if not (csDesigning in ComponentState) then
|
|
begin
|
|
if Value then
|
|
begin
|
|
{ Only register if assigned, to prevent unnecessarily overhead }
|
|
if Assigned(FAfterMessage) then
|
|
WndProcHook.RegisterWndProc(FControl, DoAfterMessage, hoAfterMsg);
|
|
if Assigned(FBeforeMessage) then
|
|
WndProcHook.RegisterWndProc(FControl, DoBeforeMessage, hoBeforeMsg);
|
|
end
|
|
else
|
|
begin
|
|
if Assigned(FAfterMessage) then
|
|
WndProcHook.UnRegisterWndProc(FControl, DoAfterMessage, hoAfterMsg);
|
|
if Assigned(FBeforeMessage) then
|
|
WndProcHook.UnRegisterWndProc(FControl, DoBeforeMessage, hoBeforeMsg);
|
|
end;
|
|
end;
|
|
FActive := Value;
|
|
end;
|
|
|
|
procedure TJvWindowHook.SetAfterMessage(const Value: TJvHookMessageEvent);
|
|
begin
|
|
if Active and not (csDesigning in ComponentState) then
|
|
begin
|
|
{ Only register if assigned, to prevent unnecessarily overhead }
|
|
if Assigned(Value) and not Assigned(FAfterMessage) then
|
|
WndProcHook.RegisterWndProc(FControl, DoAfterMessage, hoAfterMsg)
|
|
else
|
|
if not Assigned(Value) and Assigned(FAfterMessage) then
|
|
WndProcHook.UnRegisterWndProc(FControl, DoAfterMessage, hoAfterMsg);
|
|
end;
|
|
FAfterMessage := Value;
|
|
end;
|
|
|
|
procedure TJvWindowHook.SetBeforeMessage(const Value: TJvHookMessageEvent);
|
|
begin
|
|
if Active and not (csDesigning in ComponentState) then
|
|
begin
|
|
{ Only register if assigned, to prevent unnecessarily overhead }
|
|
if Assigned(Value) and not Assigned(FBeforeMessage) then
|
|
WndProcHook.RegisterWndProc(FControl, DoBeforeMessage, hoBeforeMsg)
|
|
else
|
|
if not Assigned(Value) and Assigned(FBeforeMessage) then
|
|
WndProcHook.UnRegisterWndProc(FControl, DoBeforeMessage, hoBeforeMsg);
|
|
end;
|
|
FBeforeMessage := Value;
|
|
end;
|
|
|
|
procedure TJvWindowHook.SetControl(Value: TControl);
|
|
var
|
|
SavedActive: Boolean;
|
|
begin
|
|
if Value <> Control then
|
|
begin
|
|
SavedActive := Active;
|
|
Active := False;
|
|
if FControl <> nil then
|
|
FControl.RemoveFreeNotification(Self);
|
|
|
|
if Assigned(Value) and (csDestroying in Value.ComponentState) then
|
|
{ (rb) this should not happen in calls made by Jv components }
|
|
FControl := nil
|
|
else
|
|
begin
|
|
FControl := Value;
|
|
|
|
if FControl <> nil then
|
|
FControl.FreeNotification(Self);
|
|
|
|
Active := SavedActive;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvWindowHook.UnHookControl;
|
|
begin
|
|
SetActive(False);
|
|
end;
|
|
|
|
procedure TJvWindowHook.WriteForm(Writer: TWriter);
|
|
begin
|
|
Writer.WriteBoolean(IsForm);
|
|
end;
|
|
|
|
//=== { TJvReleaser } ========================================================
|
|
|
|
procedure TJvReleaser.CMRelease(var Msg: TMessage);
|
|
var
|
|
Obj: TObject;
|
|
Index: Integer;
|
|
begin
|
|
Obj := TObject(Msg.WParam);
|
|
Index := FReleasing.IndexOf(Obj);
|
|
if Index >= 0 then
|
|
FReleasing.Delete(Index);
|
|
Obj.Free;
|
|
end;
|
|
|
|
constructor TJvReleaser.Create;
|
|
begin
|
|
inherited Create{$IFDEF CLR}(nil){$ENDIF};
|
|
FReleasing := TList.Create;
|
|
end;
|
|
|
|
procedure TJvReleaser.DefaultHandler(var Msg);
|
|
begin
|
|
with TMessage(Msg) do
|
|
if FHandle <> 0 then
|
|
Result := CallWindowProc(@DefWindowProc, FHandle, Msg, WParam, LParam);
|
|
end;
|
|
|
|
destructor TJvReleaser.Destroy;
|
|
begin
|
|
while FReleasing.Count > 0 do
|
|
begin
|
|
TObject(FReleasing[0]).Free;
|
|
FReleasing.Delete(0);
|
|
end;
|
|
|
|
FReleasing.Free;
|
|
if FHandle <> 0 then
|
|
DeallocateHWnd(FHandle);
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TJvReleaser.GetHandle: THandle;
|
|
begin
|
|
if FHandle = 0 then
|
|
FHandle := AllocateHWnd(WndProc);
|
|
Result := FHandle;
|
|
end;
|
|
|
|
class function TJvReleaser.Instance: TJvReleaser;
|
|
begin
|
|
if GReleaser = nil then
|
|
GReleaser := TJvReleaser.Create;
|
|
Result := GReleaser;
|
|
end;
|
|
|
|
procedure TJvReleaser.Release(AObject: TObject);
|
|
begin
|
|
{ Make sure we're not already releasing this object }
|
|
if FReleasing.IndexOf(AObject) < 0 then
|
|
begin
|
|
FReleasing.Add(AObject);
|
|
PostMessage(Handle, CM_RELEASE, Integer(AObject), 0);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvReleaser.WndProc(var Msg: TMessage);
|
|
begin
|
|
try
|
|
Dispatch(Msg);
|
|
except
|
|
{$IFDEF COMPILER6_UP}
|
|
if Assigned(ApplicationHandleException) then
|
|
ApplicationHandleException(Self);
|
|
{$ELSE}
|
|
Application.HandleException(Self);
|
|
{$ENDIF COMPILER6_UP}
|
|
end;
|
|
end;
|
|
|
|
initialization
|
|
{$IFDEF UNITVERSIONING}
|
|
RegisterUnitVersion(HInstance, UnitVersioning);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
finalization
|
|
{ Don't call FreeAndNil for GReleaser, it's (hypothetically) possible that
|
|
objects need access to the GReleaser var (via call to ReleaseObj) during
|
|
GReleaser.Destroy }
|
|
GReleaser.Free;
|
|
FreeAndNil(GJvWndProcHook);
|
|
GReleaser := nil;
|
|
{$IFDEF UNITVERSIONING}
|
|
UnregisterUnitVersion(HInstance);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
end.
|
|
|