"Shell Hooks" - care to comment ?

Anybody willing to comment on using ShellProcs ?

i (think) i have followed the specs to the letter, but the computer doesn't like it.

i'm installing a system wide hook (yes, in a DLL) and setting the hook by

InstDLL := GetModuleHandle ('MyHook.DLL');
hProc := SetWindowsHookEx (WH_SHELL, MyHook, InstDll, 0);

i have also tried to set it within the DLL by
hProc := SetWindowsHookEx (WH_SHELL, MyHook, 0, 0);

and i am not doing much in "MyHook" proc either: i am just sending a message to the main window (i have the main handle passed to the DLL before it). it goes like this:

function MyHook(nCode: integer; wp: WPARAM; lp: LPARAM): LRESULT;stdcall;

begin
if(nCode >= 0) then
  SendMessage(Caller, wm_User+1, wp, nCode);
Result := CallNextHookEx(hook, nCode, wp, lp);
end;
[variables Caller & Hook are defined]

but the Hook function only for the process and not systemwide and often it causes a GPF in Kernel32 !

Any comments ....?
LVL 7
ahalyaAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

ahalyaAuthor Commented:
Edited text of question
0
ahalyaAuthor Commented:
Edited text of question
0
ahalyaAuthor Commented:
looks like i've to increase points (even) to get comments !

0
Cloud Class® Course: MCSA MCSE Windows Server 2012

This course teaches how to install and configure Windows Server 2012 R2.  It is the first step on your path to becoming a Microsoft Certified Solutions Expert (MCSE).

d003303Commented:
Hi,
try to call CallNextHookEx AFTER you have processed your hook procedure.

Hope this helps,

Slash/d003303
0
ahalyaAuthor Commented:
Hi Slash/d003303 !

Thx for your comment.
actually i am calling the NextHookProc after sending my msg. i made a mistake in shortening the code.

Here is my hook proc.
=========================================================
begin
if(nCode <0) then
   begin
   Result := CallNextHookEx(hookshell, nCode, wp, lp);
   end
else
  begin;
  SendMessage(Caller, wm_User+1, wp, nCode);
  Result :=  CallNextHookEx(hookshell, nCode, wp, lp);
  end;
end;

0
ahalyaAuthor Commented:
Edited text of question
0
ahalyaAuthor Commented:
now that i'm giving away so may points i think i'm entitled to a perfect example !
0
d003303Commented:
Hi ahalya,

NEVER use SendMesage in a HookProc ! Use PostMessage instead, SendMessage waits until the message is processed by the receiving window. If something in your your message handler causes the shell to send out a WH_SHELL message, you are in danger to cause a deadlock or a GPF.
0
d003303Commented:
Sorry, the above should be a comment...
0
ahalyaAuthor Commented:
Thx for the comment. my "test" message handler just increments a count. Thx for your comment (the second one as well). i'll open this up to the other experts now.


0
d003303Commented:
Yo,

maybe the error is somewhere else in your code. So I decieded to quickly (hopefuly not too dirty ;-)) write such a program, here is a working code :

1. DLL-Code

// to be hooktest.dpr
library hooktest;

uses
  Windows,
  Messages;

var HCaller,
    HHookProc : THandle;
    HookProc  : function(nCode: integer; wp: WPARAM; lp: LPARAM): LRESULT; stdcall;

function TheHookProc(nCode: integer; wp: WPARAM; lp: LPARAM): LRESULT; stdcall;
begin
  // this makes you feel safe when no message
  // seems to come through to your main app
  messagebeep(0);

  if(nCode < 0) then
   begin
     Result := CallNextHookEx(HHookProc, nCode, wp, lp);
   end
  else
   begin
     PostMessage(HCaller, wm_User+1, wp, nCode);
     Result := CallNextHookEx(HHookProc, nCode, wp, lp);
   end;
end;

procedure Init(Wnd, LibHandle, ModHandle : THandle); stdcall;
begin
  HCaller := Wnd;
  @HookProc := GetProcAddress(LibHandle, 'TheHookProc');
  if @HookProc <> nil
   then HHookProc := SetWindowsHookEx(WH_SHELL, HookProc, ModHandle, 0);
end;

procedure Exit; stdcall;
begin
  UnhookWindowsHookEx(HHookProc);
end;

exports
  Init,
  Exit,
  TheHookProc;

begin
end.

-----------------------------------------------

2. Test-Application-Code

// to be hookexe.pas
unit hookexe;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls;

type
  TForm1 = class(TForm)
    StatusBar1: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    MsgCount  : Integer;
    LibHandle,
    ModHandle : THandle;
    InitProc  : procedure(Wnd, LibHandle, ModHandle : THandle); stdcall;
    ExitProc  : procedure; stdcall;
    procedure WMUSER_1(var Message : TMessage); message WM_USER + 1;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.WMUSER_1(var Message : TMessage);
begin
  Inc(MsgCount);
  StatusBar1.SimpleText := Format('Got Message %d, Code %d', [MsgCount, Message.lParam]);
end;

procedure TForm1.FormCreate(Sender: TObject);
var DllName : string;
begin
  MsgCount := 0;
  // assuming the DLL is called hooktest.dll and is located in the app's directory
  DllName := ExtractFilePath(Application.ExeName) + 'hooktest.dll';
  LibHandle := LoadLibrary(PChar(DllName));
  ModHandle := GetModuleHandle('hooktest.dll');
  if LibHandle <> 0 then
   begin
     StatusBar1.SimpleText := 'Succeeded to load DLL';
     @ExitProc := GetProcAddress(LibHandle, 'Exit');
     if @ExitProc <> nil then
      begin
        StatusBar1.SimpleText := 'Succeeded to get exitproc address';
        @InitProc := GetProcAddress(LibHandle, 'Init');
        if @InitProc <> nil then
         begin
           StatusBar1.SimpleText := 'Succeeded to get initproc address';
           InitProc(Self.Handle, LibHandle, ModHandle);
         end;
      end;
   end
  else StatusBar1.SimpleText := 'Failed to load DLL';
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if LibHandle <> 0 then
   begin
     if @ExitProc <> nil
      then ExitProc;
     FreeLibrary(LibHandle);
   end;
end;

end.

-----------------------------------------------

3. Form-Code, to be hookexe.dfm

object Form1: TForm1
  Left = 200
  Top = 108
  Width = 262
  Height = 195
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  PixelsPerInch = 96
  TextHeight = 13
  object StatusBar1: TStatusBar
    Left = 0
    Top = 149
    Width = 254
    Height = 19
    Panels = <>
    SimplePanel = True
  end
end

-----------------------------------------------

4. EXE-Project-file

program hooktst;

uses
  Forms,
  hookexe in 'hookexe.pas' {Form1};

{$R *.RES}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.


Have fun,

Slash/d003303
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
ahalyaAuthor Commented:
Hi there,

Thx for your interest. But your code does NOT correctly work. (atleast not in my m/c). To illustrate my point drop two labels on your form and do the following in the message handler.
[actually one label is enough; i/m trying to seperate create/destroy and activate]

Labels get updated only when "HookEXE" is activated and not when activations is switched between other programs. try opeining notepad and paint and switch between them while running Hookexe.
Nothing is happens for me. [yeah, i hear those beeps too. but i don't receive any messages ! - i even tried sending messages even when ncode is < 0; didn't work]


case Message.LParam of
HSHELL_WINDOWCREATED:Label1.Caption := 'Window Created at '+ TimeToStr(Now);
HSHELL_WINDOWDESTROYED:Label1.Caption := 'Window Destroyed at ' + TimeToStr(Now);
HSHELL_WINDOWACTIVATED:Label1.Caption := 'Window Activated at ' + TimeToStr(Now);
HSHELL_TASKMAN:Label1.Caption := 'Taskman Selected';
HSHELL_ACTIVATESHELLWINDOW:Label1.Caption := 'Shell Main window Activated';
end;

case Message.LParam of
HSHELL_WINDOWCREATED   : Label2.Caption := 'Last Create Message received at ' + TimeToStr(Now) + ' with handle  : ' + IntToStr(Message.WParam);
HSHELL_WINDOWDESTROYED : Label2.Caption := 'Last Destroy message received at ' + TimeToStr(Now) + ' with handle  : ' + IntToStr(Message.WParam);
end;

Form1.update;

0
d003303Commented:
Yo,

got the problem. Every hook function is executed in the context of the thread the hook event belongs to. For that, the DLL has to be instanciated from the system for each thread the hook is valid for. The effect is, that the global variables of the DLL are only valid for YOUR application's thread. If another thread causes the hook event, the hook procedure uses uninitialized variables.
Solution : The Caller variable is obsolete. The corresponding code line in hooktest.dpr has to be
  PostMessage(FindWindow('TMyShellHookMainWindow', nil), wm_User+1, wp, nCode);

In hookexe.pas :
Change the name of the form in object inspector to
TMyShellHookMainWindow

Now the handle is valid in any context of the hook procedure. You can change the name of the form in any name you like, just remember that the name has to be adjusted in the DLL-DPR, too.
Also, thus the form name is global to the system, use a 'unique' name.

Have fun,
Slash/d003303
0
ahalyaAuthor Commented:
Thx. this sounds very probable. i'll try it out.

but in that case, how the heck do we maintain global variables within the DLL ?  (in Delphi ?)

i heard that some linker switches do the trick in c++ stuff.
is there an equivalent in Delphi ?


0
d003303Commented:
Global variables in DLLs are global to each instance. Sharing the global variables between instances is not that easy. It cannot be a linker switch, the OS is instanciating a module with an increment on its reference count, allocationg a new heap and giving the module the heap address. The code-segment is only loaded once. So there is no mechanism for the linker to interact.
To share variables between instances you need memory mapped files. I am currently working on a component to simplify their use.

Slash/d003303
0
ahalyaAuthor Commented:
yes, it works (almost).
i'm still having some trouble getting all the messages and the relevant info. (wnd handle & stuff).

i'll try it some more before awarding the points for you.
0
ahalyaAuthor Commented:
couple of questions:

(1). What should be the third parameter
     in a SetWindowsHookEx.

in your code var "ModHandle". (Help file calls it an instance handle, hMod ?)

{SetWindowsHookEx(WH_SHELL, HookProc, ModHandle, 0);}


(2).

in your code you are getting variables LibHandle and a ModHandle.
{form Code:
LibHandle := LoadLibrary(PChar(DllName));
ModHandle := GetModuleHandle('MyHook.DLL');}

BUT, Wouldn't LoadLibrary and GetModuleHandle will return the same value ? (i.e. LibHandle and ModHandle are always equal !)

Could you please clarify ?



0
d003303Commented:
Yo,

you are right, LoadLibrary and GetModuleHandle return the same value. So you can omit the GetModuleHandle call and simplify the Init procedure with just 1 parameter:

procedure Init(LibHandle : THandle); stdcall;

About the  CallNextHookEx function :
If you refer to the documentation it must not be possible that CallNextHookEx is working properly, because the HHookProc parameter is zero in a process space different from the process space that set the hook. I went through some Microsoft C-samples for hooks, and in some comments it says that CallNextHook MUST be called with zero as the first parameter to be working SAFE ! Funny, isn't it ?
I think that it refers to legacy garbage. CallNextHookEx was available in Win3.1 where there was ONE large process space. It would not be nice to call a function CallNextHookExExExEx...
By the way, in their hook samples they also use FindWindow to post the messages to the main application :-)

Have fun,
Slash/d003303
0
ahalyaAuthor Commented:
sorry i didn't get back with my comments all these days.

i'm really confused after reading your last comments.
CallNextHookEx with zero as the first param. Hmm...
what on earth is going on in the OS ?  

anyway, i now have almost concluded that this thing is not goinh to work well. When i have my hook on, the taskbar does weird things. Whenever i close an app task bar doesn't update itself (and all  i'm doing in my hook is a "FindWindow" and postmessage, for God's sake).

Thanks a lot for your comments and time. i really enjoyed it. btw, i do these things for "fun" and this time i haven't had much of it :-(

If you have any more comments, i'm eager to hear them. if you figure out a better way ever please remember to add a comment here !


0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.