Data-Driven Code

In an ideal implementation of a table-based design such as a finite state machine (FSM), the program is built from the tables themselves. In this kind of program, the tables are embedded in the code and somehow direct the flow of execution. The wisdom of this is clear: the tables are a product of your design process, and using them directly unifies the design—or at least some elements of it—with the code. It’s also easier to make design changes because you don’t have to translate between logical and physical models.

When it comes to building data-driven programs, working with more traditional Windows programming languages such as C and C++ offers two definite advantages over Visual Basic. First, you can maintain tables of pointers to functions and invoke those functions directly through indexes into the tables.

This removes the need for the unwieldy jumble of conditional statements needed in our first stab at an FSM in Visual Basic, reducing the DoFSM function to just two statements:

void fvDoFSM(int nState, int *nEvent)
{
    (aapvActionTable[nState][*nEvent])();
    nEvent = aanStateTable[nState][*nEvent];
}

Second, you can lay out the tables in compile-time initialization statements. This is where the design and implementation intersect since you can lay out the table in a readable fashion and any changes you make to it are directly changing the code. Here’s what the comment stripper FSM tables might look like in a C program:

void (*aapvActionTable[NUM_STATES][NUM_EVENTS])() =
{
//                E_SLASH     E_STAR     E_OTHER

/* S_OUTSIDE  */ {fvOutSlash, fvOutStar, fvOutOther},
/* S_STARTING */ {fvStaSlash, fvStaStar, fvStaOther},
/* S_INSIDE   */ {fvInsSlash, fvInsStar, fvInsOther},
/* S_ENDING   */ {fvEndSlash, fvEndStar, fvEndOther}
};

int aanStateTable[NUM_STATES][NUM_EVENTS] =
{
//                E_SLASH     E_STAR     E_OTHER

/* S_OUTSIDE  */ {S_STARTING, S_OUTSIDE, S_OUTSIDE},
/* S_STARTING */ {S_STARTING, S_INSIDE,  S_OUTSIDE},
/* S_INSIDE   */ {S_INSIDE,   S_ENDING,  S_INSIDE},
/* S_ENDING   */ {S_OUTSIDE,  S_ENDING,  S_INSIDE}
};

Unfortunately, although Visual Basic has an AddressOf operator, the only useful thing you can do with it is pass the address of a function or procedure in a parameter list. (C programmers will be disappointed to find that AddressOf isn’t really like C’s unary & operator.) Although you can use AddressOf in calls to Visual Basic functions, ultimately you can’t do much inside those functions except pass the address on to a Windows API function. This capability is a major leap forward from all previous versions of Visual Basic, but the fact that you can’t invoke a Visual Basic function from an address means that you can’t implement an action table like the C one shown above.

Or can you? You can certainly store Visual Basic function addresses in a table by passing them to a suitable procedure. Visual Basic permits you to store function addresses in long variables:

Sub AddAddressToTable(ByVal niState As Integer, _
                      ByVal niEvent As Integer, _
                      ByVal pcbVbCodeAddr As Long)
    ActionTable(niState, niEvent) = pcbVbCodeAddr
End Sub

Unfortunately, that’s as far as you can go with pure Visual Basic. Perhaps a future version of Visual Basic will have a dereferencing operator or maybe a CallMe function that accepts an address and calls the function at that address; for now, however, you’re on your own.

But don’t despair, because you’re not sunk yet. Visual Basic doesn’t have a CallMe function, but there’s nothing to stop you from writing your own. You’ll need to write it in another language, of course, but if you’re one of those Visual Basic programmers who breaks out in a cold sweat at the thought of firing up a C compiler, take heart—this is likely to be the shortest C program you’ll ever see. Here’s the program in its entirety:

#include "windows.h"

__declspec(dllexport) int LibMain(
                         HANDLE hModule,
                         WORD wDataSeg,
                         WORD cbHeapSize, LPSTR pszCmdLine)
{
    return 1;
}

__declspec(dllexport) int _WEP(int bSystemExit)
{
    return 1;
}

void __stdcall CallMe(void (*pfvVbCode)(void))
{
    pfvVbCode();
}

The business end of this code is a single statement; the rest is scaffolding to make a DLL. (You also need to use a DEF file to make the linker export the CallMe symbol.) Now all you need to do is include a suitable Declare statement in your Visual Basic code, and you can call Visual Basic functions from a table!

Declare Sub CallMe Lib "callme.dll" (ByVal lAddress As Any)
§
CallMe ActionTable(nState, nEvent)

The source code for the DLL and a Visual Basic program that calls it can be found in CHAP14\callme.

Building a Better Event Queue

Remember Message Blaster? Message Blaster is a custom control that lets you intercept Windows messages sent to any Visual Basic control. Windows messages are the raw material of Visual Basic events, but the Visual Basic designers filtered out most of the messages when they decided which events Visual Basic programmers were likely to need. A form’s Resize event, for example, occurs after the resize has happened, which makes implementing size limits for a resizeable form ugly because you have to snap the size back in the Resize event handler. With Message Blaster, you can intercept the WM_SIZE message and change the form’s size with a suitable API call before Windows repaints it.

Now that you know what Message Blaster is, forget it. Visual Basic 5 lets you do all the things that Message Blaster did, directly from Visual Basic code. Message Blaster is an example of a subclassing control; subclassing is what Windows programmers do to hook a custom message handler (usually called a window procedure) onto a window, and subclassing controls were an inelegant hack to make this possible in earlier versions of Visual Basic. By allowing Windows callback functions to be coded in Visual Basic, Visual Basic 5’s new AddressOf operator opens up subclassing directly to Visual Basic programmers.

The theory goes like this: You nominate any object that you have (or can get) a window handle for and tell Windows the address of a Visual Basic procedure to call whenever it receives a message for that object. For messages you don’t want to handle, you simply call the original message handler. To fix the resizing problem outlined above, you’d write something like this:

pcbOldWindowProc = SetWindowLong(Me.hWnd, GWL_WNDPROC, _
                                 AddressOf lMyWindowProc)
§

Function lMyWindowProc(ByVal hWnd As Long, _
                       ByVal lMsg As Long, _
                       ByVal wparam As Long, _
                       ByVal lparam As Long) As Long

    If lMsg = WM_SIZE Then
        ' Play with the size here.
    End If

    lMyWindowProc = CallWindowProc(pcbOldWindowProc, hWnd, _
                                   lMsg, wParam, lParam)
End Function

Any messages that Windows receives for a window are queued so that they arrive in sequence, and you can use this behavior to make a queue for FSMs. The simplest way is to hang a window procedure off an arbitrary control and start sending messages to the queue with PostMessage, but this is a bit ugly and can’t be done unless you have a form loaded. A better way is to create a window for your own exclusive use behind the scenes. The code is straightforward:

lHwnd = CreateWindowEx(WS_EX_TRANSPARENT, "static", _
                       "My Window", WS_OVERLAPPED, _
                       0&, 0&, 0&, 0&, 0&, 0&, _
                       CLng(App.hInstance), 0&)

lEventMsg = RegisterWindowMessage("FSM Event")

The choice of style and extended style parameters is arbitrary and doesn’t really matter since you’re never going to display the window. Now all you have to do is hook up an event handler to the window and start sending messages. It’s a good idea to register a private message as done here, but you could just use any message number greater than WM_USER. It’s best to encapsulate the code in Visual Basic functions or a class (CHAP14\fsm\fsmcls\pubfsm.cls shows one possible way), but be aware that the window procedure must be in a standard module. All the constants and Visual Basic declarations for all the functions can be pasted from the API Viewer tool supplied with Visual Basic. This tool is run from the file Apiload.exe, which is located in the VB=Winapi folder on the Visual Basic 5 CD.