6
votes

Background: I am using OmniThreadLibrary to load batch mode ADO stored procedures in the background. I am doing some slightly dodgy stuff by swapping the connection after opening the SP but that seems to be quite reliable. I'm using PostMessage to send messages back to the calling form and that works in my test applications. Primoz' comms channels work for me, I'm using those for inter-thread comms but for our main application I'm trying to avoid that dependency by using standard PostMessage calls as we do elsewhere in the app.

Problem: Unfortunately when I put this into our main application the PostMessage calls in the thread start failing with 1400:invalid window handle.

I have liberally added extra PostMessage calls and logging code to try to locate the problem, but I'm out of ideas now. The code is boilerplate:

const WM_PW_ADLQUEUEEMPTY = WM_USER + 11;
...
if PostMessage (OwnerHandle, WM_PW_ADLPROGRESS, QueueID, 10) then
    pwDebugLog ('TADLQueue.Run WM_PW_ADLPROGRESS send to  ' + IntToHex (OwnerHandle, 8) + ' (IsWindow '+BoolToStr(IsWindow(OwnerHandle),true)+')     OK for Queue ' + IntToStr (QueueID))
else
    pwDebugLog ('TADLQueue.Run WM_PW_ADLPROGRESS send to  ' + IntToHex (OwnerHandle, 8) + ' (IsWindow '+BoolToStr(IsWindow(OwnerHandle),true)+') failed for Queue ' + IntToStr (QueueID));

But the log for a series of calls is not very revealing to me. note that the four hex digits after the time is the thread id from GetCurrentThreadID.

15:41:53.221 1614  TpwAsyncDataLoader.RunQueue WM_PW_ADLPROGRESS send to  00A5110C (IsWindow True)    OK for Queue -6
15:41:53.265 13B4  TADLQueue.Run WM_PW_ADLPROGRESS send to  00A5110C (IsWindow True)     OK for Queue -6
15:41:53.554 13B4  TADLQueueManager.WriteSysErrorMessageToDatabase Postmessage   00A5110C (IsWindow False)  failed with 1400  Invalid window handle

Can anyone shed some light on this? I'm confused at how a window handle can become invalid while I'm looking at it, but that's what it looks like to me.

The one thing I can think of is that the form I'm showing here isn't processing messages and I'm seeing a "message queue full" failure rather than the IsWindow(handle) failure that it looks like. How can I test for that?

2
Based on the comments below regarding recreating the window handle perhaps you could try storing a reference to the TWinControl object rather than the actual window handle. Then your PostMessage call can use aControl.Handle which is more likely to get a genuine and valid handle. That would be my first course of action...shunty
Don't do that, @Shunty. If the control's window has been destroyed but not yet re-created (or not even created at all yet), then accessing its Handle property in another thread will force the window to be allocated. But now it's been allocated in the wrong thread. When dealing with multithreading, "more likely" isn't good enough. You need absolute certainty because it's so hard to test anything.Rob Kennedy
Duh! <slaps head>, okay, fair enough. I admit I didn't think about it being recreated in the wrong thread. Ironically (and fortunately) I already use something along the lines of the AllocateHWnd method you suggest below - but I completely forgot about it!shunty

2 Answers

6
votes

There are cases where a handle gets recreated, most notably when you change window flags. This might be what's happening in your application.

All I found so far about recreating windows handle is this post from Allen Bauer but I'm certain reading a more detailed one written by Peter Below. Unfortunatly I can't seem to find that one.

Finally, you need to be aware of cases where your handle may need to get recreated. This can happen if the surrounding form or the parent component's handle goes through a recreate process. Up until more recent releases of Windows, the only way to change some window flags was to destroy the handle and recreate with new flags in the CreateWindowEx() call. There are many components that still do this. You know if you're in a recreate situation by checking (csRecreating in ControlState).

Edit

Not actually the posts from Peter I had in mind but it might give you some fresh ideas.

The form will not have a handle until you show it the first time (unless something in the form load sequence request the handle) but the handle is not destroyed when you hide the form and unless you do something that forces the form to recreate the handle, e.g. change its border style or border icons, or call RecreateWnd yourself the handle will stay the same.

It may not be desirable but it cannot be avoided, at least not the way Delphi drag&dock is currently implemented. When you dock the dragged form to another form it becomes a control (with WS_CHILD window style) and that means that its window handle has to be destroyed and recreated with the new style. And destroying the window handle of a container control automatically destroys the handles for all child controls as well.

and

There is also the fact that the forms window handle is destroyed and recreated when you assign to its Parent property. This also destroys and recreates the handles for all controls on the form.

0
votes

I had a similar issue (but in VC++2010), and I did not find the solution on any forum, so I post it here, hope this will help:

Issue:

  • Creating a thread,
  • Passing the HWnd handle
  • In the thread, PostMessage throws a 1400 error (invalid handle), although the pointer was equal with the handle as seen from UI thread (with GetSafeHWnd()).

Solution:

  1. Do not pass the handle, but the parent CDialog(Ex) class
  2. This class has a m_hWnd member that will do the job

Here is a (Cpp) example, sorry for the cast mess.

// In the worker thread
ThreadParam *threadParam = (ThreadParam*)param

// This is ugly because my pointer is a void *, to avoid one more forward declaration
CCoreGenDlg *dlg = static_cast<CCoreGenDlg *>(threadParam->ptr);

// Post
bool b = PostMessage(dlg->m_hWnd ,1221,0,(LPARAM)message);

Cheers'