Link to home
Start Free TrialLog in
Avatar of Kyle Foster
Kyle FosterFlag for United States of America

asked on

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

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;

Avatar of Member_2_248744
Member_2_248744
Flag of United States of America image

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/questions/21718165/PopupPanel-component.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
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.
 
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;
Avatar of Kyle Foster

ASKER

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?  
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.
Im still upset that I can't get the windowshook to give me system wide messages.
ASKER CERTIFIED SOLUTION
Avatar of Member_2_248744
Member_2_248744
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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