Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Callbacks, Threads, and MFC

0.00/5 (No votes)
16 May 2000 1  
Learn how to use callbacks and threads with MFC.

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