We help IT Professionals succeed at work.

Another SetWindowsHookEx problem.  Setting ThreadID to 0 in Delphi 7 but getting an Application Hook instead

Kyle Foster
Kyle Foster asked
on
1,518 Views
Last Modified: 2008-01-09
I have a Windows Hook DLL where I am trying to capture LButtonDown throughout the system to close one or more of a PopupMenu that I have created myself.  It is descended from TComponent so it has nothing to do with the Windows common popupmenu control, but it must close itself if the user clicks anywhere else on the screen.

I am only getting the WM_LBUTTONDOWN message when I click on my app so it appears that I am not getting a system wide hook.  It is behaving like an Application Hook.

Can someone look at the syntax because I've went over it 100 times and I am probably missing something stupid.

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

here are the HookMouse,UnHookMouse and HookProc functions from the DLL,

you can ignore the parts about the FormDebug (that is working) and the ComponentHandles (there may be more than once instance of the component so I keep a list.  That is also working)

---------------
function HookProc(nCode: Integer; MsgID: WParam; Data: LParam): LResult; stdcall;
var
    ndx     : Integer;
    aHandle : Integer;
begin
    Result := CallNextHookEx(Hook,nCode,MsgID,Data);

    try
        if MsgID=WM_LBUTTONDOWN then begin
            if FormLog<>nil then
                formLog.mmoLOG.Lines.Add('Left Mouse Down Received');

            if ComponentHandles<>nil then
                for ndx := 0 to ComponentHandles.Count-1 do begin
                    aHandle := StrToIntDef(ComponentHandles[ndx],0);
                    if (aHandle<>0) then
                        PostMessage(aHandle, WM_CloseIt, 0, 0);
                end;
        end;
    except
    end;
end;

procedure HookMouse(TheComponentHandle:HWnd;Debug:Boolean); stdcall;
begin
    if ComponentHandles=nil then
        ComponentHandles := TStringlist.Create;

    if (FormLog=nil) and Debug then begin
        FormLog := TFormLog.Create(nil);
        FormLog.Show;
    end;

    ComponentHandles.Add(IntToStr(TheComponentHandle));

    if Hook = 0 then
        Hook:=SetWindowsHookEx(WH_MOUSE,HookProc,HInstance,0);
end;

procedure UnHookMouse(TheComponentHandle:HWnd); stdcall;
var
    ndx : Integer;
begin
    ndx := ComponentHandles.IndexOf(IntToStr(TheComponentHandle));
    if ndx>=0 then
        ComponentHandles.Delete(ndx);

    if ComponentHandles.count=0 then begin
        ComponentHandles.Free;
        ComponentHandles := nil;

        UnhookWindowsHookEx(Hook);
        Hook:=0;

        FormLog.Close;
        FormLog.Free;
        FormLog := nil;
    end;
end;

Comment
Watch Question

Commented:
Hello  kfoster11 , there may be a better way to do this, I would think that the menus will get when they loose the keyboard focus, and then close, because they also vanish if you do NOT click anything, and just change focus (by keyboard). You may look at this EE Question, for some code to do this -

https://www.experts-exchange.com/Programming/Programming_Languages/Delphi/Q_21718165.html

I have plenty of examples for doing Key Hooks, the reason your code does not work is because this DLL is loaded into most of the other processes and in the other processes the HookMouse procedure is NOT called, so in those DLL loads the  TheComponentHandle  is never loaded into the variable, , you will need  Map File memory container to have this TheComponentHandle availible to all instances of the DLL

Commented:
here is some code for a Mouse Hook Library, it has two exported functions, StartHook and StopHook,. . .
By the way - - Your "ComponentHandles" StringList may Not work to store window handles be cause it is NOT a shared memory, if you need to have an array of Handles, I would think you will need to do it in a Memory Mapped file, so all the DLLs can read it, , I wonder why you use stdcall  in your HookMouse procedure ?



library MouseHk;

uses
  Messages, Windows, SysUtils;

{$R *.RES}



const
memFileName = 'eJ7Lc+2Yk';

var
Hooked: Boolean = False;
hMouseHook, hMemFile: THandle;
pFormHnd: PDWORD = nil;



function HkFunc(Code, wParam: Integer; var MouseStrut: TMOUSEHOOKSTRUCT): Integer; stdcall;
begin
Result:=CallNextHookEx(0, Code, wParam, Integer(@MouseStrut));
if pFormHnd = nil then Exit;
if (Code = HC_ACTION)then
  if (wParam = WM_LBUTTONDOWN) or (WM_NCLBUTTONDOWN = wParam) then
    PostMessage(pFormHnd^, WM_CLOSE, 0, 0);

end;



function StartHook(hForm: THandle): Integer;
begin
Result := 0;
if Hooked then
  begin
  Result := 1;
  Exit;
  end;
if not IsWindow(hForm) then
  begin
  Result := 2;
  Exit;
  end;
hMouseHook := SetWindowsHookEx(WH_MOUSE, @HkFunc, hInstance, 0);
if hMouseHook = 0 then
  Result := 3 else
  begin
  hMemFile := CreateFileMapping(MAXDWORD, nil, PAGE_READWRITE,0,
                SizeOf(THandle), memFileName);
  pFormHnd := MapViewOfFile(hMemFile, FILE_MAP_WRITE, 0, 0, 0);
  if pFormHnd = nil then
    begin
    Result := 4;
    UnhookWindowsHookEx(hMouseHook);
    Exit;
    end else
    pFormHnd^ := hForm;

  Hooked := True;
  end;
end;


function StopHook: Boolean;
begin
if Hooked and (hMouseHook <> 0) then
  Result := UnhookWindowsHookEx(hMouseHook) else
  Result := True;
Hooked := False;
if pFormHnd <> nil then
  begin
  UnmapViewOfFile(pFormHnd);
  pFormHnd := nil;
  end;
CloseHandle(hMemFile);
end;


procedure EntryProc(dwReason : DWORD);
begin
if (dwReason = Dll_Process_Detach) then
 begin
 if hMouseHook <> 0 then
   UnhookWindowsHookEx(hMouseHook);
 if pFormHnd <> nil then
   UnmapViewOfFile(pFormHnd);
 CloseHandle(hMemFile);
 end;
end;


exports
  StartHook,
  StopHook;

begin
DLLProc := @EntryProc;
hMemFile := OpenFileMapping(FILE_MAP_WRITE, False, memFileName);
  if hMemFile <> 0 then
    pFormHnd := MapViewOfFile(hMemFile, FILE_MAP_WRITE, 0, 0, 0);
end.
 

Commented:
code in Form to use DLL -




procedure TForm1.but_MouseHkClick(Sender: TObject);
var
hLibb: THandle;
StartHook: function(hForm: THandle): Integer;
begin
hLibb := LoadLibrary('MouseHk.dll');
if hLibb = 0 then
  begin
  Showmessage('ERROR - Library did NOT Load');
  Exit;
  end;

@StartHook := GetProcAddress(hLibb, 'StartHook');
if @StartHook = nil then
  begin
  ShowMessage('StartHook was not located');
  FreeLibrary(hLibb);
  Exit;
  end;
if StartHook(Handle) <> 0 then
  begin
  Showmessage('ERROR - StartHook Failed');
  end;

end;
CERTIFIED EXPERT

Author

Commented:
Here are my thoughts.  My Application will have multiple TMyPopups.  All of these instances will talk to the same dll.  If there is another application that is using the dll, you are correct in saying that there will be another copy of the dll mapped into the processes memory segment.  But it will have it's own hook specific to that application so the dll should work fine.  

Even if there may be a better way to do the popup menu scenario.  I still should be able to make the hook dll work.  There's nothing too it and I've done it before I just couldn't find the code.  My Hook Dll and loadlibrary code look the same as yours.

Also, the only reason I use STDCALL for my StartHook,StopHook procs are in case I wish to use the dll from another language besides Delphi.  Kinda Old School.

The problem once again is that I am getting an Application hook.  There is only one app calling the dll to set the hook.  My SetWindowsHookEx is the same as everyone  elses as far as I can tell.

To keep it simple.  I set the windows hook to capture system wide Left mouse down events and when a WM_LMOUSEDOWN is captured I write to a log forms memo control.

If I click in my app I get the log message, if I click anywhere else on the form I get nothing.

Should I be using the JOURNAL option instead of the WH_MOUSE?  
CERTIFIED EXPERT

Author

Commented:
Well, I just looked at the link you posted above, and it sparked some memories of a custom hint box that a couples of us implemented in a product for win95 that did the same thing.

It appears that I have degressed with time.  I will play with that, because quite frankly, I don't like the thought of distributing a mousehook dll to multiple versions of windows and having to field to calls.

Thanks, I'll let you now how it goes.
CERTIFIED EXPERT

Author

Commented:
Im still upset that I can't get the windowshook to give me system wide messages.
Commented:
This one is on us!
(Get your first solution completely free - no credit card required)
UNLOCK SOLUTION
CERTIFIED EXPERT

Author

Commented:
EVERY Process.  I did not know this, but it makes everything completely understandable to me know.  Thanks.

But, due to your comment that stoked my memory.  I build a function into the popup form to determine is the mouse is over the form.  

I added a timer to the TComponent Descendent to make sure that the mouse is over at least 1 of the popups.  If it leaves and does not return for 500 milliseconds. then I post my user defined message to the foms to close themselves.

Thanks
Unlock the solution to this question.
Join our community and discover your potential

Experts Exchange is the only place where you can interact directly with leading experts in the technology field. Become a member today and access the collective knowledge of thousands of technology experts.

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.