Click here to Skip to main content
Click here to Skip to main content

Callbacks, Threads, and MFC

By , 16 May 2000
 

One of the many problems that seem to plague programmers is the problem of using callback methods. These callbacks are part how the Windows API works, and some are the result of enumerators which provide access to underlying sets of Windows information.

This essay discusses how you can move from the raw API callback domain back to the MFC domain, which greatly simplifies how you actually write the code to handle the callbacks. It will be done primarily in the context of callbacks, with a final example about how to create worker threads in an MFC class.


Callbacks, particularly Enumerators

There are many useful methods that you can use to find out information; for example EnumFontFamiliesEx, EnumWindows, and the like. For some of these, there is no other effective way to enumerate the objects. For others, like EnumWindows, the method is the strongly recommended method for doing the enumeration (EnumWindows, for example, works even when other threads may be creating or deleting windows concurrent with your enumerating them).

The real problem, as seen by many, is that these are raw API functions, which in MFC are ::EnumWindows, ::EnumChildWindows, ::EnumFontFamiliesEx, and so on. What you get is a raw API object, for example, an HWND, and life becomes difficult because you have no access to the instance that did the enumeration or any MFC properties of it. For example, you may want to place the list of fonts in a list box, or the list of windows in a combo box, or something similar.

This often forces the unnecessary use of global variables, and an unnecessary reliance on GetDlgItem, both of which are fundamentally bad ideas (if you wonder about GetDlgItem being bad, see my essay on this topic).

For most enumerators, this can be avoided entirely by using the LPARAM parameter to the call. For example, the specification of several enumerator functions are:

int EnumFontFamiliesEx(HDC, LPLOGFONT, FONTENUMPROC,
                       LPARAM, DWORD);
BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM);
BOOL EnumWindows(WNDENUMPROC, LPARAM);
BOOL EnumThreadWindows(DWORD, WNDENUMPROC, LPARAM);
BOOL EnumMetaFile(HDC, HMETAFILE, MFENUMPROC, LPARAM);
BOOL EnumResourceNames(HMODULE, LPCTSTR,
                       ENUMRESOURCENAMESPROC,
                       LONG /* LPARAM */);
BOOL EnumResoureTypes(HMODULE, ENUMRESOURCETYPESPROC,
                      LONG /* LPARAM */);

Alas, due to what I think is a serious failure in design, there are some enumerators which do not take an LPARAM, such as

EnumDateFormats
EnumDateFormatsEx
EnumTimeFormats

which make life difficult; what is inexcusable here is that the methodology of using an LPARAM was well-known when these functions were designed.

The above are only representative examples. In addition, there are a number of "Enum..." functions that do not use callbacks, and these can be safely ignored for this discussion.

For the sake of our example, we will look at a typical callback enumerator, EnumWindows. Note that the callback techniques I am about to describe apply to all callbacks, whether from enumerators or other sources, to which you can supply an arbitrary LPARAM-style value.

When using EnumWindows, remember that the function you call has no access to your class unless you take specific action to make it so. For example,

EnumWindows(enumwndfn, NULL)

will call the enumerator function, enumwndfn, which must be either an ordinary C function, or a static class member (thus having no access to any instance of the class, such as your CDialog-derived class)

The preferred solution to this is to pass the class instance in by using the LPARAM value:

EnumWindows(enumwndfn, (LPARAM)this)

Add the two declarations to your class:

static BOOL CALLBACK enumwndfn(HWND hWnd, LPARAM lParam);
BOOL enumwndfn(CWnd * wnd);

I often use the same name for both, simplifying the number of concepts I have to deal with; C++ overloading rules helps sort them out. Note carefully the use of the word "static" to qualify the first declaration. The consequence of this is that there is no this parameter implicit in the call, which also means that the method has no implicit access to the class instance.

In the implementation of the static member function do

BOOL CALLBACK CMyDialog::enumwndfn(HWND hWnd, LPARAM lParam)
{
    CMyDialog * me = (CMyDialog *)lParam;
    return me->enumwndfn(CWnd::FromHandle(hWnd);
}

For the nonstatic member, you now have direct access to the objects of the class, for example, a list box c_Windows:

BOOL CMyDialog::enumwndfn(CWnd * wnd)
{
    // full access to class here
    CString s;
    wnd->GetWindowText(s);
    c_Windows.AddString(s);
}

which clearly is more convenient for MFC programming. 

So, you say, "But I really use LPARAM to pass information in! Now what do I do?" The answer is straightforward. The simple answer is: store the information you used to pass via LPARAM in an instance variable of the class. Instead of accessing the lParam variable, you can instead write code as follows:

enumParam = whatever;
EnumWindows(enumwndfn, this);
BOOL CMyDialog::enumwndfn(CWnd * wnd)
{
    if(enumParam == whatever) ...
}

Some of you will now say "Aha! Got you! That technique isn't thread-safe if I have multiple threads doing the enumeration!". Indeed, that is so. So there is a slightly more complex solution.

typedef struct EnumParm {CMyDialog * me; LPARAM lParam;}

then write

EnumParam p;
p.me = this;
p.lParam = whatever;
EnumWindows(enumwndfn, (LPARAM)&p);

and rewrite the handlers to be

BOOL CMyDialog::enumwndfn(HWND hWnd, LPARAM lParam)
{
    EnumParm * p = (EnumParm *)lParam;
    return p->me->enumwndfn(CWnd::FromHandle(hWnd),
                                  p->lParam);
}
BOOL CMyDialog::enumwndfn(CWnd * wnd, LPARAM lParam)
{
    // enumeration handler code here
}

and the problem is solved.


Worker Threads in a class

I use this same technique when using worker threads. I often want the worker thread to operate as a collection of MFC code in the context of my class, although it is not a GUI-based thread. The same casting techniques apply:

static UINT threading(LPVOID p);
void threading();

To start a thread, call

    AfxBeginThread(threading, this);

and the code is

UINT CMyClass::threading(LPVOID p)
{
    CMyClass * me = (CMyClass *)p;
    me->threading();
    return 0;
}
void CMyClass::threading()
{
    // complete class instance access available
}

Summary

Using the raw API for callbacks does not require that you leave the MFC domain and start "hand coding" everything. It is generally easy to switch between the domains, as this essay shows.


The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.

Send mail to newcomer@flounder.com with questions or comments about this article.
Copyright © 1999 CompanyLongName All Rights Reserved.
www.flounder.com/mvp_tips.htm

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Joseph M. Newcomer
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionI get access violationmemberDror Yona4 Mar '09 - 23:00 
Hello all,
 
Trying the following, the application crashes with access violation (vc++ 2005 mfc).
myThread is static.
 

UINT CmyDlg::myThread(LPVOID p)
{
	CmyDlg * me=(CmyDlg *)p;
	while(true)
	{
		me->myDlgPublicFunction();
		Sleep(500);
	}
	return true;
}
 
Thanks for any help Smile | :)
AnswerRe: I get access violationmemberJoseph M. Newcomer6 Mar '09 - 6:06 
Then you must have a bug.
 
Seriously, what do you expect me to make of this?
 
You have not shown how you launch the thread, nor did you say where the access fault occurs, or anything else that could possibly lead to an analysis of the problem.
 
Also, at what point did you fail to understand the difference between "UINT" and "bool"? "return 0" would make sense; "return true" cannot make sense.

QuestionHow to end the thread for the problem belowmembermailtochandra2000@yahoo.com14 Nov '06 - 22:08 
MVXR5000ProcessMonitor.cpp
void MVXR5000ProcessMonitor::Init()
{
for(BYTE bySystemId = CERBERUS_ARRAYCONTROLLER; bySystemId <= CERBERUS_SYS_VIEWS; bySystemId++)
m_arrProcessMonitor[bySystemId ].SetSystemId(bySystemId);
//Added by chandroo at 12:20Am
//HWND hWnd = GetSafeHwnd();
/*commented by chandroo for testing
AfxBeginThread(MonitorAC, &m_arrProcessMonitor[CERBERUS_ARRAYCONTROLLER]);*/
AfxBeginThread(MonitorAC, &m_arrProcessMonitor[CERBERUS_ARRAYCONTROLLER]);
AfxBeginThread(MonitorVP1, &m_arrProcessMonitor[CERBERUS_VIEW_1]);
AfxBeginThread(MonitorVP2, &m_arrProcessMonitor[CERBERUS_VIEW_2]);
AfxBeginThread(MonitorVP3, &m_arrProcessMonitor[CERBERUS_VIEW_3]);
AfxBeginThread(MonitorVP4, &m_arrProcessMonitor[CERBERUS_VIEW_4]);
AfxBeginThread(MonitorVP5, &m_arrProcessMonitor[CERBERUS_VIEW_5]);
}
 
MVXR5000ProcessMonitor.h
 
protected:

static UINT MonitorAC(LPVOID pArg);
static UINT MonitorVP1(LPVOID pArg);
static UINT MonitorVP2(LPVOID pArg);
static UINT MonitorVP3(LPVOID pArg);
static UINT MonitorVP4(LPVOID pArg);
static UINT MonitorVP5(LPVOID pArg);
static UINT WaitForSoftwareShutdownRequest(LPVOID pArg);
 

and in the MVXR5000ProcessMonitor.cpp
 
UINT MVXR5000ProcessMonitor::MonitorAC(LPVOID pArg)
{
ProcessMonitor *pPM = (ProcessMonitor *) pArg;

CoInitializeEx(0, COINIT_MULTITHREADED);

LoadSysSettings(CERBERUS_ARRAYCONTROLLER);

pPM->Init(ProcessStatus, vpSetting.ProcessToMonitor,vpSetting.nProcesses, TRUE, FALSE, TRUE);
 

CoUninitialize();

return 0;

}
 

UINT MVXR5000ProcessMonitor::MonitorVP1(LPVOID pArg)
{
ProcessMonitor *pPM = (ProcessMonitor *) pArg;

CoInitializeEx(0, COINIT_MULTITHREADED);

LoadSysSettings(CERBERUS_VIEW_1);

pPM->Init(ProcessStatus,vpSetting.ProcessToMonitor,vpSetting.nProcesses, TRUE,
FALSE, FALSE, "admin", "mvxr5000", "c2vp1", "workgroup");

CoUninitialize();

return 0;
}
 

MVXR5000ProcessMonitor::~MVXR5000ProcessMonitor()
{

for(int i =0;i
GeneralEnumwindowsmembercaykahve31 Aug '05 - 21:23 
Hi,
 
I'm trying to use EnumWindows like in your article, but cannot figure out what is wrong with my code. After starting some certain applications with ShellExecuteEx() function and getting their process handles in return, on some other event I want to get the CWnd handle to those windows and bring them to the front and resize, for which I use SetWindowPos.
 
How I try to get the mentioned CWnd handle:
- After starting the application, I get the process ID of the handle:
bool CMyDlg::PlaceWnd(HANDLE ProcessHandle)
{
   OpenedThreadPID = GetProcessId(ProcessHandle);   
   
  if(EnumWindows(MyFunc,(LPARAM)this))
  {
      SetWindowPos(&SearchedCwnd->wndTopMost,
        StartX,StartY,Width,Height,
        SWP_NOREPOSITION|SWP_NOSENDCHANGING|SWP_NOZORDER|SWP_SHOWWINDOW
      );
      return true;
   }
   else
      return false;
}
- Then I call EnumWindows function like in your article. and in the non-static function I check the called window's process ID. The message boxes I put in the non static function never appear. What am I doing wrong? BTW, can I also reach the windows which are minimized to tray (notification area)?
BOOL CALLBACK CMyDlg::MyFunc(HWND hWnd, LPARAM lParam)
{
   CMyDlg *me = (CMyDlg *) lParam;   
   return me->MyFunc(CWnd::FromHandle(hWnd));   
}
 
BOOL CMyDlg::MyFunc(CWnd * pWnd)
{
   DWORD FoundThreadPID = 0;      
   GetWindowThreadProcessId(pWnd->m_hWnd,&FoundThreadPID);
   
   if(FoundThreadPID == OpenedThreadPID)
   { 
      SearchedCwnd = pWnd;
      return TRUE;
   }
   return FALSE;
}
Any help appreciated. Thanks in advance
Caykahve
GeneralEnumwindow and callback in c++memberarssa202030 Jul '04 - 7:11 
Hi all,
I would like to send a message to all open Internet Explorer Windows to change thier title (and later, send a string to a text box in that IE window --clicked by a user--
i know that EnumWindows This function enumerates all top-level windows on the screen by passing the handle to each window, in turn, to an application-defined callback function.
in my app i did the following,
 

//Global Variable
HWND handleback=0;
 
BOOL onefunc(HWND);
static BOOL CALLBACK myfunc(HWND Handle,LPARAM lParam)
{
return onefunc(Handle);
}
 
BOOL onefunc(HWND Handle)
{
char array [256];
//HWND hWndIE;
if (Handle > 0)
{
//its valid
//PostMessage(hWndIE ,WM_KEYDOWN,/(WPARAM)Buffer'A',0);
handleback=Handle;
GetWindowText(handleback,array, 256);
//get the window ttile,
if(strstr(array,"Microsoft Internet Explorer")!=NULL)
{
SetWindowText(handleback,"Mywindow1");
//i also want to send character to a user
//clicked textbox in the IE window
PostMessage(handleback,WM_KEYDOWN,'A',0);

}
return true ;
}
}
 
//in main
EnumWindows(&myfunc,NULL);
 
>>However, this doesnt seem to work, it gets stuck (calling myfunc and onefunc repeatidly.
how can i implement this? what is wrong with my CALLBACK function?
 
Regards,
Mo
marabo82@hotmail.com
GeneralRe: Enumwindow and callback in c++memberJoseph M. Newcomer14 Aug '04 - 17:29 
Using a global variable is bad practice. Use the LPARAM variable to pass the handle of the window to receive the message. There is no particular reason to use the separate onefunc, since it seems to be syntactic noise. Just move all the code into myfunc.
 
EnumWindows(myfunc, (LPARAM)this);
 
...
static BOOL CALLBACK myfunc(HWND handle, LPARAM lParam)
{
CWnd * wnd = (CWnd *)lParam;
...
wnd->PostMessage(UWM_FOUND_ONE, (WPARAM)handle);
 
I would let the receiver of the message that the window has been found worry about what to send as a message, and sending a WM_KEYDOWN without the corresponding WM_KEYUP is bad practice. Also, since the WM_KEYDOWN will then be interpreted in the context of the receiving window, the state of the control, alt, and shift keys may not be what you expect. If you want to send a character, send a WM_CHAR message.
 
For example, WM_KEYDOWN of '%' is actually a left-arrow command, because there IS no '%' key. There is a '%' character, code 37, but WM_KEYDOWN of code 37 is VK_LEFT. So 'A' will not send the letter A. It might. It might send 'a'. It might send Alt-A. It might send Ctrl-A. And you probably can't outguess what it will actually send.
 
YOu would not use &myfunc because that is marginal syntax. Just use the name myfunc without the &.
 
The problem here is that you presume the text will say "Microsoft Internet Explorer", so it won't work with other browsers and it won't work for any user who has not selected English as the primary language.
 
It is not Unicode-aware, so it only works for 8-bit compilations. You should use TCHAR and use a #define for the size of the array of characters (which are not required to be 8-bit in general).
 
You say "repeatedly", but do you know how many windows you actually have available to call? In my systems, it is usually hundreds. Use Spy++ to see a list of all the top-level windows. There are a lot. So it is probably behaving correctly.
GeneralNeed helpmemberSilverBrain18 Sep '03 - 15:19 
I need to know how an AsyncMonikerFile object react, and about its async respones.
Is it comparable to threads ??
 
I'v implemented it, and the async responses comes back through a timer with a CALLBACK to
the caller object instance.
 
My implemetation crashes (Access violation) in some of the calls to OnUpdate made by the
AsyncMonikerFile object.
I don't know why. I think that here comes the threads topic.
 
How i can solve this ? Confused | :confused:
 
if you want, i explain it with code.
 
Thanks.
 

I apologizes, it is my first english write. Blush | :O
 

GeneralRe: Need helpmemberJoseph M. Newcomer18 Sep '03 - 17:31 
I've never looked at monikers. However, it sounds definitely like an issue of thread safety, MFC object maps, and similar issues. Key thing here is that the thread which is invoked by a callback from a timer is NOT the thread that implements any of the rest of the code, and therefore, it must not actually do anything that depends on thread context. Calling OnUpdate from another thread is almost certainly going to be a bad idea. Use PostMessage or PostThreadMessage to transfer control from one thread to another. Read my essays on worker threads and UI threads here on codeproject.
GeneralGetting error trying to do very similar thingmembermellertson31 Jul '03 - 13:43 
You seem to be pretty experience code writer. Can you help me with this one? I'm trying to start a worker thread in a class, but I'm getting this error: "error C2665: 'AfxBeginThread' : none of the 2 overloads can convert parameter 1 from type 'unsigned int (__thiscall CScheduler::*)(void *)'"
 
Here's my code:
 
CWinThread* StartSchedulerThread() {
SScheduler Params;

...
// populate Params
 
return AfxBeginThread(CScheduler::ThreadMain, (LPVOID)&Params);
}
 
And then my class member function:
UINT CScheduler::ThreadMain(LPVOID pParam)
{
if(bReload)
{ // Reload the backup jobs file

}
return 0;
}
 

Am I just totally missing the mark here, I can't see the forest for the trees.
 
Thanks much,

 
Mike Ellertson
GeneralRe: Getting error trying to do very similar thingmemberJoseph M. Newcomer31 Jul '03 - 21:27 
Read my essay on threads. I make it clear that a thread must be declared as a static function, which is what you forgot to do. As such, it cannot access any member variables unless they, too, are static, which is almost always a mistake. Your params structure should contain a pointer to the instance of the object you wish to reference, if you need to reference a dynamic instance of a class.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 17 May 2000
Article Copyright 2000 by Joseph M. Newcomer
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid