DLL Hook CBT

Il client di Scintilla necessita di conoscere quali finestre vengono create per potere intervenire nel momento stesso in cui la finestra viene creata, attivata ecc... Per potere essere informati di questo tipo di attività occorre creare un hook di sistema CBT. I messaggi che si ricevono sono del
tipo HCBT_CREATEWND, HCBT_ACTIVATE. Per una lista completa vedere qui. In realtà al nostro client non serve discernere quale messaggio sia stato ricevuto ma se anche solo un messaggio è stato ricevuto. Sulla base del lavoro di Primož Gabrijelčič ho creato un hook che si occupi solo di mandare il messaggio al componente sulla Form, senza alcun filtro. Il componente deve solo settare la dll che intende utilizzare e  ha un solo evento che riceve i messaggi cbt.


il parametro importante è lparam perchè contiene l'handle della finestra. Con l'handle è possibile agire per ottenere ulteriori informazioni:

var
  C: array[0..16384] of Char;
  PID: UINT;
  buf:  Array [0..255] of char;

begin

    SendMessage( W, WM_GETTEXT, 256, integer(@C));
    GetWindowThreadProcessID(W,@PID);


    GetClassName( W, Buf, 256);
   Caption:=string(Buf);

Per quanto riguarda l'Hook di Mouse è sufficiente una copia perfetta di questo sorgente con riferimento ai messaggi WH_MOUSE. Quello che è molto importante quando si scrive un hook è di non portarsi dietro intere librerie di sistema solo per sfruttare una singola funzione. In questo caso le funzioni:

function StrScan(const Str: PChar; Chr: Char): PChar; assembler;
function LastDelimiter(const Delimiters, S: string): Integer;
function ExtractFileName(const FileName: string): string;
function ChangeFileExt(const FileName, Extension: string): string;

sono copiate dalla sysutils di Delphi ( possono variare da versione a versione ). Importarle ci evita tutta una serie di problemi legati all'exception handle durante il loading. Lo stesso concetto si applica per le procedure
procedure FreeTokenInformation(var Buffer: Pointer);
procedure QueryTokenInformation(Token: THandle;  InformationClass: TTokenInformationClass; var Buffer: Pointer);
che appartengono a jclSecurity.pas (Jedi library),

Inoltre le seguenti
function DSiClassWndProc(Window: HWND; Message, WParam, LParam: longint): longint; stdcall;
procedure DSiDeallocateHWnd(wnd: HWND);
function DSiAllocateHWnd(wndProcMethod: TWndMethod): HWND;
sono importate dalla relativa unit sempre di Primož Gabrijelčič .

In ultima analisi potremmo addirittura permetterci di lavorare con una variabile locale alla dll che eviti l'invio di messaggi al ricevente ( il TComponent sulla form ) se provenienti dalla stessa finestra quindi con lo stesso Handle. Se dobbiamo solo chiudere le finestre dobbiamo solo sapere della loro esistenza, non di tutto quello che succede. Ancora più opportuno unificare hook CBT e MOUSE per non ricevere troppi messaggi. La variabile può essere gestita nella procedure TCBTHook.ProcessMessage(var Message: TMessage);



library HookCbtkDLL;

uses
  Windows,
  cbt in 'cbt.pas',
  CbtCommon in 'CbtCommon.pas';

procedure DLLEntryPoint(reason: integer);
begin
  if reason = DLL_PROCESS_DETACH then
    Cbt.ProcessDetached;
end; { DLLEntryPoint }

exports
  AttachReceiver,
  DetachReceiver,
  LastError;

begin
  DisableThreadLibraryCalls(HInstance);
  Cbt.ProcessAttached;
  DLLProc := @DLLEntryPoint;
end.



unit Cbt;

interface

uses
  Windows,
  Messages,
  CbtCommon ;


  procedure ProcessAttached;
  procedure ProcessDetached;


  function AttachReceiver(receiver: THandle; Filter: boolean): integer; stdcall;
  function DetachReceiver( receiver: THandle): integer; stdcall;
  function LastError: DWORD; stdcall;

implementation

uses
  CbtSecurity;

type
  THookReceiver = record
    Filtering: boolean;
    Handle   : THandle;
  end;

  TSharedHookData = record
    HookCallback : HHOOK;
    Receiver     : THookReceiver;
  end;
  PSharedHookData = ^TSharedHookData;

  TStaticHookData = record
    HookCallback: TFNHookProc;
  end;

type
  THookWrapper = class
  private
    HookMutex: THandle;
    fLastError: DWORD;
    MemFile  : THandle;
    Receiver: THookReceiver;  //  il Tcomponent
    Shared   : PSharedHookData; // memory mapped file
    Static   : TStaticHookData;
  protected
    function  Hook: integer; virtual;
    function  Unhook: integer; virtual;
  public
    constructor Create(baseName: string; hookCallback: TFNHookProc);
    destructor  Destroy; override;
    function  AttachReceiver(receiver: THandle; Filter: boolean): integer;
    function  DetachReceiver(receiver: THandle): integer;
    procedure input(code: integer; wParam: WPARAM; lParam: LPARAM; var Result: LRESULT);
    procedure CallNextHook(code: integer; wParam: WPARAM; lParam: LPARAM; var Result: LRESULT);
    property  LastError: DWORD read fLastError;
  end;


var
  Wrapper: THookWrapper;
function CBTHookCallback(code: integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
  Result := 0;
  if assigned(wrapper) and assigned(wrapper.Shared) then begin
    WaitForSingleObject(wrapper.HookMutex, INFINITE);
    try
      if code = HCBT_ACTIVATE then
        Wrapper.input(code,wParam,PCBTActivateStruct(lParam)^.hWndActive, Result)
      else if code = HCBT_CLICKSKIPPED then
        Wrapper.input(code,wParam,PMouseHookStruct(lParam)^.hwnd,Result)
      else if code >=0 then
        Wrapper.input(code,wParam,lParam,Result);
    finally ReleaseMutex(wrapper.HookMutex); end;
      Wrapper.CallNextHook(code,wParam,lParam,Result);
  end;
end;

(*Copiate da SysUtils *)
function StrScan(const Str: PChar; Chr: Char): PChar; assembler;
asm
        PUSH    EDI
        PUSH    EAX
        MOV     EDI,Str
        MOV     ECX,0FFFFFFFFH
        XOR     AL,AL
        REPNE   SCASB
        NOT     ECX
        POP     EDI
        MOV     AX,Chr//GG
        REPNE   SCASB
        MOV     EAX,0
        JNE     @@1
        MOV     EAX,EDI
        DEC     EAX
@@1:    POP     EDI
end;

function LastDelimiter(const Delimiters, S: string): Integer;
var
  P: PChar;
begin
  Result := Length(S);
  P := PChar(Delimiters);
  while Result > 0 do begin
    if (S[Result] <> #0) and (StrScan(P, S[Result]) <> nil) then
      Exit;
    Dec(Result);
  end;
end;

function ExtractFileName(const FileName: string): string;
var
  I: Integer;
begin
  I := LastDelimiter('\:', FileName);
  Result := Copy(FileName, I + 1, MaxInt);
end;

function ChangeFileExt(const FileName, Extension: string): string;
var
  I: Integer;
begin
  I := LastDelimiter('.\:',Filename);
  if (I = 0) or (FileName[I] <> '.') then I := MaxInt;
  Result := Copy(FileName, 1, I - 1) + Extension;
end;
(*fine copia SysUtils*)



procedure ProcessAttached;
var
  hBaseName: array [0..MAX_PATH] of char;
  strBaseName: string;
begin
  if GetModuleFileName(HInstance, hBaseName, SizeOf(hBaseName)-1)= 0 then begin
    MessageBox(0,'Hook','Failed to retrieve own module name!',MB_OK);
    Halt;
  end;
  strBaseName := '_HOOK_'+ChangeFileExt(ExtractFileName(hBaseName),'')+'_';
  Wrapper := THookWrapper.Create(strBaseName+'CBTALLMESSAGES', @CBTHookCallback);

end;

procedure ProcessDetached;
begin
  wrapper.Free;
end;

function AttachReceiver(receiver: THandle; Filter: boolean): integer; stdcall;
begin
  Result := Wrapper.AttachReceiver(receiver,Filter);
end;

function DetachReceiver( receiver: THandle): integer; stdcall;
begin
  Result := Wrapper.DetachReceiver(receiver);
end;

function LastError: DWORD; stdcall;
begin
  Result := Wrapper.LastError;
end;

function THookWrapper.AttachReceiver(receiver: THandle; Filter: boolean): integer;
begin
  Result := 0;
  if not assigned(Shared) then
    Exit;
  WaitForSingleObject(HookMutex, INFINITE);
  try
      if Shared^.Receiver.Handle <> 0 then  Result := ERROR_TOO_MANY_RECEIVERS;
      Shared^.Receiver.Handle    := receiver;
      Shared^.Receiver.Filtering := Filter;

      if Shared^.Receiver.Handle  <> 0 then // installo l'hook sul primo receiver
        Result := Hook
      else
        Result := ERROR_NO_ERROR;
  finally ReleaseMutex(HookMutex); end;
end;

procedure THookWrapper.input(code: integer; wParam: WPARAM; lParam: LPARAM;  var Result: LRESULT);
var
  msgRes   : DWORD;
  MsgSent   : boolean;
begin
    if Shared^.Receiver.Filtering then begin
      MsgSent := SendMessageTimeout(Shared^.Receiver.Handle, WM_USER + code, wParam, lParam,
           SMTO_ABORTIFHUNG OR SMTO_BLOCK, 5000{ms}, msgRes) <> 0;
      if Result = 0 then
        Result := msgRes;
    end
    else
      MsgSent := PostMessage(Shared^.Receiver.Handle,WM_USER + code, wParam, lParam) or
        (GetLastError <> ERROR_INVALID_WINDOW_HANDLE);
end;

procedure THookWrapper.CallNextHook(code: integer; wParam: WPARAM; lParam: LPARAM; var Result: LRESULT);
var
  res: LRESULT;
begin
  res := CallNextHookEx(Shared^.HookCallback, code, wParam, lParam);
  if Result = 0 then
    Result := res;
end;

constructor THookWrapper.Create(baseName: string; hookCallback: TFNHookProc);
var
  wasCreated: boolean;
begin


  Static.HookCallback := hookCallback;

  HookMutex := CreateMutex_AllowEveryone(true, PChar(baseName + 'Mutex'));
  if HookMutex = 0 then
    fLastError := GetLastError
  else begin
    try
      MemFile := CreateFileMapping_AllowEveryone(INVALID_HANDLE_VALUE, PAGE_READWRITE,
        0, SizeOf(TSharedHookData), PChar(baseName + 'Shared'));
      if MemFile = 0 then
        fLastError := GetLastError
      else begin
        wasCreated := (GetLastError = NO_ERROR);
        Shared := MapViewOfFile(MemFile, FILE_MAP_WRITE, 0, 0, 0);
        if Shared = nil then
          fLastError := GetLastError
        else if wasCreated then
          FillChar(Shared^, SizeOf(TSharedHookData), 0);
      end;
    finally ReleaseMutex(HookMutex); end;
  end;
end;

destructor THookWrapper.Destroy;
begin

  DetachReceiver(Receiver.Handle );

  UnmapViewOfFile(Shared);
  CloseHandle(MemFile);
  CloseHandle(HookMutex);
  inherited;
end;

function THookWrapper.DetachReceiver(receiver: THandle): integer;
begin
  Result := 0;
  if not assigned(Shared) then
    Exit;
  WaitForSingleObject(HookMutex, INFINITE);
  try
    if Shared^.Receiver.Handle <= 0 then
      Result := ERROR_NOT_REGISTERED
    else begin
      if Shared^.Receiver.Handle = 0 then
        Result := Unhook
      else
        Result := 0;
    end;
  finally ReleaseMutex(HookMutex); end;
end;

function THookWrapper.Hook: integer;
begin
  Result := 0;
  if not assigned(Shared) then
    Exit;
  WaitForSingleObject(HookMutex, INFINITE);
  try
    if Shared^.HookCallback <> 0 then
      Result := ERROR_ALREADY_HOOKED
    else begin
      Shared^.HookCallback := SetWindowsHookEx( WH_CBT, Static.HookCallback,HInstance,0);
      if Shared^.HookCallback = 0 then
        Result := GetLastError;
    end;
  finally ReleaseMutex(HookMutex); end;
end;


function THookWrapper.Unhook: integer;
begin
  Result := 0;
  if not assigned(Shared) then
    Exit;
  WaitForSingleObject(HookMutex, INFINITE);
  try
    if Shared^.HookCallback <> 0 then begin
      if not UnhookWindowsHookEx(Shared^.HookCallback) then
        Result := GetLastError;
      Shared^.HookCallback := 0;
    end;
  finally ReleaseMutex(HookMutex); end;
end;



end.

unit cbtCommon;

interface

uses
  Windows;


const
  ERROR_FIRST = 0;

  ERROR_NO_ERROR = ERROR_FIRST;
  ERROR_EXPORTS = ERROR_NO_ERROR-1;
  ERRROR_ALREADY_REGISTERED = ERROR_EXPORTS-1;
  ERROR_NOT_REGISTERED = ERRROR_ALREADY_REGISTERED-1;
  ERROR_TOO_MANY_RECEIVERS = ERROR_NOT_REGISTERED-1;
  ERROR_ALREADY_HOOKED = ERROR_TOO_MANY_RECEIVERS-1;
  LAST_ERROR = ERROR_ALREADY_HOOKED;

var

  HookErrors: array [LAST_ERROR..ERROR_FIRST] of string;

implementation

resourcestring
  sNoError           = 'No error.';
  sNoExports         = 'error exporting functions.';
  sAlreadyRegistered = 'Receiver already registered.';
  sNotRegistered     = 'Receiver not registered.';
  sTooManyReceivers  = 'Too many receivers.';
  sAlreadyHooked     = 'System hook already active.';

initialization
  HookErrors[ERROR_NO_ERROR]           := sNoError;
  HookErrors[ERROR_EXPORTS]            := sNoExports;
  HookErrors[ERRROR_ALREADY_REGISTERED]:= sAlreadyRegistered;
  HookErrors[ERROR_NOT_REGISTERED]     := sNotRegistered;
  HookErrors[ERROR_TOO_MANY_RECEIVERS] := sTooManyReceivers;
  HookErrors[ERROR_ALREADY_HOOKED]     := sAlreadyHooked;
end.

unit CBTSecurity;

interface

uses
  Windows, JwaAclApi, JwaAccCtrl, JwaWinNT, JwaWinBase, JwaWinType;

type
  PSecurityAttributes = LPSECURITY_ATTRIBUTES;

type
  TcbtSecurityAttributes = class
  private
    gsaDacl    : PACL;
    gsaSecAttr : TSecurityAttributes;
    gsaSecDescr: TSecurityDescriptor;
    gsaSid     : PSID;
  protected
    function GetSA: PSecurityAttributes;
  public
    constructor AllowAccount(const accountName: string);
    constructor AllowEveryone;
    constructor AllowSID(sid: PSID);
    destructor  Destroy; override;
    property SecurityAttributes: PSecurityAttributes read GetSA;
  end;

function CreateEvent_AllowAccount(const accountName: string; manualReset, initialState: boolean; const eventName: string): THandle;
function CreateEvent_AllowEveryone(manualReset, initialState: boolean; const eventName: string): THandle;
function CreateFileMapping_AllowAccount(const accountName: string; hFile: THandle; flProtect, dwMaximumSizeHigh, dwMaximumSizeLow: DWORD;
  const fileMappingName: string): THandle;
function CreateFileMapping_AllowEveryone(hFile: THandle; flProtect, dwMaximumSizeHigh, dwMaximumSizeLow: DWORD;
  const fileMappingName: string): THandle;
function CreateMutex_AllowAccount(const accountName: string;  initialOwner: boolean; const mutexName: string): THandle;
function CreateMutex_AllowEveryone(initialOwner: boolean;  const mutexName: string): THandle;
function CreateSemaphore_AllowAccount(const accountName: string; initialCount, maximumCount: longint; const semaphoreName: string): THandle;
function CreateSemaphore_AllowEveryone(initialCount, maximumCount: longint; const semaphoreName: string): THandle;

function GetCurrentSIDName: string;

implementation

uses
  SysUtils,
//  JclSecurity,
  JwaSddl;
procedure FreeTokenInformation(var Buffer: Pointer);
begin
  if Buffer <> nil then
    FreeMem(Buffer);
  Buffer := nil;
end;
procedure QueryTokenInformation(Token: THandle;  InformationClass: TTokenInformationClass; var Buffer: Pointer);
var
  Ret: BOOL;
  Length, LastError: DWORD;
begin
  Buffer := nil;
//  if not IsWinNT then  // Win9x/ME
//    Exit;
  Length := 0;
  {$IFDEF FPC}
  Ret := GetTokenInformation(Token, InformationClass, Buffer, Length, @Length);
  {$ELSE ~FPC}
  Ret := GetTokenInformation(Token, InformationClass, Buffer, Length, Length);
  {$ENDIF ~FPC}
  if (not Ret) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) then
  begin
    GetMem(Buffer, Length);
    {$IFDEF FPC}
    Ret := GetTokenInformation(Token, InformationClass, Buffer, Length, @Length);
    {$ELSE ~FPC}
    Ret := GetTokenInformation(Token, InformationClass, Buffer, Length, Length);
    {$ENDIF ~FPC}
    if not Ret then
    begin
      LastError := GetLastError;
      FreeTokenInformation(Buffer);
      SetLastError(LastError);
    end;
  end;
end;


function CreateEvent_AllowAccount(const accountName: string;  manualReset, initialState: boolean; const eventName: string): THandle;
var
  gsa: TcbtSecurityAttributes;
begin
  gsa := TcbtSecurityAttributes.AllowAccount(accountName);
  try
    Result := CreateEvent(gsa.SecurityAttributes, manualReset, initialState, PChar(eventName));
  finally FreeAndNil(gsa); end;
end;

function CreateEvent_AllowEveryone(manualReset, initialState: boolean;   const eventName: string): THandle;
var
  gsa: TcbtSecurityAttributes;
begin
  gsa := TcbtSecurityAttributes.AllowEveryone;
  try
    Result := CreateEvent(gsa.SecurityAttributes, manualReset, initialState, PChar(eventName));
  finally FreeAndNil(gsa); end;
end;

function CreateFileMapping_AllowAccount(const accountName: string;  hFile: THandle; flProtect, dwMaximumSizeHigh, dwMaximumSizeLow: DWORD;
  const fileMappingName: string): THandle;
var
  gsa: TcbtSecurityAttributes;
begin
  gsa := TcbtSecurityAttributes.AllowAccount(accountName);
  try
    Result := CreateFileMapping(hFile, gsa.SecurityAttributes, flProtect,
      dwMaximumSizeHigh, dwMaximumSizeLow, PChar(fileMappingName));
  finally FreeAndNil(gsa); end;
end;

function CreateFileMapping_AllowEveryone(hFile: THandle; flProtect,  dwMaximumSizeHigh, dwMaximumSizeLow: DWORD;
  const fileMappingName: string): THandle;
var
  gsa: TcbtSecurityAttributes;
begin
  gsa := TcbtSecurityAttributes.AllowEveryone;
  try
    Result := CreateFileMapping(hFile, gsa.SecurityAttributes, flProtect,
      dwMaximumSizeHigh, dwMaximumSizeLow, PChar(fileMappingName));
  finally FreeAndNil(gsa); end;
end;

function CreateMutex_AllowAccount(const accountName: string;  initialOwner: boolean; const mutexName: string): THandle;
var
  gsa: TcbtSecurityAttributes;
begin
  gsa := TcbtSecurityAttributes.AllowAccount(accountName);
  try
    Result := CreateMutex(gsa.SecurityAttributes, initialOwner, PChar(mutexName));
  finally FreeAndNil(gsa); end;
end;

function CreateMutex_AllowEveryone(initialOwner: boolean;  const mutexName: string): THandle;
var
  gsa: TcbtSecurityAttributes;
begin
  gsa := TcbtSecurityAttributes.AllowEveryone;
  try
    Result := CreateMutex(gsa.SecurityAttributes, initialOwner, PChar(mutexName));
  finally FreeAndNil(gsa); end;
end;

function CreateSemaphore_AllowAccount(const accountName: string; initialCount, maximumCount: longint; const semaphoreName: string): THandle;
var
  gsa: TcbtSecurityAttributes;
begin
  gsa := TcbtSecurityAttributes.AllowAccount(accountName);
  try
    Result := CreateSemaphore(gsa.SecurityAttributes, initialCount, maximumCount, PChar(semaphoreName));
  finally FreeAndNil(gsa); end;
end;

function CreateSemaphore_AllowEveryone(initialCount, maximumCount: longint; const semaphoreName: string): THandle;
var
  gsa: TcbtSecurityAttributes;
begin
  gsa := TcbtSecurityAttributes.AllowEveryone;
  try
    Result := CreateSemaphore(gsa.SecurityAttributes, initialCount, maximumCount, PChar(semaphoreName));
  finally FreeAndNil(gsa); end;
end;

function GetCurrentSIDName: string;
var
  hAccessToken: THandle;
  hProcess    : THandle;
  infoBuffer  : pointer;
  SIDName     : PChar;
begin
  Result := '';
  hProcess := GetCurrentProcess;
  if OpenProcessToken(hProcess, TOKEN_READ, hAccessToken) then try
    QueryTokenInformation(hAccessToken, TokenUser, infoBuffer);
    if assigned(infoBuffer) then try
      if ConvertSidToStringSid(PSIDAndAttributes(infoBuffer)^.sid, SIDName) then begin
        Result := SIDName;
        LocalFree(cardinal(SIDName));
      end;
    finally FreeMem(infoBuffer); end
  finally CloseHandle(hAccessToken); end;
end;



constructor TcbtSecurityAttributes.AllowAccount(const accountName: string);
var
  domain    : string;
  domainSize: DWORD;
  sid       : PSID;
  sidSize   : DWORD;
  use       : DWORD;
begin
  if Win32Platform <> VER_PLATFORM_WIN32_NT then
    Exit;

  domainSize := 0;
  LookupAccountName(nil, PChar(accountName), nil, sidSize, nil, domainSize, use);
  sid := AllocMem(sidSize);
  try
    SetLength(domain, domainSize);
    Win32Check(LookupAccountName(nil, PChar(accountName), sid, sidSize, PChar(domain), domainSize, use));
    AllowSID(sid);
  finally FreeMem(sid); end;
end;

constructor TcbtSecurityAttributes.AllowEveryone;
var
  siaWorld: SID_IDENTIFIER_AUTHORITY;
  sid     : PSID;
begin
  if Win32Platform <> VER_PLATFORM_WIN32_NT then
    Exit;

  siaWorld := SECURITY_WORLD_SID_AUTHORITY;
  sid := AllocMem(GetSidLengthRequired(1));
  try
    Win32Check(InitializeSid(sid, @siaWorld, 1));
    PDWORD(GetSidSubAuthority(sid, 0))^ := SECURITY_WORLD_RID;
    AllowSID(sid);
  finally FreeMem(sid); end;
end;

constructor TcbtSecurityAttributes.AllowSID(sid: PSID);
var
  daclSize: integer;
  sidSize : integer;
begin
  if Win32Platform <> VER_PLATFORM_WIN32_NT then
    Exit;
  sidSize := GetLengthSid(sid);
  gsaSid := AllocMem(sidSize);
  Move(sid^, gsaSid^, sidSize);

  daclSize := SizeOf(ACL) + SizeOf(ACCESS_ALLOWED_ACE) + GetLengthSid(gsaSid);
  gsaDacl := AllocMem(daclSize);
  Win32Check(InitializeAcl(gsaDacl, daclSize, ACL_REVISION));
  Win32Check(AddAccessAllowedAce(gsaDacl, ACL_REVISION, GENERIC_ALL, gsaSid));

  Win32Check(InitializeSecurityDescriptor(@gsaSecDescr, SECURITY_DESCRIPTOR_REVISION));
  Win32Check(SetSecurityDescriptorDacl(@gsaSecDescr, true, gsaDacl, false));

  FillChar(gsaSecAttr, SizeOf(gsaSecAttr), 0);
  gsaSecAttr.nLength := SizeOf(gsaSecAttr);
  gsaSecAttr.lpSecurityDescriptor := @gsaSecDescr;
end;

destructor TcbtSecurityAttributes.Destroy;
begin
  if assigned(gsaSid) then begin
    FreeMem(gsaSid);
    gsaSid := nil;
  end;
  if assigned(gsaDacl) then begin
    FreeMem(gsaDacl);
    gsaDacl := nil;
  end;
  inherited;
end;

function TcbtSecurityAttributes.GetSA: PSecurityAttributes;
begin
  if Win32Platform = VER_PLATFORM_WIN32_NT then
    Result := @gsaSecAttr
  else
    Result := nil;
end;

end.

unit HookCbtComp;

interface

uses
  SysUtils,
  Windows,
  Messages,
  Classes,
  Controls,
  CbtCommon;

type

  TCBTHookAllMessages = procedure(Sender: TObject; Code, wParam, lParam: longint) of object;

  TSysHook = class(TComponent)
  private
    FActive      : boolean;
    FHookDLLName : string;
    FIsFiltering : boolean;
    FListenerWnd : THandle;
    FOnAllCbtMessages: TCBTHookAllMessages;
    procedure HookMain(var Message: TMessage);
    procedure SetFiltering(const Value: boolean);
    property  AllowFiltering: boolean read FIsFiltering write SetFiltering;
  protected
    function  MyName: string; virtual;
    procedure ProcessMessage(var Message: TMessage); virtual; abstract;
    procedure SetHookDLLName(const Value: string); virtual;
    procedure AllCbtMessages(code, wParam, lParam: longint ); virtual;
  public
    destructor  Destroy; override;
    function  Start: string;
    procedure Stop;
    property  Active: boolean read FActive;
  published
    property  HookDLL: string read FHookDLLName write SetHookDLLName;
    property  OnAllCbtMessages: TCBTHookAllMessages read FOnAllCbtMessages write FOnAllCbtMessages;
  end;


  TCBTHook = class(TSysHook)
  private
  protected
    procedure ProcessMessage(var Message: TMessage); override;
  published
  end;

  procedure Register;

implementation

uses
  Forms,
  HookCbtLoader;
const
  GWL_METHODCODE = SizeOf(pointer) * 0;
  GWL_METHODDATA = SizeOf(pointer) * 1;

  CDSiHiddenWindowName = 'DSiUtilWindow';
var
  GDSiWndHandlerCritSect: TRTLCriticalSection;
  GDSiWndHandlerCount: integer;
  GTerminateBackgroundTasks: THandle;

procedure Register;
begin
  RegisterComponents('HOOK',[TCBTHook]);
end; { Register }

  procedure DSiDeallocateHWnd(wnd: HWND);
  begin
    if wnd = 0 then
      Exit;
    DestroyWindow(wnd);
    EnterCriticalSection(GDSiWndHandlerCritSect);
    try
      Dec(GDSiWndHandlerCount);
      if GDSiWndHandlerCount <= 0 then
        {$IFDEF ScopedUnitNames}Winapi.{$ENDIF}Windows.UnregisterClass(CDSiHiddenWindowName, HInstance);
    finally LeaveCriticalSection(GDSiWndHandlerCritSect); end;
  end;

  function DSiClassWndProc(Window: HWND; Message, WParam, LParam: longint): longint; stdcall;
  var
    instanceWndProc: TMethod;
    msg            : TMessage;
  begin
    {$IFDEF CPUX64}
    instanceWndProc.Code := pointer(GetWindowLongPtr(Window, GWL_METHODCODE));
    instanceWndProc.Data := pointer(GetWindowLongPtr(Window, GWL_METHODDATA));
    {$ELSE}
    instanceWndProc.Code := pointer(GetWindowLong(Window, GWL_METHODCODE));
    instanceWndProc.Data := pointer(GetWindowLong(Window, GWL_METHODDATA));
    {$ENDIF ~CPUX64}
    if Assigned(TWndMethod(instanceWndProc)) then
    begin
      msg.msg := Message;
      msg.wParam := WParam;
      msg.lParam := LParam;
      msg.Result := 0;
      TWndMethod(instanceWndProc)(msg);
      Result := msg.Result
    end
    else
      Result := DefWindowProc(Window, Message, WParam,LParam);
  end; { DSiClassWndProc }
  function DSiAllocateHWnd(wndProcMethod: TWndMethod): HWND;
  var
    alreadyRegistered: boolean;
    tempClass        : TWndClass;
    utilWindowClass  : TWndClass;
  begin
    Result := 0;
    FillChar(utilWindowClass, SizeOf(utilWindowClass), 0);
    EnterCriticalSection(GDSiWndHandlerCritSect);
    try
      alreadyRegistered := GetClassInfo(HInstance, CDSiHiddenWindowName, tempClass);
      if (not alreadyRegistered) or (tempClass.lpfnWndProc <> @DSiClassWndProc) then begin
        if alreadyRegistered then
          {$IFDEF ScopedUnitNames}Winapi.{$ENDIF}Windows.UnregisterClass(CDSiHiddenWindowName, HInstance);
        utilWindowClass.lpszClassName := CDSiHiddenWindowName;
        utilWindowClass.hInstance := HInstance;
        utilWindowClass.lpfnWndProc := @DSiClassWndProc;
        utilWindowClass.cbWndExtra := SizeOf(TMethod);
        if {$IFDEF ScopedUnitNames}Winapi.{$ENDIF}Windows.RegisterClass(utilWindowClass) = 0 then
          raise Exception.CreateFmt('Unable to register DSiWin32 hidden window class. %s',
            [SysErrorMessage(GetLastError)]);
      end;
      Result := CreateWindowEx(WS_EX_TOOLWINDOW, CDSiHiddenWindowName, '', WS_POPUP,
        0, 0, 0, 0, 0, 0, HInstance, nil);
      if Result = 0 then
        raise Exception.CreateFmt('Unable to create DSiWin32 hidden window. %s',
                [SysErrorMessage(GetLastError)]);
      {$IFDEF CPUX64}
      SetWindowLongPtr(Result, GWL_METHODDATA, NativeInt(TMethod(wndProcMethod).Data));
      SetWindowLongPtr(Result, GWL_METHODCODE, NativeInt(TMethod(wndProcMethod).Code));
      {$ELSE}
      SetWindowLong(Result, GWL_METHODDATA, cardinal(TMethod(wndProcMethod).Data));
      SetWindowLong(Result, GWL_METHODCODE, cardinal(TMethod(wndProcMethod).Code));
      {$ENDIF ~CPUX64}
      Inc(GDSiWndHandlerCount);
    finally LeaveCriticalSection(GDSiWndHandlerCritSect); end;
  end;



destructor TSysHook.Destroy;
begin
  Stop;
  inherited;
end;

procedure TSysHook.AllCbtMessages (code, wParam, lParam: longint);
begin
  if assigned(FOnAllCbtMessages) then
    FOnAllCbtMessages(Self,code,wParam,lParam);
end;

procedure TSysHook.HookMain(var Message: TMessage);
begin
  if Message.Msg < WM_USER then
    with Message do
      Result := DefWindowProc(FListenerWnd, Msg, wParam, lParam)
  else
    ProcessMessage(Message);
end;

//Solo per error reporting.
function TSysHook.MyName: string;
begin
  if Name <> '' then
    Result := Name
  else
    Result := ClassName;
end;

procedure TSysHook.SetFiltering(const Value: boolean);
begin
  if Active then
    raise Exception.Create('CBT HOOK is active!');
  FIsFiltering := Value;
end;

// Setta il nome della DLL
procedure TSysHook.SetHookDLLName(const Value: string);
begin
  if FActive then
    MessageBox(0, PChar('can''t change DLL name while cbt hook is active!'),
      PChar(MyName), MB_OK + MB_ICONERROR)
  else
    FHookDLLName := Value;
end;

function TSysHook.Start: string;
var
  hookRes: integer;
begin
  if not FActive then begin
    if FHookDLLName = '' then
      Result := 'Missing DLL name!'
    else begin
      hookRes := LoadHookDLL(FHookDllName);
      if hookRes <> 0 then
        Result := HookError(hookRes)
      else begin
        FListenerWnd := DSiAllocateHwnd(HookMain);
        if FListenerWnd = 0 then
          Result := 'Listener Error'
        else begin
          hookRes := CbtfnAttachReceiver(FListenerWnd,FIsFiltering);
          if hookRes <> 0 then begin
            DSiDeallocateHWnd(FListenerWnd);
            FListenerWnd := 0;
            Result := HookError(hookRes);
          end
          else begin
            Result := '';
            FActive := true;
          end;
        end;
      end;
    end;
  end;
end;

procedure TSysHook.Stop;
begin
  if FActive then begin
    if FListenerWnd <> 0 then begin
      DSiDeallocateHWnd(FListenerWnd);
      FListenerWnd := 0;
    end;
    UnloadHookDLL;
    FActive := false;
  end;
end;

procedure TCBTHook.ProcessMessage(var Message: TMessage);
var
  code       : DWORD;
  flags      : byte;
  repeatCount: word;
  scanCode   : byte;
begin
  if Message.Msg >= WM_USER then begin
    Message.Result := 0;
    code := Message.msg-WM_USER;
    AllCbtMessages(code,Message.wParam,Message.lParam); // nessun filtro. Tutti i messaggi
  end;
end;
initialization
  InitializeCriticalSection(GDSiWndHandlerCritSect);
  GDSiWndHandlerCount := 0;
finalization
  DeleteCriticalSection(GDSiWndHandlerCritSect);

end.
unit HookCbtLoader;

interface

uses
  Windows,
  CbtCommon;

type
  TCbtFNAttachReceiver = function ( receiver: THandle; isFiltering: boolean): integer; stdcall;
  TCbtFNDetachReceiver = function ( receiver: THandle): integer; stdcall;
  TCbtFNLastError      = function (): DWORD; stdcall;
var
  // Setto i puntatori alle funzioni di attach, detach e Lasterror
  CbtfnAttachReceiver: TCbtFNAttachReceiver;
  CbtfnDetachReceiver: TCbtFNDetachReceiver;
  CbtfnLastError     : TCbtFNLastError;

  function  LoadHookDLL(dllName: string): integer;

  procedure UnloadHookDLL;

  function  HookError(errorCode: integer): string;

implementation

uses
  SysUtils;


var
  dllHandle: HINST;
  dllCount : integer;

  function LoadHookDLL(dllName: string): integer;
  begin
    if dllHandle = 0 then begin
      dllHandle := LoadLibrary(PChar(dllName));
      if (dllHandle = 0) and (not SameText(ExtractFileExt(dllName),'.dll')) then
        dllHandle := LoadLibrary(PChar(ChangeFileExt(dllName,'.dll')));
      if dllHandle = 0 then
        Result := GetLastError
      else begin
        Inc(dllCount);
        @CbtfnAttachReceiver := GetProcAddress(dllHandle,'AttachReceiver');  // setto gli address
        @CbtfnDetachReceiver := GetProcAddress(dllHandle,'DetachReceiver');
        @CbtfnLastError      := GetProcAddress(dllHandle,'LastError');
        if (@CbtfnAttachReceiver = nil) or
           (@CbtfnDetachReceiver = nil) or
           (@CbtfnLastError = nil) then
        begin                            
          Result := ERROR_EXPORTS;
          UnloadHookDLL;
        end
        else
          Result := 0;
      end;
    end
    else begin
      Inc(dllCount);
      Result := 0;
    end;
  end;

  procedure UnloadHookDLL;
  begin
    if dllHandle <> 0 then begin
      Dec(dllCount);
      if dllCount <= 0 then begin
        FreeLibrary(dllHandle);
        dllHandle := 0;
        dllCount := 0;
        @CbtfnAttachReceiver := nil;
        @CbtfnDetachReceiver := nil;
        @CbtfnLastError      := nil;
      end;
    end;
  end;

  function HookError(errorCode: integer): string;
  begin
    if errorCode > 0 then
      Result := SysErrorMessage(errorCode)
    else if (errorCode >= Low(HookErrors)) and
            (errorCode <= High(HookErrors)) then
      Result := HookErrors[errorCode]
    else
      Result := Format('Error %d',[errorCode]);
  end;
  
initialization
  dllHandle := 0;
  dllCount := 0;
  CbtfnAttachReceiver := nil;
  CbtfnDetachReceiver := nil;
  CbtfnLastError      := nil;
finalization
  if dllHandle <> 0 then
    MessageBox(0, 'CBT Hook DLL was not unloaded!', 'CBTHOOK', MB_OK + MB_ICONWARNING);
end.

Commenti

Post più popolari