How to access the UI controls from another thread?

I want to access the UI controls from another thread, I have read in this article (intended for .NET) that SendMessage() is problematic:

http://www.codeproject.com/Articles/10311/What-s-up-with-BeginInvoke

So should I use PostMessage()?
Complicated topic Steve. MSDN's writeups on the various Win Api functions oftentimes mentions thread considerations. In general though, I wouldn't eliminate the possibility of using SendMessage(). What the real issue is - is that if something is blocking a GUI thread from processing messages, then SendMessage() will cause a 'hang'. You really need to experiment with your code to see what works and what doesn't. And study the issue!
The way I designed my program prevents a deadlock from happening. I meant is using SendMessage() inherently wrong to access UI controls from another thread?

Also when reading a UI control content from another thread, I don't see what other way to do it without SendMessage(), for example (pseudo-code):

1
2
3
4
/* Thread A */

// Get content of hEdit into str
// DeleteFile(str) 


So if I used PostMessage() (which is asynchronous), then I could reach DeleteFile(str) before the content of hEdit are placed into str.

So should I use PostMessage()?


I'm not sure that would help. In either case DeleteFile(str) could be called before str was set.
I'm not sure that would help. In either case DeleteFile(str) could be called before str was set.


When I want to call PostMessage(), I actually use it to send a user-defined message telling the UI thread to call SendMessage() with WM_SETTEXT, and then I wait on an event, and when the UI thread receives this user-defined message and call SendMessage(), it signals the event so that my other thread wakes up.

So I am not calling PostMessage() directly with WM_SETTEXT from my other thread.
Last edited on
Well, then that should work. I was going to recommend that. I'd create an event then use something like WaitForSingleObject() to determine when str finally had in it what you wanted.
Well, then that should work. I was going to recommend that. I'd create an event then use something like WaitForSingleObject() to determine when str finally had in it what you wanted.


Doing it this way is just imitating the way SendMessage() works, so do you know why SendMessage() should not be used instead?
I decided to drop what I'm doing and play around with this a little Steve. What I just did was create an app with two GUI Windows but they are both in seperate threads. Right at the outset that sets up a potentially problematic situation.

What the app looks like is this. The main or 1st window has an edit control and a button on it. In the edit control I put ....

"Here Is Some Hello, World! Like Text"

The caption of the button under it says "Create Second Gui Window". When the button is clicked I call CreateThread() and the name of the ThreadProc is "SecondThreadProc". In that thread I call CreateWindowEx() to create a new GUI Window of a class I previously registered as "Second_Thread". Right after the CreateWindow() call to create and show that window I have my second message pump for the seperate thread. So in other words, I've got two message pumps working - one for each GUI thread. The second GUI thread window has only one control on it which is a button which reads "Retrieve Text", and when that button is clicked I try to do what you mentioned at first, which was retrieve the text out of a control on the other window which is in another thread. What I did in that button click code was just call GetWindowText() on the edit control with the text line in it. That would make a SendMessage() call to get the text. My guess as to what would happen when I clicked the button was that I'd end up without the text, because I figurred there could be a thread switch away from my code to retrieve the text from the other thread, and before it showed up in my button click procedure in the 2nd thread. But in all the tests I made it worked perfectly, at least in this instance, although I'm not sure I'd want to bet my life on it working in every possible instance. A bit later I'll post what code I have and perhaps you can try it and see if it works on your box.

What I really want to do is set it up exactly "by the books", which, in my mind, is a bit complicated. Let me describe what I perceive as potential issues. First, a control on the 1st or initial GUI object/window which contains text to be retrieved is of a different Window Class from the main window's Window Class, such as for example, the internal Window Class for an edit control. And it would be that internal window class which will receive a WM_GETTEXT message when an attempt is made either through GetWindowText() or Post/SendMessage() for the text. So to get at that message handler one would need to initially subclass the edit control (if that is what one is attempting to retrieve the text from) so as to intercept its WM_GETTEXT message. Within that 'intercepted' message code one could foreward the message on to its default window procedure which would cause the text to be copied to the pointer you've provided. After that process returns one could then signal an EVENT previously created, i.e., turn it 'on', and back in the other thread which is waiting for the signal through a call to WaitForSingleObject() one could then read the text which should be there.

That's my idea anyway. I'm not an expert at threads, but I do successfully use them occasionally. What I've learned is one can't always be sure what is going to happen before one tries it. In a bit I'll post what I have so far.
This code seems to work. In rethinking the issue, I think we've both been making it more complicated than it is. Run this and see what you think. I developed it in Code::Blocks using MinGW x64, but I expect it ought to compile/run OK in VC too...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//Main.h
#ifndef Main_h
#define Main_h

#define IDC_EDIT              1500
#define IDC_BUTTON            1505
#define IDC_RETRIEVE_TEXT     1600
#define dim(x)                (sizeof(x) / sizeof(x[0]))

struct WndEventArgs
{
 HWND                         hWnd;
 WPARAM                       wParam;
 LPARAM                       lParam;
 HINSTANCE                    hIns;
};

long fnWndProc_OnCreate       (WndEventArgs& Wea);
long fnWndProc_OnCommand      (WndEventArgs& Wea);
long fnWndProc_OnDestroy      (WndEventArgs& Wea);

struct EVENTHANDLER
{
 unsigned int                 iMsg;
 long                         (*fnPtr)(WndEventArgs&);
};

const EVENTHANDLER EventHandler[]=
{
 {WM_CREATE,                  fnWndProc_OnCreate},
 {WM_COMMAND,                 fnWndProc_OnCommand},
 {WM_DESTROY,                 fnWndProc_OnDestroy}
};
#endif 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//Main.cpp
#ifndef UNICODE
    #define UNICODE
#endif
#ifndef _UNICODE
    #define _UNICODE
#endif
#include <windows.h>
#include <cstdio>
#include "Main.h"


LRESULT CALLBACK fnSecondThread_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 switch(msg)
 {
   case WM_CREATE:
     {
        CREATESTRUCT* pCreateStruct = (CREATESTRUCT*)lParam;
        HWND hMain                  = (HWND)pCreateStruct->lpCreateParams;
        HINSTANCE hIns              = ((LPCREATESTRUCT)lParam)->hInstance;
        SetWindowLongPtr(hWnd,0*sizeof(void*),(LONG_PTR)hMain);
        CreateWindowEx(0,L"button",L"Retrieve Text",WS_CHILD|WS_VISIBLE,50,40,200,30,hWnd,(HMENU)IDC_RETRIEVE_TEXT,hIns,0);
        return 0;
     }
   case WM_COMMAND:
     {
        if(LOWORD(wParam)==IDC_RETRIEVE_TEXT && HIWORD(wParam)==BN_CLICKED)
        {
           HWND hMain=(HWND)GetWindowLongPtr(hWnd,0*sizeof(void*));
           HWND hEdit=GetDlgItem(hMain,IDC_EDIT);
           wchar_t szBuffer[128];
           GetWindowText(hEdit,szBuffer,127);
           MessageBox(hWnd,szBuffer,L"Text From Edit Control...",MB_OK);
        }
        return 0;
     }
 }

 return (DefWindowProc(hWnd, msg, wParam, lParam));
}



DWORD _stdcall SecondGuiThread(LPVOID pVoid)
{
 MSG messages;

 HWND hSecond=CreateWindowEx(0,L"Second_Thread",L"Second Thread Window",WS_OVERLAPPEDWINDOW|WS_VISIBLE,675,275,320,150,HWND_DESKTOP,0,GetModuleHandle(NULL),pVoid);
 while(GetMessage(&messages,hSecond,0,0)>0)
 {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
 }

 return TRUE;
}



long fnWndProc_OnCreate(WndEventArgs& Wea)
{
 wchar_t szClassName[]=L"Second_Thread";
 WNDCLASSEX wc;

 Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
 CreateWindowEx(WS_EX_CLIENTEDGE,L"edit",L"Here Is Some Hello, World Like Text!",WS_CHILD|WS_VISIBLE,25,25,250,30,Wea.hWnd,(HMENU)IDC_EDIT,Wea.hIns,0);
 CreateWindowEx(0,L"button",L"Create Second Thread",WS_CHILD|WS_VISIBLE,50,90,200,30,Wea.hWnd,(HMENU)IDC_BUTTON,Wea.hIns,0);
 memset(&wc,0,sizeof(wc));
 wc.lpszClassName=szClassName;                wc.lpfnWndProc=fnSecondThread_WndProc;
 wc.cbSize=sizeof (WNDCLASSEX);               wc.hInstance=Wea.hIns;
 wc.hCursor=LoadCursor(NULL,IDC_ARROW),       wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;
 wc.cbWndExtra=sizeof(void*);
 RegisterClassEx(&wc);

 return 0;
}



long fnWndProc_OnCommand(WndEventArgs& Wea)
{
 if(LOWORD(Wea.wParam)==IDC_BUTTON && HIWORD(Wea.wParam)==BN_CLICKED)
 {
    DWORD lpThreadId=0;
    HANDLE hThread=CreateThread(NULL,0,SecondGuiThread,Wea.hWnd,0,&lpThreadId);
    if(hThread)
    {
       SetWindowLongPtr(Wea.hWnd,0*sizeof(void*),(LONG_PTR)hThread);
       SetWindowLongPtr(Wea.hWnd,1*sizeof(void*),(LONG_PTR)lpThreadId);
    }
 }

 return 0;
}



long fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 PostQuitMessage(0);
 return 0;
}



LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;

 for(unsigned int i=0; i<dim(EventHandler); i++)
 {
     if(EventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(Wea);
     }
 }

 return (DefWindowProc(hwnd, msg, wParam, lParam));
}



int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 wchar_t szClassName[]=L"Threads8";
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 memset(&wc,0,sizeof(wc));
 wc.lpszClassName=szClassName;                wc.lpfnWndProc=fnWndProc;
 wc.cbSize=sizeof (WNDCLASSEX);               wc.hInstance=hIns;
 wc.hCursor=LoadCursor(NULL,IDC_ARROW),       wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;
 wc.cbWndExtra=2*sizeof(void*);
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,275,475,320,200,HWND_DESKTOP,0,hIns,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
 }

 return messages.wParam;
}



Its only a 19 k exe with MinGW. And it seems to work. Look in fnSecondThread_WndProc() where I'm using GetWindowText() to get the text into szBuffer[] from the other thread. That call isn't asychronous and neither is SendMessage(), so they have to return before the MessageBox() call. And it shouldn't matter how many thread switches there might be while those calls are executing, MessageBox() won't get called until they return.
Last edited on
I reworked the above program to work with PostMessage(). It was a bit of a trial, but I finally got it. If you replace my GetWindowText() call in the above code with ...

 
PostMessage(hWnd,WM_GETTEXT,(WPARAM)127,(LPARAM)szBuffer);


... it'll fail. As you first noted PostMessage() is an asychronous call. When the call is made the message is posted to the message qenue (don't know how to spell that - never did; have tried to learn, but I can't. Its unlearnable for me), the text isn't yet in the buffer MessageBox() reads, and there's no output.

So I did the synchronization stuff and got it to work finally more or less as I thought it would, with the exception that WaitForSingleObject() won't work (at least not for me). Had to use MsgWaitForMultipleObjectsEx() instead. Here's the code...


(uses same header as above post)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
//Main.cpp
#ifndef UNICODE
    #define UNICODE
#endif
#ifndef _UNICODE
    #define _UNICODE
#endif
#include <windows.h>
#include <cstdio>
#include "Main.h"


LRESULT CALLBACK fnSecondThread_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 switch(msg)
 {
   case WM_CREATE:
     {
        CREATESTRUCT* pCreateStruct = (CREATESTRUCT*)lParam;
        HWND hMain                  = (HWND)pCreateStruct->lpCreateParams;
        HINSTANCE hIns              = ((LPCREATESTRUCT)lParam)->hInstance;
        SetWindowLongPtr(hWnd,0*sizeof(void*),(LONG_PTR)hMain);
        CreateWindowEx(0,L"button",L"Retrieve Text",WS_CHILD|WS_VISIBLE,50,40,200,30,hWnd,(HMENU)IDC_RETRIEVE_TEXT,hIns,0);
        return 0;
     }
   case WM_COMMAND:
     {
        if(LOWORD(wParam)==IDC_RETRIEVE_TEXT && HIWORD(wParam)==BN_CLICKED)
        {
           HWND hMain=(HWND)GetWindowLongPtr(hWnd,0*sizeof(void*));
           HWND hEdit=GetDlgItem(hMain,IDC_EDIT);
           wchar_t szBuffer[128];
           HANDLE hEvent=(HANDLE)GetWindowLongPtr(hMain,3*sizeof(void*));
           PostMessage(hEdit,WM_GETTEXT,(WPARAM)127,(LPARAM)szBuffer);
           if(hEvent)
           {
              DWORD dwWait=0;
              dwWait=MsgWaitForMultipleObjectsEx(1,&hEvent,INFINITE,QS_ALLINPUT,0);
              if(dwWait>=WAIT_OBJECT_0 && dwWait<=1)
                 GetWindowText(hEdit,szBuffer,127);
              ResetEvent(hEvent);
           }
           MessageBox(hWnd,szBuffer,L"Text From Edit Control...",MB_OK);
        }
        return 0;
     }
 }

 return (DefWindowProc(hWnd, msg, wParam, lParam));
}


DWORD _stdcall SecondGuiThread(LPVOID pVoid)
{
 MSG messages;

 HWND hSecond=CreateWindowEx(0,L"Second_Thread",L"Second Thread Window",WS_OVERLAPPEDWINDOW|WS_VISIBLE,675,275,320,150,HWND_DESKTOP,0,GetModuleHandle(NULL),pVoid);
 while(GetMessage(&messages,hSecond,0,0)>0)
 {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
 }

 return TRUE;
}


LRESULT __stdcall fnEditControl_WndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 HANDLE hEvent=NULL;
 LRESULT lReturn=0;
 HWND hMain=NULL;

 if(msg==WM_GETTEXT)
 {
    hMain=GetParent(hwnd);
    hEvent=(HANDLE)GetWindowLongPtr(hMain,3*sizeof(void*));
    lReturn=CallWindowProc((WNDPROC)GetWindowLongPtr(hMain,2*sizeof(void*)), hwnd, msg, wParam, lParam);
    BOOL blnSetEvent=SetEvent(hEvent);
 }
 else
 {
    hMain=GetParent(hwnd);
    lReturn=CallWindowProc((WNDPROC)GetWindowLongPtr(hMain,2*sizeof(void*)), hwnd, msg, wParam, lParam);
 }

 return lReturn;
}


long fnWndProc_OnCreate(WndEventArgs& Wea)
{
 wchar_t szClassName[]=L"Second_Thread";
 WNDPROC fnEditProc=NULL;
 HANDLE hEvent=NULL;
 HWND hCtl=NULL;
 WNDCLASSEX wc;

 Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
 hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,L"edit",L"Here Is Some Hello, World Like Text!",WS_CHILD|WS_VISIBLE,25,25,250,30,Wea.hWnd,(HMENU)IDC_EDIT,Wea.hIns,0);
 fnEditProc=(WNDPROC)SetWindowLongPtr(hCtl,GWLP_WNDPROC,(LONG_PTR)fnEditControl_WndProc);
 SetWindowLongPtr(Wea.hWnd,2*sizeof(void*),(LONG_PTR)fnEditProc);
 hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
 SetWindowLongPtr(Wea.hWnd,3*sizeof(void*),(LONG_PTR)hEvent);
 CreateWindowEx(0,L"button",L"Create Second Thread",WS_CHILD|WS_VISIBLE,50,90,200,30,Wea.hWnd,(HMENU)IDC_BUTTON,Wea.hIns,0);
 memset(&wc,0,sizeof(wc));
 wc.lpszClassName=szClassName;                wc.lpfnWndProc=fnSecondThread_WndProc;
 wc.cbSize=sizeof (WNDCLASSEX);               wc.hInstance=Wea.hIns;
 wc.hCursor=LoadCursor(NULL,IDC_ARROW),       wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;
 wc.cbWndExtra=sizeof(void*);
 RegisterClassEx(&wc);

 return 0;
}


long fnWndProc_OnCommand(WndEventArgs& Wea)
{
 if(LOWORD(Wea.wParam)==IDC_BUTTON && HIWORD(Wea.wParam)==BN_CLICKED)
 {
    DWORD lpThreadId=0;
    HANDLE hThread=CreateThread(NULL,0,SecondGuiThread,Wea.hWnd,0,&lpThreadId);
    if(hThread)
    {
       SetWindowLongPtr(Wea.hWnd,0*sizeof(void*),(LONG_PTR)hThread);
       SetWindowLongPtr(Wea.hWnd,1*sizeof(void*),(LONG_PTR)lpThreadId);
    }
 }

 return 0;
}


long fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 HANDLE hEvent=NULL;

 PostQuitMessage(0);
 hEvent=(HANDLE)GetWindowLongPtr(Wea.hWnd,3*sizeof(void*));
 CloseHandle(hEvent);

 return 0;
}


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;

 for(unsigned int i=0; i<dim(EventHandler); i++)
 {
     if(EventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(Wea);
     }
 }

 return (DefWindowProc(hwnd, msg, wParam, lParam));
}


int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 wchar_t szClassName[]=L"Threads7";
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 memset(&wc,0,sizeof(wc));
 wc.lpszClassName=szClassName;                wc.lpfnWndProc=fnWndProc;
 wc.cbSize=sizeof (WNDCLASSEX);               wc.hInstance=hIns;
 wc.hCursor=LoadCursor(NULL,IDC_ARROW),       wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;
 wc.cbWndExtra=4*sizeof(void*);
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,275,475,320,200,HWND_DESKTOP,0,hIns,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
 }

 return messages.wParam;
}
You know, I read that article you posted a link to, and that's what convinced me to try the PostMessage() instead of SendMessage(), but I'm still not convinced its all necessary - the PostMessage() I mean. I've read tons of that kind of stuff over the years about how all kinds of bad things can happen in multi-threaded apps, but I've simply never experienced any of it. I do have one mission critical major app which uses multithreading here where I work, and it breaks all the rules put out by articles such as the one you posted. That app has been tested every which way but loose and it has NEVER failed. Not for me, and not for any of its users. And it has been used for years, on lots of different machines. So I tend to take articles like that with a 'grain of salt'.
You know, I read that article you posted a link to, and that's what convinced me to try the PostMessage() instead of SendMessage(), but I'm still not convinced its all necessary - the PostMessage() I mean. I've read tons of that kind of stuff over the years about how all kinds of bad things can happen in multi-threaded apps, but I've simply never experienced any of it. I do have one mission critical major app which uses multithreading here where I work, and it breaks all the rules put out by articles such as the one you posted. That app has been tested every which way but loose and it has NEVER failed. Not for me, and not for any of its users. And it has been used for years, on lots of different machines. So I tend to take articles like that with a 'grain of salt'.


Unfortunately, there is no credible source to know for sure what is safe and what is not! I don't think that MSDN has something on this subject.
To answer your question most directly, I can't imagine why SendMessage() would be considered so unacceptable in this context. In the context of the above app I developed, which is I believe very close to the scenario you described, it has worked perfectly for me on my x64 Windows 7 machine compiled as x64. Haven't tested on anything else. Can't imagine wht it could fail. I've got two message pumps running in two seperate threads. Nothing is blocking either one anywhere. However, the much more complicated PostMessage() code works too, and that was my original thought of how to do it. But upon developing the SendMessage() code and studying it and thinking about it, I can't see where it could fail. I'd love if somebody could show me what alterations were necessary to make it fail.

In terms of documentation on multi-threading, its out there but scattered. I've piles of it on the MSDN CDs that came with the Enterprise Edition of Visual Studio 6. I just never had enough interest to read through all the stuff. The whole issue turns me off so bad I shied away from doing multi-threading for years and years. I must have read dozens of articles and posts exactly like the one you posted where some know it all postulates 'golden rules' which never must be broken or the space time continuom will come unglued and we'll all disappear into nothingness, and when I try it it works perfectly, just like my example above. So I'm at a loss as to why all the hysterics about multi-threading.
Topic archived. No new replies allowed.