Link to home
Start Free TrialLog in
Avatar of DelphiOnly
DelphiOnly

asked on

How to do the Reverse of "GetWindowThreadProcessID" ?

When i use ShellExecuteEx to run an app, i can receive an hInstApp and an hProcees. Now i want to find the window handles (HWNDs) associated with the process. How can i do that (preferably w/o going through enumwindows)

if i know the HWND, then i can get the PID using GetWindowThreadProcessID API. Is there an API to do the reverse ?

Also can someone explain to me the differences between a THandle, hInstApp & hProcess (from the SHELLEXECUTEINFO structure) and a PID ?
Avatar of heathprovost
heathprovost
Flag of United States of America image

I have looke through the API very carfully and see no way to do what you are asking.  I think the problem is that although all windows have a PID associated with their thread, not all processes have window handles.  For example, Console apps and simple executables with no message loop (dont call winmain) wont have a window handle.  Therefore you are pretty much stuck with either EnumWindows or if you are carefule you can use GetTopWindow, but there is no guarentee that another window wont open between the call to ShellExecuteEx and GetTopWIndow.  In practice it works rather nicely most of the time though.

As for the difference between those handle types. A THandle can be ANY type of handle (more or less) but is usually a unique identifier for a window.  A hinstApp and a hProcess are very similar on the surface.  The primary difference is that some API calls require one, while others require the other.  I think that hprocess handles are actually system objects while hinstapp is just a unique ID.  ProcessID is also only a unique ID for a process.  You generally have to get a handle to the process to do any real work (OpenProcess API).

Hope this helps

Heath
Avatar of Madshi
Madshi

Hi,

here come my 2 cents. Heath is right, but perhaps I can fill some gaps...

I've never done anything with the "hInstApp" value. Normally you need only a process ID (there's exactly one process ID for each process) or a process handle (there can be multiple handles for each process).
Unfortunately you don't get the PID from ShellExecuteEx (I hate it) and unfortunately there's no conversion call to get a PID from a process Handle (I hate it).
The problem with the window handles is this: A process can have one or more threads. Each thread can have no, one or more windows. Now what window would you like to get if there was a function like PidToWnd or hProcessToWnd?
For what purpose do you need the window handles?
You could do this: Enumerate all threads of the process, then enumerate all windows of all the threads in the process (using EnumThreadWindows). Unfortunately there's no way in NT4 to enumerate threads. I'm currently working on a solution that reads this kind of information from the registry, but it's not ready yet. However, if you have Delphi4, you can download my unit "enumStuff", which handles all enumeration stuff like process enumeration, thread enumeration (only win95/98 in the moment) and window enumeration (all windows, or all windows of a thread, or all taskbar buttons, or ...).
More questions?
Perhaps you should tell us for what you need the window handles...

Regards, Madshi.
Oooops, have forgotten to say: You can download the "enumStuff" unit from my homepage "http://www.madshi.com" in the "sources" section. But remember, it works only with D4...  :-(
Avatar of DelphiOnly

ASKER

Heath,

i want to open this question to others. if i don't get a satisfactory answer then i'll come back to your answer.  I know that not all Processes will have window handles. But my questions is to find the Window handle when it exists.

As for your comment on the type of handles, please i need better & clear explanations. (yes, the "Dummy" series books are written for me :-)

Madshi:

Let me try to understand this better:
1.  A process has ONE unique Process ID (PID)
2.  But it can have multiple Process Handles ............(hProcess)
 
3.  Process can have multiple threads. (QUESTION: Is the handle of a thread is the hProcess ?)
4.  Each thread can have multiple Windows (HWND)

There are two questions:
When we ShellExecuteEx we get an hProcess back.  But according to (2), our process could have multiple hProcesses. so what do we get ?

If I KNOW that my process creates only one thread and that thread creates only one window then how do i go about grabbing that window handle.

and btw, if a "function like PidToWnd or hProcessToWnd" existed i would have loved to receive  a list of HWNDs. (kinda like a TStringList !). That would have been nice, but one cannot expect such nice things from Microsoft, i guess.

========
and now to what i am trying to do:

I have a list of *.RA Files.

i use ShellExecEx to launch Real Player to play the first *.RA File.  (can use CreateProcees as well)

i want to start playing the second file when RealPlayer is finished playing the first file.

Right now the only way I could come up with is to track the caption of the RealPlayer  (btw, Is there a better way here ?) to tell me when the first file is done.

so i need the HWND to send in the GetWindowText API.  Since i could not find the HWND from hProcess (or PID if i use CreateProcess instead of ShellExecEx) i am using FindWindow (i am actually checking the Window Caption within an EnumWindows) and I hate doing this !!

Things that could help me:

How to get the process handle within the EnumWindows given the Window Handle ? [if i know this i don't have to check the Window Caption within my EnumWindows, but rather can look for the hProcess i got back from ShellExecEx]

How do i make sure that "ShellExecEx" waits until "RealPlayer" is started ?
=======

finally, why i am doing this:

i have a bunch of real audio files in my hard drive, but couldn't figure out how can i tell the RealPlayer to continuously play those songs. so i had no choice :-)

and i use Delphi 2 !! (cannot afford to keep up with the ungrades :-)



Hi DelphiOnly,

1. A process has ONE unique Process ID (=> PID)
2. But it can have multiple Process Handles (=> hProcess)
3. Process can have multiple threads.
   >> QUESTION: Is the handle of a thread is the hProcess ? <<
   NO, the threads have their own ID and their own handle. hProcess is the handle of the PROCESS
4. Each thread can have multiple Windows (=> HWND)

>> There are two questions:
>> When we ShellExecuteEx we get an hProcess back.  But
>> according to (2), our process could have multiple hProcesses.
>> so what do we get ?

Ok, something about handles and IDs. Each process and each thread has exactly one ID. It's a unique identifier. Now, if you want to do some things, you need to OPEN such a process/thread ID with OpenThread/OpenProcess. You have to give in, which rights you need and you get a handle to the process/thread as a result. That means each handle represents the process/thread with different (or with the same) rights. The handle you receive from ShellExecuteEx is just one handle, there can be more.
As I said, unfortunately you can't (in a simple way) get the processID from the hProcess.

>> If I KNOW that my process creates only one thread and that
>> thread creates only one window then how do i go about
>> grabbing that window handle.

You should call CreateProcess instead of ShellExecuteEx. With CreateProcess you must give in the realPlayer executable full path and as the first parameter the file you want to be played. I think, that is no problem, right?
From CreateProcess you receive this structure:
    HANDLE hProcess;
    HANDLE hThread;
    DWORD dwProcessId;
    DWORD dwThreadId;
Now you have the handles and the IDs of the process and of its main thread. If you're sure that there's only one thread, you can simply use EnumThreadWindows(dwThreadID) now. Then the enumeration will only return windows that belong to the first thread of the realPlayer.

>> and btw, if a "function like PidToWnd or hProcessToWnd"
>> existed i would have loved to receive  a list of HWNDs.
>> (kinda like a TStringList !). That would have been nice, but
>> one cannot expect such nice things from Microsoft, i guess.
This is what my unit "enumStuff" does, but only for Delphi4...  :-(

>> Right now the only way I could come up with is to track the
>> caption of the RealPlayer  (btw, Is there a better way here
>> ?) to tell me when the first file is done.
Hmmm. I can tell you how you can wait until the realPlayer is being terminated. But I think, that wouldn't help, would it?

>> How do i make sure that "ShellExecEx" waits until
>> "RealPlayer" is started ?
Use WaitForInputIdle(hProcess,...)

More questions?

Regards, Madshi.
Hey, Madshi knows alot more than me about this kind of stuff then I do.  You should give him the points, he deserves them more than me.  I am getting there though Madshi :)

Heath
Hi Heath, thank you...   :-)))

Regards, Madshi.
> I can tell you how you can wait until the realPlayer is being terminated.
> But I think, that wouldn't help, would it?

no. when RealPlayer does not Termintae after it finishes playing the RA file. so i cannot use the Exit Codes & the STILL_ACTIVE state.


> WaitForInputIdle(hProcess,...)
I hope i can use the "hProcess" i get from ShellExecEx (or CreateProcess) as the argument for this function.  Can I ? (if you know a qucik answer then let me know. - i'll try this out tonight)
 
> More questions?
Hope you're not being sarcastic here. if you think i'm asking too many questions for the points i have allocated then please let me know. i'll raise the stakes !!

If you were serios, then here is a question that ponders me. Right now i have a timer in my programs and i check the caption of the RealPlayer every second.  - i don't think it can get any lousier than this :-(

I would like to know whether I can use a "MessageHook" and have RealPlayer Callback my MessageProc when it changes its Caption ?  I have fiddled around with KBHooks, JournalHooks and ShellHooks, but never tried any MessageHooks. Wanna get me going ?

Heath:
I'll award points for both of you at the end.
>> no. when RealPlayer does not Termintae after it finishes
>> playing the RA file. so i cannot use the Exit Codes & the
>> STILL_ACTIVE state.
Then you'll have to look at the caption...  :-(

>> I hope i can use the "hProcess" i get from ShellExecEx (or
>> CreateProcess) as the argument for this function.  Can I ?
>> (if you know a qucik answer then let me know. - i'll try this
>> out tonight)
Yes, you can!!!
 
>> > More questions?
>> Hope you're not being sarcastic here. if you think i'm asking
>> too many questions for the points i have allocated then
>> please let me know. i'll raise the stakes !!
It wasn't meant sarcasticly...  :-)

>> If you were serios, then here is a question that ponders me.
>> Right now i have a timer in my programs and i check the
>> caption of the RealPlayer every second.  - i don't think it
>> can get any lousier than this :-(
>> I would like to know whether I can use a "MessageHook" and
>> have RealPlayer Callback my MessageProc when it changes its
>> Caption ?  I have fiddled around with KBHooks, JournalHooks
>> and ShellHooks, but never tried any MessageHooks. Wanna get
>> me going ?
Yes, try SetWindowsHookEx with WH_GetMessage. That should work. However, you must put your callback function into a little dll...

Regards, Madshi.
P.S: You really don't need to raise the points...  :-)
Adjusted points to 200
Madshi,

i tried a hook with WH_GetMessage without much success.

i tried to post a message to my app (don't know whether this is a good idea).
after filtering them in my DLL. Here goes my code:

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

begin
if(nCode < 0) then  Result := CallNextHookEx(0, nCode, wp, lp)
else
   begin;
   M := @lp;
   if (M^.Message = WM_SetText) then
   PostMessage(FindWindow(MainAppClassName, nil),
                            MainAppMessageNumber, MainAppMessageID, lp);
   Result := CallNextHookEx(0, nCode, wp, lp);
   end;
end;

========

But i did NOT get any messages in my program.
so i removed the  
     if (M^.Message = WM_SetText) then
line in the DLL. (thus sent all the messages) & tried to filter them in the EXE.

In this case I get the messages, but the results were devastating !! -This crashes the system - probably due to stack overflow)

Got any ideas ? what can i do to see when another Window changes the Caption ?

Thx for your time.

PS: talking to myself, "hmm..  now that i have raised the points i'm entitled to ask a few more questions"  :-)


Try this one:

function MsgHookProc(nCode: integer; wp: WPARAM; lp: LPARAM): LRESULT; stdcall;
var msg : PMsg;
begin
if(nCode < 0) then  Result := CallNextHookEx(0, nCode, wp, lp)
else
   begin;
   msgM:=pointer(lp);  // the "@" was wrong!
   if (msgM^.Message = WM_SetText) then
   PostMessage(FindWindow(MainAppClassName, nil),
                            MainAppMessageNumber, MainAppMessageID, lp);
   Result := CallNextHookEx(0, nCode, wp, lp);
   end;
end;

I've not tested it (since I'm at work now), but it *should* work.

When you leave the line "if M^.Message=WM_SetText then" away, this will happen:

Your Application's window gets e.g. a WM_PAINT message. The hook gets this message. The hook calls "PostMessage" to post this message to the Application's window. But before this "PostMessage" message comes to the Application's window, it runs through the hook again. So now you have a message dead lock!

Regards, Madshi.
ok. i understand why the @lp is wrong.
But  I'm not getting any messages even with M := pointer(lp)
===========================================================
Here goes my Msg handler for my message "MainAppMessageNumber"
---------------
procedure TMsgHookForm.WMUser_DLLMsg(var Message : TMessage);

var l : longint;
    M : PMsg;

begin
M := pointer(Message.lparam);
if M.Message = 0 then exit;

if Message.wParam = MainAppMessageID then
   begin;
   Memo1.Lines.Add('Handle: ' + IntToHex(M^.HWND, 4));
   Memo1.Lines.Add('        ' + IntToStr(M^.message));
   Memo1.Lines.Add('        ' + IntToStr(M^.wparam));
   Memo1.Lines.Add('        ' + IntToStr(M^.lparam));
   Memo1.Lines.Add('        (' + IntToStr(M^.pt.x) + ' , ' + IntToStr(M^.pt.y) + ')');
   end;
end;

------------
and here's the DLL code:

   M := pointer(lp);
   if (M^.Message = WM_SetText) then
   PostMessage(FindWindow(MainAppClassName, nil),
               MainAppMessageNumber, MainAppMessageID, lp);
   Result := CallNextHookEx(0, nCode, wp, lp);

In the moment I'm at work. So I can't really test what you need. If we can't get it to work, please wait until Friday, then I can test it completely at home...

Hmmm. Could you please change your dll's code to this, just to find out if your hook procedure is called at all.

  if M^.Message=WM_SetText then beep;
  result:=CallNextHookEx(0,nCode,wp,lp);

Another thing. You'll get problems with transporting the lp to your application. The problem is that when e.g. the caption of WinWord changes, your dll hook gets this message, but in this moment your dll is loaded in the memory/address context of winword. If you now simply send the pointer to a data block (that is valid in winword's memory/adress context) to your application, the pointer will NOT be valid in your application's memory/address context.
So either you can only transport a 4 byte value through lParam (e.g. the window handle), or you have to send the WM_COPYDATA message (instead of WMUser_DLLMsg) to your application. Please look at the documentation of WM_COPYDATA. But I would suggest that you only send the window handle. You can get all other parameters you need with API functions.

Regards, Madshi.
Madshi, i'll wait until you get some time to check this out. btw, i have noticed a weird behaviour.

if i filter for Mouse DBLCLICK (instead of SetTEXT) then my program gets the message. i.e if i use the following code in the DLL then my program receives the mouse left button double click messages.

if M^.Message=WM_LBUTTONDBLCLICK  then
PostMessage(FindWindow(MainAppClassName, nil),
                    MainAppMessageNumber, MainAppMessageID, lp);

Thanks for pointing out the trouble with passing "lp". Now i know why my values didn't make any sense when i got the values for mouse dblclick :-)  i'll look into COPYDATA as well.

i'll try to "Beep" with SetTEXT and let you know.
i got copydata working. but i do not receive WM_SetText, WM_GetText  messages. (i receive all the Mouse messages though).

Looks like i get "some" of the WM_SetText messages if i hook using the WH_CALLWNDPROC.  but i still don't get all of them.

a few things i'm wondering about:
Is there a "set" of messages that WH_GetMESSAGE will not get ?
Also will there be a difference  when  the app gets a message via "sendMessage"  or via "PostMessage".

Maybe WH_GETMESSAGE gets only the "Post"ed messages and not the "Send" ones ? Could that be the reason why i'm missing the WM_SetTexts ?
>> Is there a "set" of messages that WH_GetMESSAGE will not get ?
Don't think so. This would be documented somewhere.

>> Also will there be a difference  when  the app gets a message via "sendMessage"  or via "PostMessage".
Maybe WH_GETMESSAGE gets only the "Post"ed messages and not the "Send" ones ? Could that be the reason why i'm missing the WM_SetTexts ?
 
Don't think so, either. The GetMessage hook runs at the receiver side of the story. So it doesn't matter much, if the message is waiting for a result or not.
 
Could you please post your latest code? Then I will have a look at it. Have you tested the beep?

Regards, Madshi.
Madshi,

the BEEP did not work.  here is my hook & message handler code. i can ZIP up and send you the dfm & pas files if you think that would help to sort this out. [i have a Memo & four EditBoxes in my form to display the info received through WM_COPYDATA]

Thanks for your time.

================================================
//this is the hook in the DLL.
//var definitions in the DLL as as follows:
var M : ^TMsg;
    P : PCopyDataStruct;
    D : TCopyDataStruct;
    h : HWND;

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

var M : ^TMsg;
    D : TCopyDataStruct;
    h : HWND;

begin
if(nCode < 0) then Result := CallNextHookEx(0, nCode, wp, lp);
else
 begin;
 M := pointer(lp);
 h := FindWindow(MainAppClassName, nil);

 if h <> M^.hwnd then  //if from my app then ignore.
    try
    D.dwData := M^.HWND;
    D.cbData := SizeOf(TMsg);
    D.lpData := pointer(M);

    if (M^.Message = WM_SetText) then
    //   if not ((M^.Message > WM_MouseFirst) and  (M^.Message < WM_MouseLast)) THEN
         SendMessage(FindWindow(MainAppClassName, nil),
                     WM_CopyData, M^.HWND, LongInt(@D));
 
    except on Exception do
      beep
      end;
   Result := CallNextHookEx(0, nCode, wp, lp);
   end;
end;


================================================
//this is the message handler in the EXE.

procedure TMsgHookForm.WMCopyData(var Message : TMessage);

var M : PMsg;
    P : PCopyDataStruct;

begin
P := pointer(Message.lparam);
M := pointer(P^.lpData);

Memo1.Lines.Add('Handle: ' + IntToHex(M^.HWND, 4));
Memo1.Lines.Add('   Msg: ' + IntToStr(M^.message) + '[' + IntToHex(M^.Message, 4) +']');
Memo1.Lines.Add('   WP : ' + IntToStr(M^.wparam));
Memo1.Lines.Add('   LP : ' + IntToStr(M^.lparam));
Memo1.Lines.Add('   pt :(' + IntToStr(M^.pt.x) + ' , ' + IntToStr(M^.pt.y) + ')');

Edit1.text := IntToStr(M^.pt.x);
Edit2.text := IntToStr(M^.pt.y);
Edit3.text := IntToStr(M^.HWND);
Edit4.text := IntToStr(M^.Message);
end;

Hmmm. It seems that you're right somehow. I could not get it to work with WH_GetMessage, either. I love Microsoft...  :-(((
But with using WH_CallWndProc (you are right there too) you get most of the caption changes. However (you are right there too :-), not all. Don't know why. Perhaps some are ownerdrawn (without using WM_SETTEXT/SetWindowText).
So you'll have to check whether you get the RealPlayer caption changes...
Here's my last code. Perhaps it helps:

// Main app
procedure TForm1.WMCopyData(var Message: TMessage);
var P: PCopyDataStruct;
begin
  P:=pointer(Message.lparam);
  Memo1.Lines.Add('Handle: '+IntToStr(P^.dwData)+'; new caption: "'+pchar(P^.lpData)+'"');
end;

// dll
function MsgHookProc(nCode: integer; wp: wParam; lp: lParam) : LRESULT; stdcall;
var M : PCWPStruct;
    D : TCopyDataStruct;
    h : HWND;
begin
  if nCode>=0 then begin
    M:=pointer(lp);
    if M^.Message=WM_SETTEXT then begin
      h:=FindWindow(MainAppClassName,nil);
      if h<>M^.hwnd then  //if from my app then ignore.
        try
          D.dwData:=M^.HWND;
          D.cbData:=strLen(pchar(M^.lParam));
          D.lpData:=pointer(M^.lParam);
          SendMessage(h,WM_CopyData,M^.Hwnd,integer(@D));
        except beep end;
    end;
    result:=CallNextHookEx(0,nCode,wp,lp);
  end else result:=CallNextHookEx(0,nCode,wp,lp);
end;

You have implemented the WM_CopyData correctly. But you've missed one point: You CAN send the whole ^TMsg structure, that works. But in the ^TMsg structure, the lParam is again a pointer to a string. So you transported the structure correctly, but the string pointer is still invalid. So what I did is only transporting the string in WM_CopyData's lpData and the window handle in the dwData field. I think these both values are all that you need, or am I wrong?

Regards, Madshi.
ok. i guess that's all we could try. (btw, i love Micro$oft as much as you do :-)
Looks like i'm doing better with WH_CALLWNDPROC (or CallWndProcRet) than with WH_GetMessage.  Thanks for all your time.

btw, a pascal question: When we declare a varible of Pointer type, should we allocate space for that using new ? (or does the compiler do it? )

give me a dummy answer so that i can award you the points.


ASKER CERTIFIED SOLUTION
Avatar of Madshi
Madshi

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
my question was in regard to the DLL i was writing.

i use a variable called
var M : ^TCWPStruct;

so that in my hook proc. i can assign the LPARAM to this variable:
M := pointer(lp);  //and then get the handle by
h := M^.hwnd;

My question is: In my initialization routine in the DLL (after the main "begin") should i allocate space to M by using new(M) ?

in the same token, what shd i do when i declare a variable of type "Pchar" ? Can i use it without allocating memory for it ?
oh.. btw thanks for all your info.  
You have to be careful with this allocating issue. Always read the documentation very carefully.
Sometimes you must give a buffer into an API in that way that the API fills the buffer. In most cases YOU have to allocate this buffer before calling the API (and deallocate it after using it). This is always the case when the documentation says nothing about the buffer you have to give in.
Sometimes the API allocates the buffer for you. In the latter case sometimes you don't need to deallocate the buffer, sometimes you have to. (This is always described in the documentation.)
In this special case (dll hook / TCWPStruct), the documentation says that lParam is a pointer to a TCWPStruct. That means the structure is already allocated and filled somewhere. The line "M:=pointer(lp)" does nothing but converting the pointer TYPE. No data is copied/moved. You could also write "h:=PCWPStruct(lp)^.hwnd", since "PCWPStruct=^TCWPStruct". You see what is happening here? It's only a type conversion, to tell Delphi that "lp" is a pointer to a TCWPStruct. So if you would allocate M and then would write "M:=pointer(lp)", M would first point to the newly allocated buffer at (e.g.) $45678901, then it would be just set to where lp points to, (e.g.) $12345678. So the allocated buffer would simply be lost. Do you understand that?

The PChar stuff is as difficult as the buffer/pointer stuff. You must always read the documentation very carefully. If you give in a (pchar) buffer, that the API fills, you have to allocate the pchar before calling the API. In other cases you must not allocate it. It's difficult. If you have a situation, where you are not sure: Just ask me...   :-)

Regards, Madshi.
got some idea. thx.

will come back to you when i have other questions :-)

Yes, do that...  :-)