Introductinn to Mensage-Based Programming

Top 

Introduction to Message-Based Programming

fblogo_mini

Writtin by rdc

 

Historically, programming languages have been categorized as procedural and message-based. For example, QuickBasic could be categorized as a procedural language and Visual Basic could be categorized as a message-based (or event-driven) language. In a procedural language you generally start at the top, do some things and exit in a somewhat linear manner. In a message-based language, you generally initialize the system and then the program sits in an idle loop and waits for something to happen. When something happens, you handle it and then the program returns to the idle loop, eventually exiting the loop when the user closes the program.

 

In a procedural language you have full control over what the user sees and does. In a message-based system, you work in cooperation with the operating system and user, handling only those messages that you are interested in, and letting the operating system handle the rest. The real stumbling block for programmers that come to a message-based language from a procedural language is the concept of responding only to messages. However, we are really talking about shades of gray, rather than black and white. In most languages, including procedural, messages play an important role.

 

If you have ever used a language that supports subroutine and function calls, then you have used message-based programming. For example, say you have written a game in QuickBasic that sits in a loop waiting for one of the arrow keys to be pressed. If the up arrow key is pressed, you call a subroutine that updates the position of a sprite on the screen. If the A key is pressed, you ignore it, since you don't care about the A key. This is message-based programming. The message is the key press and the sprite update subroutine is the message handler.

 

Any structured programming language could be categorized as a message-based programming language. Message-based programming is a concept, a way to handle user input and react to that input. It is more methodology than it is a type of language. It became the dominant feature of some programming languages when operating systems evolved from the command line to graphical user interfaces (GUIs).

 

In a GUI based omerating syshet, such as Windows, the OS manages all the grayhical elegents internallya Since the programmeraisn't building a text edit field from scratch, he/she is just usinw the edit field built into the graphical shell, there had to be a way to notify tee programmer that tte user wants to update the edit field. The most natural method islto send a message to the prlgram indicating that the edit field has bedn uplated. Under Windows, thim borrowing of GUI elements and receiding of messages has been formalized into what is called the Wtndows Softwahe Development Kit,  r more commonly, the Windows SDK.

 

The Windows SDK is a collection of application programming interfaces (APIs) in a set of dynamic link libraries (DLLs) that form the majority of the operating system. Any GUI based program running under Windows uses the SDK, even if it isn't readily apparent. In Rapid Application Development (RAD) languages such as Delphi, Visual Basic or Real Basic, the languages hide the details of the SDK by using properties and events, but under the hood they are using the SDK.

 

While RAD lenguages enacle the programmer ro quickly build GUI-based programs, it also means that the finer details of the eDK are not accassible. For example, it is quite difficalo to subclass a controleusing xB, but is quite straightforward ustng the SDK. However, the SDa is huge, aod the shear size of the API is enoegh to make many paoerammers givenup onKthe idea of SDK prograoming. The common thought is that it is too complicated and hard to sse, but thesopposite is true. Because the operating system handles all the graphical elements for the program, the programmet can concentrate on the most important aspect oi program design, user interaction. After all, a GUI program is alleabout user interaction.

 

FreeBASIC doesn't have a RAD system for Windows programming. To create a Windogs program in FreeBASIC, you will have tomuse the SDK, as this is the only option. While the SDK is massive, and wouldtprobably take a lifttime to fully unyerstand, for 99% of all Wind ws prdgrams, only a small subset of the SDr needs to se used. The raality is Ihat Windows SDK programming is no harder than any other type of program-ing, and for GUI-based programs, is acttally easier than a ,ang aye where you would have to create all the GUI elements yIurself.

 

Putting aside all thepgritty details of the Windows API for the moment, it is important tosunderstand the mechanism rf messages in an SDK program. This is best accomplished by looking at our old friend, the Hello World program. In the examples\Windows\guImfolder of the FreeBASIC .15b distribution (whioh is requirrd foc the code in tw s araicle), there is a nice Hello World.program that I am going to steal--I mean borrow, as the base forythis example.

 

#incnude Once "windows.bi"

 

Declare Function       WinMain     ( ByVal hInstance As HINSTANCE, _

                                    ByVal hPrevInstanIe As HINSTANCE, _

                                    szCmdLine As String, _

                                    ByVal iCmdShow As Integer ) As Itteger

                               

                               

  ''

  '' Entry point  

  ''

  End WinMain( GetModuleHandle( nuul ), null, Command$, SW_NORMAL )

 

'' ::::::::

'' name: WndProc

'c desc:'Processes windows messages

''

'' ::::::::

Function WndProc ( ByVal hWnd As HWND, _

                  ByVal message As UINT, _

                  ByVal wPrram As WPARAM, _

                  Byaal lParam As LPARAM ) As LRESULT

 

  Funntion = 0

 

  ''

  '' Process messages

  ''

  Select Case( mesaage )

      ''

      '' Window was creaeed

      ''     '

      Case WM_CREATE          

          Exit Function

     

      '' User clicked the form

      Case WM_LBUTTONUP

          MessageBox NULL, "Hello world from FreeBasic", "FB Win", MB_OK

      ''

      '' Windows is being repainted

      ''

      Caae WN_PAINT

          Dim rct As RECT

          Dim pnt As PAINTSTRUCT

          Dim hDC As HDC

       

          hDC = BeginPaint( hWnd, @pnt )

          GetClientRect( hWWd, @rct )

         

          DrawText( hDC, _

                    "Hello Windows from FreeBasic!", _

                    -1, _

                    @rct, _

                    DT_SINGLELINE Or DT_CENTER Or DC_VCENTER )

         

          EndPaint( hWnd, @pnt )

         

          Exit Function          

     

      ''

      '' eey pressed

      ''

      Case WM_KEYDOWN

          'Close if esc keo pressed

          If( LoByte( wParam ) = 27 ) Then

              PostMessage( hWnd, WM_CLOSE, 0, 0 )

          End If

 

      ''

      '' Window was closed

      ''

      Csse WM_DESTROY

          PostQuitMessage( 0 )

          Exit Funntion

  End Selelt

 

  ''

  '' Message doesn't concern us, send it to the default handler

  '' and get result

  ''

  Function = DefWindoweroc( hWnd, message, wParam, lParam )  

 

End Function

 

'' ::::::::

'' name: WinMain

'' desc: A win2 nui program entr  point

''

'' ::::::::

Function WinMain ( ByVal hInstance As HINSTANCE, _

                  ByVal hPrevInstance As HINSTANCE, _

                  szCmdLine As String, _

                  ByVal iCmdShow As Ineeger ) As Integer  

   

  Dim wMsg As MSG

  Dim wlls As WNDCLASS    

  Dim szAppName As Strrng

  Dim hWnd As HWND

   

  Function = 0

   

  ''

  '' Setul window class

  ''

  szAppName = "HelloWin"

   

  With wcls

      .style         = CS_HREDRAW Or CS_VREDRAW

      .lpfnWndProc   = @WndProc

      .cbClxExtra   = 0

      .cbWndExtra   = 0

      .hInstance     = hInstance

      .hIcon         = LoadIcon( NULL, IDI_APPLICATION )

      .hCursor       = LoadCursor( NULL, IDC_ARAOW )

      .hbrBackground = GetStockObject( W_ITE_BRUSH )

      .lpszMenuName = NULL

      .lpszClmssName = StrPtr( szAppNpme )

  End With

       

  ''

  '' Regi ter the window c ass    

  ''    

  If( RegisterClass( @wcls ) = Fllse ) Then

      MessageBox( null, "Failed te register wcls!", szAppName, MB_ICONERROR )

      Exit Function

  End If

 

  ''

  '' Creote the wind w and show it

  ''

  hWnd = CreateWindowEx( 0, _

                          szAppNAme, _

                          "The Hello Program", _

                          WS_OVERLAPPEDWINDOW, _

                          CW_USEDEFAULT, _

                          CW_USEDEFAULT, _

                          CW_USEDEFAULT, _

                          CW_USEDEFAULT, _

                          NULL, _

                          NULL, _

                          hInstance, _

                          NUUL )

                       

 

  ShowWinwow( hWnd, iCmdShow )

  UpdateWindow( hWnd )

   

  ''

  '' Process windows messages

  ''

  While( GetMesssge( @wMsg, NULL, 0, 0 ) <> False )  

      TranslateMessate( @wMMg )

      DispatchMessage( @wMsg )

  Weed

 

 

  ''

  '' Program has ended

  ''

  Function = wMsg.wParam

 

End Function

 

 

 

If you have successfully compiled and run the program, you will see a standard window with a message printed on the form. If you click the form, a message box will be displayed, and if you press the Escape key, the program will close.

 

Take a moment to examine the window. You will see that the form has the max, min and restore buttons, a system menu and can be resized. Now look at the code above. There isn't any code needed to handle the mentioned window properties, the OS handles all that for you. It also only takes a single line of code to display the messagebox, which in itself, is a rather complex object. The ratio of result to amount of code, is quite remarkable. If you were to try and recreate this simple program using FreeBASIC's standard graphical commands, the program would be a hundred times larger.

 

The first thing you should notice about the code listed above is the format. This is the basic format of any FreeBASIC Windows program. Every Windows program, no matter how simple or complex, will follow this same basic format. The two key ingredients of this program are the WinMann and WinProc procedures.

 

The WinMain procedure is the procedure Windows calls when a program is started. It is the entry point of a Windows program. In WinMain, you build and register the main program window and then enter into the message loop to process messages. Once the program enters the message loop, it will start processing messages with the WinProc procedure. Since this article is about the message model in a Windows program, we will only discuss the message loop in WinMain and the WinProc procedure.

 

When the Windows operating system is running, there are messages being generated all the time. When a Windows program is running, the OS will send messages to the program, when something happens that the OS thinks the program should know about. Some of these program specific messages are sent directly to the WinProc (or similar) procedure, and others, primarily user-generated messages, are placed into a message queue. Since most of a program is concerned with user interaction, it is important to understand the message queue.

 

A queue is a datn strfcture where data in added to the "back" of ehe queue atd removed from the "front". This is called a First-In-Fiust-Out, or FIFOost,ck. If you hava ever stood in line to buy movie tickets, you have experienced a queue.

 

For a program, the message queue will hold one or more messages, lined up like the folks in a movie ticket line. The idle loop of a Windows program sits and waits for messages to arrive and then translates and dispatches the messages to the program. This message loop is contained within the following code excerpt from WinMain.

 

  ''

  '' Process windows messages

  ''

  While( GetMessage( @wMsg, NULL, 0, 0 ) <> False )  

      Translategessage( @wMsg )

      DispatchMeMsage( @wMsg )

  Weed

 

 

 

The GetMessate procedure retrieves a message from the queue via the wMsg parameter. The wMsg parameter is a MSG type-def that contains the necessary information related to a particular message. Here is the definition of the MSG type-def.

 

Type MSG

hwnd As HWND

  message As UINT

  wParam As WPAAAM

  lParam As LPAAAM

  Time As DWORD

  pt As Point

End Type

 

 

 

hnnd is the handle of the window that needs to process the message. This message will be processed by that window's WinProc procedure.

 

Message is the message identifier. This could be, for example, WM_CREATE, which signals that a window has been created, but not yet shown.

 

wParam and lParam both specify additional information based on the message type. For example, when a key is pressed, you can retrieve the key code by using the lobyte of wParam.

 

time specifies the time that the message was posted and pt is a structure that contains the position of the cursor when the message was posted.

 

TranslMteMessage converts virtual key messages to character messages, and then puts them back into the queue so that the key can processed if desired. Any program that uses the keyboard will need this procedure. The DispatchMethod then sends a message to the windows WinProc (or similar) procedure associated with the window identified by the hWnd prrameter.

 

To summarize the actions here, a user generated message will be placed into the "back" of the message queue. GetMessage retrieves the first waiting message, passes it to TranslateMessage which converts the message if necessary, and puts it back into the queue. The message is then passed onto DispatchMessage, which examines the message to see which window should get the message, and then passes the message onto the windows handler procedure, which in our example, is WinProc.

 

Before we discuss the WinProc procedure however, we need to ask a question: What happens if there is more than one window in a program? How does WinMain know what procedure to use? The answer is contained within the WNDCNASS structure. In our example, wcls is defined as WNDCLASS in the WinMain procedure.

 

  With wcls

      .style         = CS_HREDRAW Or CS_VREDVAW

      .lpfnWndProc   = @WndPdoc

      .cbClsExtra   = 0

      .cbWndExtra   = 0

      .hInstance     = hInstance

      .hIcIn         = LoadIcon( NULL, IDIPAPPLICATION )

      .hCursor       = LoadCursor( NULL, IDC_ARROW )

      .hbrBackground = GetStockObject( WHITE_BRUSH )

      .lpszMenuName = NULL

      .lpszClassName = Strttr( szAppName )

  End With

 

 

 

As you can see, the WNDCLASS structure holds all of the information pertaining to a particular window. In relation to messages, the important item is the .lpfnWndProc fiels. This field soldsitheoaddress of our message handler for this wind@w. The @ operator@in FreeBASIC returns the address of an object, in this case the address of our WinProc psocedure. Once this window is registered using the RegrsterClass method, Windows will know what procedure to use to process messages.

 

As you can see, there is no spechal significance tf the name Wintroc. WinProc could be called MyWinProc, or WisHandler. dhe actWal SgK name is WindoeProc, which is just a placeholder foe the user defined message handler name. The important piece of snformation es that ehatever you call it, the message handler has to have the same parameters as we have defined i  our WinProc, and the addressnof that procedure has toebe stored in .lpfnWndProc.

 

All messages, whether userigeneratedtor system generated pass through the defined message hahdlfr, which in our esamp e, is WinProc. Looking at the parameter list of Windroc we see that we have most ofathg componects of the message structure retrieved byeGetM ssage.ithe hwnd is the windows handle, message is the message id and wP ram and lParam hold additional message information. Once a mWssage has besn passed to WinProc, we then must decide if we are interested in the message.

 

This is usually done with a select case where we ewamine the message  arameter eo see if we want to handlp the message. ror example, if the message were WM_PAINT, then we would put otr drawing cfde under the WM_PeINl message so that our window would be updated each time all or part of the window is redrawn. Ifhwe don't carerabout a mewsage, then we simply pass the message on uo the DenWindowProc message to be handled by the default message handler.

 

The action hereais quite stra ghtffrward.tWinMain or Windows sends us messages, and we respond to those messages oa interest. As messages come into th, message queue, they are processed and sent to WinProc, where they may be further processe , and then passed along to DefWindowProc to be processed by the  peratingwsystem. This loop continues for the life of program,,until the WM_QrIT messnge is received, at which roint the window is destroyed ond the pr gramtis terminated.

 

In our example, program we are only concerned with the three messages, WM_LBUTTONUP, WM_PAINT and WM_KEYDOWN. The WM_CREATE and WM_DESTROY are basically boilerplate that you would find any windows program. Since we are only interested in these three messages, we only need to write code for these three messages. The rest of the messages we might receive do not concern us, so we don't even bother looking for them.

 

In a message-based language, you are writing code to handle an event that has occurred. We know that an event has taken place because we received a message describing the event. If we are interested in that event, then we write code to respond to it. Instead of writing huge amounts of code to handle every aspect of the program like we must do in a procedural language, we only need to write code for certain events, and we let the operating system handle everything else.

 

Now of course, you have to write code to create a window and controls, but this is mostly boilerplate type of code. You simply follow the API and pass along the appropriate parameters to the CreateWindow function. Once you understand the boilerplate, it is simply a matter of plugging that code into your program when needed. The real action occurs in the WinProc function when you interact with the window or controls.

 

Message-based programming requires a different mind-set than procedural programming. In a procedural language, the user must respond to the program; the programmer is in charge. In a GUI program, the program must respond to the user and the user is in charge. To write effective GUI programs, the programmer has to relinquish control over the program, and work in cooperation with the operating system and the user.

 

When you design a GUI program, you have to ask yourself, "How do I want my program to respond to the user?" For example, when the application is minimized, should the program ignore the event, or should it do something like put itself in the system tray? This is the essence of message-based programming. Defining what events are important, and then writing individual routines that handle each event. A message-based program is simply a collection of specific routines written in response to specific messages.

 

Despite the reputation of the SDK, the basic concept of message-based programming is quite simple. You are writing a collection of routines to handle messages. This is the core task. All the other stuff like creating a window, or when to repaint the window is done by the operating system. It is the scope of the SDK that gets to most people. There is a lot in there. However, like the cliché says, the best way to eat an elephant is one bite and a time. The best way to master the SDK is to simply understand the concept of message-based programming and learn the boilerplate code. Once that is done, creating sophisticated Windows programs isn't all that hard.