Leading your developers forward







Home

Programming

Web 
Development

E-Commerce

Web Services

Consulting

Mentoring

Training

Books &
Writing

 

 

 

Command Routing

We decided to rewrite the simple Finger application from Building Internet Applications with Visual C++ to use command routing instead of messages. This page lists the changes you need to make to the code supplied with the book.

Why command routing?

Well, why not? A window can be a command target, after all, so you won't lose anything for the classes you already have that inherit from CWnd. But classes that inherit from CDocument or CWinApp can only be command targets; they cannot receive Windows messages. If the socket class communicates with commands, it can contact a document or application instance directly rather than going through a faked-up window. If you have a class in mind that is currently not a command target, there is less overhead involved in creating instances of a class that inherits from CCmdTarget than in creating ones that inherit from CWnd.

What's the difference between a command and a message?

A command is a certain kind of message. There is a message called ON_COMMAND, which indicates that a command is being routed. Many MFC objects inherit from CCmdTarget and can have commands routed to them even though they can't receive windows messages because they don't inherit from CWnd. If you aren't willing to accept that as the only really important difference between commands and messages, read the article Meandering Through the Maze of MFC Message and Command Routing in the July 1995 issue of MSJ, Microsoft Systems Journal.

How does that change the code?

A message has two parameters; a command has only one. We had three pieces of information to pass along in our socket messages before, but managed to fake up a way to encode all three pieces in two parameters. If the first parameter was positive, it was the length of the data being transmitted and the second parameter was a pointer to the data itself. If the first parameter was negative, there was no data being transmitted but rather than socket was telling the window about a status change; the value of the first parameter was cast to an enum that listed possible status change values. We just can't pull off that kind of parameter-packing with only one parameter, so we use that single parameter as a pointer to a structure, QSocketInfo, that carries all the relevant information. The vast majority of the changes we will present below are to accomodate this single fact; changes for other reasons are noted as we go. We are assuming throughout that you are using the version of QSocket that inherits from CSocket; ie that you have Visual C++ version 2.1 or later. If not, make these changes to the version of QSocket that does not inherit from CSocket. You will be changing the header and code files for two classes: QSocket (socket.h and socket.cpp) and CFingerView (fingevw.h and fingevw.cpp) as detailed below. If you'd rather not do all that typing, you can just get the zip of the four changed files. In the changed files, each change is preceded by a comment showing you what the line was in the message-passing version of Finger. These files will not work alone; they are meant to replace the ones on the CD that came with the book.

socket.h

  • Change the enum SocketReceiveCmd: add a SocketReceivedData and remove the value specifiers ( =-100 and =-101)
  • Before the definition of QSocket, add this code, which sets up a message macro to be used in a message map later:
    #ifndef _WIN32
    #define ON_SOCKET_COMMAND(id, memberFxn) \
        { CN_COMMAND, (WORD)id, AfxSig_bpv, \
        (AFX_PMSG)(BOOL (AFX_MSG_CALL CCmdTarget::*)\
        (void *))memberFxn },
    #else
    #define ON_SOCKET_COMMAND(id, memberFxn) \
         { WM_COMMAND, CN_COMMAND, (WORD)id, \
         (WORD)id, AfxSig_bpv, \
         (AFX_PMSG)(BOOL (AFX_MSG_CALL CCmdTarget::*)\
         (void *))memberFxn },
    #endif

    Why do we use different versions of the macro for 32 bit Windows (NT and 95) than for 16? Because the 32-bit version has an extra parameter at the start, set to WM_COMMAND here, that defines what sort of messages will get routed. The 16 bit version just routes WM_COMMAND messages. The 32 bit version also has an extra parameter in the middle: instead of taking a single MessageID it takes two and catches all messages in the range between those values. We only want to catch a single message, so we use the same value for both parameters.
    Why AfxSig_bpv? It is a signature (hence Sig) for functions that return a BOOL and are passed a single pointer-to-void. There are many different signatures, each used for a different combination of return type and parameter type. Since the function that will catch this message takes the pointer to or QSocketInfo structure, and returns a BOOL, this is the right signature for us.

  • Before the definition of QSocket, add this code to define the QSocketInfo structure:
    class QSocket;  //used in struct
    struct QSocketInfo {
    	SocketReceiveCmd cmd;
    	QSocket *socket;
    	char *data;
    	int  length;
    }; 
  • Change the CWnd *ReceiveWindow to CCmdTarget *ReceiveTarget
  • Change the UINT immediately following that from ReceiveMessage to ReceiveID
  • Change the parameters of SetReceiveTarget to CCmdTarget *target, UINT id

socket.cpp

  • Change the default constructor so that it initializes the renamed ReceiveTarget and ReceiveID
  • At the end of SetErrorVars(), remove the call to ReceiveWindow->SendMessage and the if surrounding it and replace it with this code:
        if (ReceiveTarget)
        {
            QSocketInfo info;
            info.cmd = SocketStatusChanged;
            info.socket = this;
            info.data = 0;
            info.length = 0;
    
            ReceiveTarget->OnCmdMsg(ReceiveID, 
                CN_COMMAND, &info, NULL);
        }
    
  • Change the parameters of SetReceiveTarget as in socket.h, and change the first two lines of the function to use the passed parameters to set the value of the member variables:
        ReceiveTarget = target;
        ReceiveID = id;
    
  • Change the first line of GetLine() to test ReceiveTarget rather than ReceiveWindow
  • At the end of OnReceive(), make essentially the same change as in SetReceiveTarget, except that the structure values are different:
        if (ReceiveTarget)
        {
            QSocketInfo info;
            info.cmd = SocketReceivedData;
            info.socket = this;
            info.data = 0;
            info.length = 0;
    
            ReceiveTarget->OnCmdMsg(ReceiveID, 
                CN_COMMAND, &info, NULL);    }
  • At the beginning of OnAccept(), make that block change again:
        if (ReceiveTarget)
        {
            QSocketInfo info;
            info.cmd = NewSocketAccepted;
            info.socket = news_socket;
            info.data = 0;
            info.length = 0;
    
            ReceiveTarget->OnCmdMsg(ReceiveID, 
                CN_COMMAND, &info, NULL);    }
  • At the end of OnAccept(), make that block change again:
        if (ReceiveTarget)
        {
            QSocketInfo info;
            info.cmd = SocketStatusChanged;
            info.socket = this;
            info.data = 0;
            info.length = 0;
    
            ReceiveTarget->OnCmdMsg(ReceiveID, 
                CN_COMMAND, &info, NULL);    }
  • At the beginning of OnConnect(), make that block change again:
        if (ReceiveTarget)
        {
            QSocketInfo info;
            info.cmd = SocketStatusChanged;
            info.socket = this;
            info.data = 0;
            info.length = 0;
    
            ReceiveTarget->OnCmdMsg(ReceiveID, 
                CN_COMMAND, &info, NULL);    }
  • At the end of OnConnect(), make that block change again:
        if (ReceiveTarget)
        {
            QSocketInfo info;
            info.cmd = SocketStatusChanged;
            info.socket = this;
            info.data = 0;
            info.length = 0;
    
            ReceiveTarget->OnCmdMsg(ReceiveID, 
                CN_COMMAND, &info, NULL);    }
  • At the end of OnClose(), make that block change again:
        if (ReceiveTarget)
        {
            QSocketInfo info;
            info.cmd = SocketStatusChanged;
            info.socket = this;
            info.data = 0;
            info.length = 0;
    
            ReceiveTarget->OnCmdMsg(ReceiveID, 
                CN_COMMAND, &info, NULL);    }
  • At the end of ConnectHelper(), make that block change one last time:
        if (ReceiveTarget)
        {
            QSocketInfo info;
            info.cmd = SocketStatusChanged;
            info.socket = this;
            info.data = 0;
            info.length = 0;
    
            ReceiveTarget->OnCmdMsg(ReceiveID, 
                CN_COMMAND, &info, NULL);    }

fingevw.h

  • Change the #define from WM_SOCKET_RESPONSE to just SOCKET_RESPONSE since it is no longer a message, and the value from WM_USER+201 to 5000, which is in the range for user-defined commands.
  • Change the message map declaration of OnSocket to have a single parameter, void *pInfo

fingevw.cpp

  • Change the message map entry from ON_MESSAGE(WM_SOCKET_RESPONSE, OnSocket) to ON_SOCKET_COMMAND(SOCKET_RESPONSE, OnSocket) to use the macro defined in socket.h earlier
  • Change the declaration of OnSocket as in fingevw.h
  • Add these three lines at the beginning of OnSocket to dereference the structure information:
    SocketReceiveCmd cmd = ((QSocketInfo *)pInfo)->cmd;
    int amount = ((QSocketInfo *)pInfo)->length;
    char *buffer = ((QSocketInfo *)pInfo)->data;
    
  • In OnSocket, change if ((int)amount > 0) to if(cmd == SocketReceivedData)
  • In OnSocket, change switch ( (SocketReceiveCmd)amount ) to switch ( cmd )
 

Is That All?

Yes, that's all it takes to convert QSocket to a command-routing class from a message-passing one, and to change the Finger app to use the commands QSocket generates. The application should compile and run with no noticeable difference at all. So why bother? Because now that you've seen how simple it is to use commands where you might have chosen to use messages, you should find several annoying programming problems just got a lot easier to handle!

We welcome comments on this or any other topic; write to ttu-comment@gregcons.com

Return to Tips, Tricks, and Updates.


Copyright Gregory Consulting Limited 1996-2007. All rights reserved.