Introduction

This article shows how to deliver an object to the document in a document/view architecture using the WM_NOTIFY message.

Background

When I write MFC applications, I employ the document-view architecture. In general, the document is a passive storage area which allows you to display information on request or update that information from the view. There are cases where a document abstracts a database or a portion of a database, and it may not be practical to update the view immediately; for instance, if the database is on a remote machine and the connection is slow, then trying to gather the information on the main thread and pass it to the view will lock up the user interface. Locking up the user interface does not make for a pleasant user experience. So, I make my requests on worker threads. This allows the user to continue to interact with the application, and it allows the document to post information back to the user about the status of the request.

The problem is that CDocument is not a window: you cannot post messages from a thread directly to a CDocument object. CDocument is based on CCmdTarget, which is the MFC class that is the basis of message routing. So, how do we get information from the worker thread to the CDocument object?

About a year ago, I read an article in MSDN on command routing, and in that article was this benign flow chart which showed how WM_COMMAND was being routed from the main frame to the active view and, finally, to the document. That same chart showed how WM_NOTIFY took a similar route to the document! I can no longer find that article. In fact, I can't seem to find any trace of any documentation which says that WM_NOTIFY gets passed to the document, save a single sentence in a Microsoft Systems Journal Q&A column from September 1998. Paul DiLascia wrote the following: "Whenever the frame gets a command (WM_COMMAND or WM_NOTIFY message), it routes it to the active view. The view, in turn, routes commands to its document."

About five years ago, Mehdi Mousavi wrote an article for CodeProject, called "Win32 vs. MFC - Part II". While the article does not explicitly deal with WM_NOTIFY, there is a nice little graphic that shows the routing of WM_COMMAND and WM_NOTIFY to the CCmdTarget::OnCmdMsg method.

Well, if you were expecting to add an ON_NOTIFY message map entry to your CDocument-based class, you would be disappointed. I know I was.

Nevertheless, WM_NOTIFY does get to the document. Really!

The following article describes a strategy for notifying the document from a worker thread using WM_NOTIFY, because we also want to pass along the relevant additional information from the worker thread. I will show you how the document gets the WM_NOTIFY message, and how you can access it.

Using the code

I have created a CNotifyObject class which maintains an extended form of the NMHDR structure. Normally, I would also abstract an engine class which separates the document from active content but, for brevity, I have made the document directly responsible for the content.

You may already have run into the NMLISTVIEW structure which extends the NMHDR structure by adding several variables that hold values used by a CListView or CListCtrl window. I extended the NMHDR structure with the NMHDROBJECT structure:

CNotifyObject maintains an NMHDROBJECT structure (called m_hdrObject), and sets m_hdrObject.pObject = this when the object is constructed. The address of m_hdrObject can be pushed into the LPARAM parameter during a call to PostMessage to send the CNotifyObject to the main thread, where it can be distributed to the views.

The following method runs from a thread inside of CNotifierAppDoc. Note how we are getting the main frame window from the CWinApp object and posting the WM_NOTIFY message to it.

You see that last line? CWnd::OnNotify combines WM_NOTIFY with pNMHDR->nCode into a single value, and passes it to the derived class' OnCmdMsg method. CFrameWnd passes it to CView, and CView passes it to CDocument:

Okay, you might say, that's a lot of work just to update a view. Why not just get the active view in the thread and post the notification directly to the view?

Good question. You could do that. But getting the notification object to the document allows you to value-add to the notification before sending it out. Second, if you have multiple views, as in the case of a splitter window project or an MDI project, you will want to update all of the views and not just the active view (which is why I use UpdateAllViews() in the OnCmdMsg method); otherwise, the active view will have the responsibility for updating the inactive views, and you don't really want to write that code for all of your views, do you? It is much better to let the document update them. Finally, I am creating temporary notification objects at will, and there has to be a point where an object that is no longer useful can be deleted (otherwise, watch out for memory leaks). Having the document control the access of the object through UpdateAllViews ensures that each view gets its time with the object (and maintains a copy, if necessary) and the object can be deleted at the end.

Points of interest

I have set up a half second delay before executing the OnInformationThread method, and a two second delay before executing the OnErrorThread method. These can both be found in the CNotifierAppDoc class, and you are free to change them and play with the values.

I am a sucker for copy constructors, so my CNotifyObjects and their subclasses have copy constructors and assignment operators. They are extensible, and allow for deep copying.

Notice that the variable Error is a pointer to a CErrorObject, while Info is a CInformationObject which is a copy of the pNotify object! This means that you can maintain a copy of the object in your view, if you want. You could extend the objects so that they contain a reference to the document so the document can update the objects directly.

Also, in the same code, there is a class called NotifyObjectType, which takes a CNotifyObject derived object and returns a value which can be compared in a switch statement. No more if/else if/else if/else coding! On the downside, it does use RUNTIME_CLASS underneath, which some people find slightly more objectionable than RTTI! You pay your money and you take your choices.

Finally, there is absolutely no reason why you should feel constrained to use CObject as the basis for CNotifyObject; pick your own base class, and run with it. I have, on many occasions, overloaded CDocument::UpdateAllViews to take other type objects as the third parameter (including void*).

Why not send message to main frame of the GUI thread? Simpl pass its HWND handle to the worker thread when you start it and send all messages to it. Then when you recieve a message in the main frame you can easily determine the active view and/or active document and pass the necessary information to it.

Why not send message to main frame of the GUI thread? Simpl pass its HWND handle to the worker thread when you start it and send all messages to it. Then when you recieve a message in the main frame you can easily determine the active view and/or active document and pass the necessary information to it.

This is, essentially, what I am doing. If you look at the code fragment in the method OnInformationThread you will see:

HWND hMain = AfxGetApp()->m_pMainWnd->GetSafeHwnd() ;

This is the main frame window. Rather than write all of the other code to get the active view or document, I let command routing do the work for me!

Right. I think we got sloppy on the wording. If you look at the thread method, you will see that it clearly uses PostMessage() and it uses the Windows API method rather than the CWnd method which would fail (acknowledging your initial post).

Neville Franks wrote:

You should never use SendMessage() from a thread unless you don't care about deadlocks occurring. Further you are stopping the worker thread until the main thread has done its stuff.

Sorry, I didn't look at the source code when I posted my first comment, but now I did and oh boy... Your attempt to separate threads fails right from the start, so all this work with posting WM_NOTIFY message is useless.

1. You are passing CDocument pointer to your worker thread. It's not the safest, but OK if you are getting a pointer to some data or making a copy of some data protected by some type of synchronization object.

2. You then start accessing this CDocument's methods directly without any protection! By calling Doc->OnInformationThread you've already crossed another thread's boundary.

3. Further. In your signal_fifo_impl class you accessing m_Signals.size() without locking it first. Your CNotifyObject is not thread safe because of CString.

I would write a class similar to your CNotifyObject which holds HWND of the main frame, and pointer to the quee. Then I'd pass an instance of this class (created on the heap) to the worker thread at creation time. From the worker thread, if I need to inform the document of some event, I'd use PostMessage with HWND of the CMainFrame. In CMainFrame I'd receive the message, and call an appropriate CDocument handler which would update the views or do whatever else is neccessary.

1. The worker thread is a static method of the CDocument so when you enter the thread you have access to the protected and private members of the CDocument object. There is nothing intrinsically wrong with this but you would have to acquire the resources that you wanted to use (either by critical section or mutex). In this example, I am not really accessing any of the documents resources except one (which I will address further down).

2. I have not crossed a thread boundary. OnInformationThread() is only used by the thread and is provide to make access to the document's resource easier (without dereferencing the Doc pointer at every turn). I would still need to lock the resources I was accessing.

3.

Damir Valiulin wrote:

In your signal_fifo_impl class you accessing m_Signals.size() without locking it first.

Good point... I had missed that. When I originally wrote this, I encapsulated CWinThread (not derived) with another class (I called it CPulseThreadObject) that also held the signal_fifo_impl object and that did the work of locking the deque, even when getting the size of the deque. I will go back and take a look at this.

4. I have seen people inherit similar classes from CWnd directly on the main thread (you could make them children of the main frame) and then post messages to the object! (Well, actually, post message to the HWND of the object). In other circumstances I have created an object collection manager which would hold a series of these objects, pull an object from the collection when I needed one and return it to the collection when I was done with it. "new" and "delete" are relatively expensive. I didn't go into any of that for the same reason as removing the CPulseThreadObject from the project: it wasn't the point of the article.

2. I have not crossed a thread boundary. OnInformationThread() is only used by the thread and is provide to make access to the document's resource easier (without dereferencing the Doc pointer at every turn). I would still need to lock the resources I was accessing.

Well, it is true for your particluar example, but in general I wouldn't recommend writing like this. I can easily see how someone could just use your example as a template and make a mistake of accessing document's data or make non-thread safe MFC calls.

Also, unless the worker threads are extremly short (then it wouldn't make sense to use them in the first place), I don't see how "new" or "delete" are expensive in terms of performance. On a relative scale WM_NOTIFY message will be a LOT slower then new or delete.

Michael, thanks for your clear and well written article. I wasn't aware that WM_NOTIFY was available to a CDocument. I've done similar things from worker threads.

You mentioned that you could post to the View from the worker thread, however readers should note that you can't access a HWND from a CWnd that was created in a different thread, because the CWnd handle maps are thread specific.

Another way to communicate across thread is to use a message queue. I prefer that option as it isn't MFC specific and helps build more reusable and independent code.