• Welcome to Jose's Read Only Forum 2023.
 

ProgEx39 -- Function Pointers For Modularizing Our Window Procedures

Started by Frederick J. Harris, December 03, 2009, 03:50:17 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris


/*
  ProgEx39 -- Function Pointers For The Purpose Of Modularizing Our
              Window Procedure.

  The general architecture I showed you in the past two Windows GUI programs is
  an architecture I haven't used for many years.  The problem is the way the
  Window Procedure is structured.  There is, at least in my opinion, a better
  way to do it, but for me to explain it I'm going to have to bring you up to
  speed on function pointers.  If you are already experienced with them then you
  can skip this material.  But before I begin let me say a few words justifying
  this.

  This technique I learned from Douglas Boling and his book "Programming
  Microsoft Windows CE".  He ascribes the technique originally to Ray Duncan who
  wrote "Power Programming" columns for PC Magazine.  Anyway, the problem with
  the switch statement in the typical Sdk style Api Windows Gui program is that
  in major Windows apps it tends to run on for thousands of lines.  I've written
  many of these myself in my early days of Windows programming back in the 90s. 
  Just in the past two programs - Form1 and Form2 - you should have been able
  to see the effect.  Form1 didn't do much and had a very short Window
  Procedure.  Form2 did quite a bit more and while WinMain() stayed the same,
  all the added functionality was added through the Window Procedure.  As you
  add functionality the Window Procedure keeps growing. 

  The best way to begin modularizing the code is to create seperate message
  handling procedures for each message you need to process to achieve your
  program's goals.  The most widely used programming system of all time was
  Microsoft's Visual Basic, and that is how things were handled there.  For
  example, if you needed to handle the WM_PAINT message in a Visual Basic
  program your code editor would open up an edit window for you like so...

  Private Sub Form_Paint()

  End Sub

  And it would be there that you would add your code that you wished to run when
  a WM_PAINT occured.  Actually, we could create such a setup as this without
  going into function pointers by just calling functions such as above from
  within our switch construct; however, I want to show you a cleaner way to go
  about it.  So lets start off again with a little review of function pointers.

  What function pointers allow you to do is call a function through its runtime
  address rather than through an alphanumeric label associated with it.  In that
  sense its more flexible than simply calling a function through its name
  because a funtion pointer can be used to call any function with the specific
  signature it was designed for.  When one speaks of a function signature one
  means its parameter list. 

  Take a look at the program below.  We have three very simple functions Fn1(),
  Fn2() and Fn3() each of which has a void parameter list and returns nothing.
  All they do is output a message that they were called.  In main() you see a
  very simple example of a function pointer declaration where a function pointer
  pFn is created which can be used to call any of these three functions.  It is
  odd looking (all function pointer declarations are at best odd looking and at
  worst downright ugly) but the dead giveaway that you are looking at a
  function pointer declaration is the '*' symbol within parentheses prefaced to
  a name such as pFn.  And that part will be in the middle with a return value
  left of it and a parameter list to right (here just void and void).  You
  assign the function address of the function you want to call simply by using
  an assignment...

  pFn=Fn2;

  In this case the compiler 'knows' that the symbol 'Fn2' is a function (it has
  to be foreward declared first), and it 'knows' that pFn is a function pointer
  to a function with a void parameter list and a void return value.  It then
  assigns the runtime address of the function to the function pointer pFn.  So
  when you call the function through the function pointer you need to involve
  any parameters or return values in the usual way.  But as I said, here we
  don't have any.  Fool with this until you are comfortable with it.  Perhaps at
  this point you'll want to look back at Form1 and Form2 in the WinMain()
  function and see how the lpfnWndProc member of the WNDCLASSEX struct was set.   
*/

#include <stdio.h>

void Fn1(void) {puts("Called Fn1");}  //Three valid functions with void
void Fn2(void) {puts("Called Fn2");}  //parameter list and void return                               
void Fn3(void) {puts("Called Fn3");}  //value that just output string.

int main(void)
{
void (*pFn)(void);  //Function pointer declaration

pFn=Fn1,  pFn();    //Call Fn1 through pFn
pFn=Fn2,  pFn();    //Call Fn2 through pFn
pFn=Fn3,  pFn();    //Call Fn3 through pFn
getchar();

return 0;
}

/*
Called Fn1
Called Fn2
Called Fn3
*/

Frederick J. Harris

#1

/*
  ProgEx39a -- Function Pointer Arrays

  Expanding on the last program, one can create arrays of function pointers.  To
  create an array of function pointers that could hold the addresses of our 3
  functions of the last example, i.e., Fn1(), Fn2(), and Fn3(), one would simply
  change this declaration...

  void (*pFn)(void);

  to this...

  void (*pFn[3])(void);

  However, its more concise to just combine the declaration of the function
  pointer array with its initialization like so...

  void (*pFn[])(void)={Fn1,Fn2,Fn3};

  If you find that a bit intimadating or strange looking, its really not too much
  different from something like this that might seem a bit more familiar...

  char* pStrs[]={"Zero","One"."Two","Three"};

  Here's the same program again showing this usage.  This time we output the
  address of each function pointer element ( &pFn[i] ), the address of the
  function itself held in the function pointer ( pFn[i] ), and the actual result
  of the function call...
*/

#include <stdio.h>

void Fn1(void) {puts("Called Fn1");}
void Fn2(void) {puts("Called Fn2");}                                 
void Fn3(void) {puts("Called Fn3");}

int main(void)
{
void (*pFn[])(void)={Fn1,Fn2,Fn3};

puts("&pFn[i]\t\tpFn[i]\t\tpFn[i]");
puts("==========================================");
for(unsigned int i=0; i<3; i++)
{
     printf("%u\t\t%u\t\t",&pFn[i],pFn[i]);
     pFn[i]();
}
getchar();

return 0;
}

/*
&pFn[i]         pFn[i]          pFn[i]
==========================================
2293600         4199056         Called Fn1
2293604         4199076         Called Fn2
2293608         4199096         Called Fn3
*/

Frederick J. Harris

#2

/*
  ProgEx39b -- Function Pointers As struct Members

  Recall from Form1 and Form2 that we had a Window Procedure which received
  messages from Windows such as WM_CREATE, WM_CHAR, WM_MOUSEMOVE, etc., when any
  hardware event occurred which pertained to our window.  These and all other
  Windows messages (there are hundreds if not thousands of them) are equates
  defined in various Windows header files that are ultimately just integer
  numbers such as a few I've listed below...

  A Few Windows Messages

  WM_CREATE       = 1
  WM_DESTROY      = 2
  WM_SIZE         = 5
  WM_ACTIVATE     = 6
  WM_PAINT        = 15
  WM_CLOSE        = 16
  ...
  ...
  ...
  WM_LBUTTONDOWN  = 513

  From the last function pointer example we looked at where we investigated
  arrays of function pointers we saw that these numbers too were just integers
  representing the runtime addresses of functions.  If we design our Windows
  program such that every Windows Message is routed to a seperate procedure for
  its handling as was done with Visual Basic, i.e.,

  Private Sub Form_Activate(...)

  End Sub


  Private Sub Form_Paint(...)

  End Sub

  ...then there is no reason we can't create an array of structs like so to
  associate the integer value of a message with the integer address of the
  procedure which will handle that message...

  struct EVENTHANDLER
  {
   unsigned int    iMsg;
   long            (*fnPtr)(HWND, unsigned int, WPARAM, LPARAM);
  };

  As you can see, the 2nd member of the EVENTHANDLER struct is a function
  pointer.  The 1st member - iMsg - is just the numeric value of the equate
  for some speciic message such as WM_CREATE, WM_CHAR, etc.  So for Form1 where
  we handled only 3 messages, i.e., WM_CREATE, WM_LBUTTONDOWN, and WM_DESTROY,
  and just using some make believe numbers for function addresses of message
  handling procedures such as Form1_OnCreate(), Form1_OnLButtonDown() and
  Form1_OnDestroy(), we could have an array of values like so...

  EVENTHANDLER evh[3];

  evh[0].iMsg = WM_CREATE,         evh[0].fnPtr = Form1_OnCreate;
  evh[1].iMsg = WM_LBUTTONDOWN,    evh[1].fnPtr = Form1_OnLButtonDown;
  evh[2].iMsg = WM_DESTROY,        evh[2].fnPtr = Form1_OnDestroy;

  i    evh[i].iMsg      evh[i].fnPtr
  ==================================
  0    1                10024256
  1    513*             10086512
  2    2                10086548

  * 513 is WM_LBUTTONDOWN

  Before showing a console window version of all this I want to mention one
  final modification I've made to Window Procedures.  This idea I robbed from
  .NET and that is to amalgamate the various Window Procedure parameters into a
  struct I name WindowsEventArguments...

  typedef struct    WindowsEventArguments
  {
   HWND             hWnd;
   WPARAM           wParam;
   LPARAM           lParam;
   HINSTANCE        hIns;
  }WndEventArgs,  *lpWndEventArgs;

  For example, in .NET's WM_PAINT handler the single argument is of type
  PAINTEVENTARGS pea.  In the simplified example below which doesn't include any
  of the Windows header files and is only a console program I changed the
  WndEventArgs types to all longs so it would compile.  The basic idea then is
  that instead of using the switch construct to map windows messages to the
  procedure which handles them, a for loop construct is used.  The for loop
  iterates through the EVENTHANDLER struct array trying to make a match between
  the message parameter received in the window procedure and the iMsg member of
  the EVENTHANDLER struct.  If a match is made then that means a message handling
  procedure exists for that message and needs to be called.  If no match is made
  after traversing the full scope of the array the DefWindowProc() function gets
  called just as before.  In the case where a match was found the address to call
  is associated right there in the EVENTHANDLER member adjacent to the iMsg
  member.  Using this technique you end up with a truely 'minimalist' window
  procedure which will never involve more than a few lines of code.  Your programs
  will then consist basically of your WinMain() entry point function, and message
  handling procedures.  Here is a rough console mode example showing the essense
  of the idea...

*/
#include                      <stdio.h>
#define                       WM_CREATE             1
#define                       WM_LBUTTONDOWN        513
#define                       WM_DESTROY            2
#define dim(x) (sizeof(x) / sizeof(x[0]))

typedef struct WindowsEventArguments                     //The Win32 Window Procedure has
{                                                        //a very specific & unique function
long                         hWnd;                      //signature comprised of the HWND
long                         wParam;                    //of the Window to which a message
long                         lParam;                    //applies, and the wParam and lParam
long                         hIns;                      //parameters which contain message
}WndEventArgs, *lpWndEventArgs;                          //specific information.

long fnWndProc_OnCreate       (lpWndEventArgs Wea);      //These are prototypes or function
long fnWndProc_OnLButtonDown  (lpWndEventArgs Wea);      //declarations for three message or
long fnWndProc_OnDestroy      (lpWndEventArgs Wea);      //event handling procedures.

struct EVENTHANDLER                                      //The 1st member of the EVENTHANDLER
{                                                        //struct is the int value of a Windows
unsigned int                 iMsg;                      //message, and the 2nd is the address
long                         (*fnPtr)(lpWndEventArgs);  //of the message handling procedure.
};

const EVENTHANDLER EventHandler[]=                       //This is an array of EVENTHANDLER structs
{                                                        //that form the association between the
{WM_CREATE,                  fnWndProc_OnCreate},       //integer value of the message defines such
{WM_LBUTTONDOWN,             fnWndProc_OnLButtonDown},  //as WM_CREATE, and the runtime address of
{WM_DESTROY,                 fnWndProc_OnDestroy}       //the procedure to be called when the WNDPROC
};                                                       //determines that that message has been received.


long fnWndProc_OnCreate(lpWndEventArgs Wea)              //These following three procedures are
{                                                        //Windows Message Handling Procedures.
puts("Called fnWndProc_OnCreate()");                    //Here they aren't handling any Windows
return 0;                                               //messages but are just to show how they
}                                                        //may be organized within a function
                                                         //pointer calling protacol.  Note that
long fnWndProc_OnLButtonDown(lpWndEventArgs Wea)         //the EVENTHANDLER struct is simply 8
{                                                        //bytes in size and holds two ints.  The
puts("Called fnWndProc_OnLButtonDown()");               //1st int is an equate defined in
return 0;                                               //Windows.h.  Windows defines hundreds
}                                                        //of integer equates/defines for its
                                                         //various Windows messages.  For example,
long fnWndProc_OnDestroy(lpWndEventArgs Wea)             //one of the 1st messages Windows sends
{                                                        //to a window when it is being created
puts("Called fnWndProc_OnDestroy()");                   //is WM_CREATE.  The 2nd member of the
return 0;                                               //EVENTHANDLER struct is the address
}                                                        //of the function to call when a message...


int main(void)                                           //...is received.  The association between
{                                                        //the #define value of a message and the
WndEventArgs Wea;                                       //address of the procedure to be called when
                                                         //that message is received is made just above
for(unsigned int i=0; i<dim(EventHandler); i++)         //where the EventHandler[] array of
     (*EventHandler[i].fnPtr)(&Wea);                     //EVENTHANDLER type was declared and
getchar();                                              //initialized.

return 0;
}

/*
Called fnWndProc_OnCreate()
Called fnWndProc_OnLButtonDown()
Called fnWndProc_OnDestroy()
*/

Frederick J. Harris

#3

/*
   ProgEx39c  Revisit ProgEx37 Basic SDK Api Template Program And Redo With Function Pointers
              Event Handling Code.
*/

//Main.cpp
#include <windows.h>
#include <tchar.h>
#define dim(x) (sizeof(x) / sizeof(x[0]))                //Macro to determine # event handlers

typedef struct WindowsEventArguments                     //The Win32 Window Procedure has
{                                                        //a very specific & unique function
HWND                         hWnd;                      //signature comprised of the HWND
WPARAM                       wParam;                    //of the Window to which a message
LPARAM                       lParam;                    //applies, and the wParam and lParam
HINSTANCE                    hIns;                      //parameters which contain message
}WndEventArgs, *lpWndEventArgs;                          //specific information.

long fnWndProc_OnCreate       (lpWndEventArgs Wea);      //These are prototypes or function
long fnWndProc_OnLButtonDown  (lpWndEventArgs Wea);      //declarations for four message or
long fnWndProc_OnPaint        (lpWndEventArgs Wea);      //event handling procedures.
long fnWndProc_OnClose        (lpWndEventArgs Wea);

struct EVENTHANDLER                                      //The 1st member of the EVENTHANDLER
{                                                        //struct is the int value of a Windows
unsigned int                 iMsg;                      //message, and the 2nd is the address
long                         (*fnPtr)(lpWndEventArgs);  //of the message handling procedure.
};

const EVENTHANDLER EventHandler[]=                       //This is an array of EVENTHANDLER structs
{                                                        //that form the association between the
{WM_CREATE,                  fnWndProc_OnCreate},       //integer value of the message define such
{WM_LBUTTONDOWN,             fnWndProc_OnLButtonDown},  //as WM_CREATE, and the runtime address of
{WM_PAINT,                   fnWndProc_OnPaint},        //the procedure to be called when the
{WM_CLOSE,                   fnWndProc_OnClose}         //the window procedure determines that
};                                                       //that message has been received.


long fnWndProc_OnCreate(lpWndEventArgs Wea)              //Message Handler For WM_CREATE Message
{
MessageBox(Wea->hWnd,_T("Window Procedure Received WM_CREATE Message!"),_T("Message Report!"),MB_OK);
return 0;
}


long fnWndProc_OnLButtonDown(lpWndEventArgs Wea)         //Message Handler For WM_LBUTTONDOWN Message
{
MessageBox(Wea->hWnd,_T("Window Procedure Received WM_LBUTTONDOWN Message!"),_T("Message Report!"),MB_OK);
return 0;
}


long fnWndProc_OnPaint(lpWndEventArgs Wea)               //Message Handler For WM_PAINT Message
{
TCHAR szBuffer[64];
PAINTSTRUCT ps;
HDC hDC;

_tcscpy(szBuffer,_T("Click Anywhere On Form (And Oh Yeah...Hello, World!)"));
hDC=BeginPaint(Wea->hWnd,&ps);
SetBkMode(hDC,TRANSPARENT);
TextOut(hDC,40,20,szBuffer,_tcslen(szBuffer));
EndPaint(Wea->hWnd,&ps);

return 0;
}


long fnWndProc_OnClose(lpWndEventArgs Wea)               //Message Handler For WM_CLOSE Message
{
MessageBox(Wea->hWnd,_T("Window Procedure Received WM_CLOSE Message!"),_T("Message Report!"),MB_OK);
DestroyWindow(Wea->hWnd);
PostQuitMessage(0);
return 0;
}


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea;

for(unsigned int i=0; i<dim(EventHandler); i++)
{
     if(EventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(&Wea);
     }
}


return (DefWindowProc(hwnd, msg, wParam, lParam));
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Form1");
WNDCLASSEX wc;
MSG messages;
HWND hWnd;

wc.lpszClassName  =  szClassName;                     //Important Field!  Character string identifying window class
wc.lpfnWndProc    =  fnWndProc;                       //Important Field!  Function Pointer.  Address of Window Procedure
wc.cbSize         =  sizeof (WNDCLASSEX);             //Those top two fields I just listed are very important.  The
wc.style          =  0;                               //others are of course necessary too, but fully understanding all
wc.hIcon          =  LoadIcon(NULL,IDI_APPLICATION);  //the implications of the .szClassName and .lpfnWndProc fields will
wc.hInstance      =  hInstance;                       //go a long way to helping you understand Win32 coding. The
wc.hIconSm        =  0;                               //.hBrushBackground field will be the color of the Window's
wc.hCursor        =  LoadCursor(NULL,IDC_ARROW);      //background.  The .cbWndExtra field is very useful as it allows
wc.hbrBackground  =  (HBRUSH)COLOR_BTNSHADOW;         //you to associate object (Window) data to the instantiated Window's
wc.cbWndExtra     =  0;                               //internal structure, i.e., accomodate member data.
wc.cbClsExtra     =  0;
wc.lpszMenuName   =  NULL;
RegisterClassEx(&wc),
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,350,250,450,300,HWND_DESKTOP,0,hInstance,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))                 //All important message pump.  This logic continually retrieves
{                                                     //messages from the program's message queene and dispatches them
    TranslateMessage(&messages);                       //to the Window Procedure for processing.
    DispatchMessage(&messages);
}

return messages.wParam;
}

Frederick J. Harris


//Main.cpp
#define  UNICODE
#define  _UNICODE
#include <windows.h>
#include <tchar.h>
#include <string>
#include "ProgEx39d.h"


long fnWndProc_OnCreate(lpWndEventArgs Wea)              //Message Handler For WM_CREATE Message
{
std::wstring* pStr=NULL;
ProgramData* pPD=NULL;
long iCntr=0;

pStr=new std::wstring;
SetWindowLong(Wea->hWnd,0,(long)pStr);
pPD=(ProgramData*)GlobalAlloc(GPTR,sizeof(ProgramData));
if(pPD)
    SetWindowLong(Wea->hWnd,4,(long)pPD);
else
    return -1;
iCntr=GetClassLong(Wea->hWnd,0);
iCntr++;
SetClassLong(Wea->hWnd,0,iCntr);

return 0;
}


long fnWndProc_OnSize(lpWndEventArgs Wea)                //Message Handler For WM_SIZE Message
{
ProgramData* pPD=NULL;

pPD=(ProgramData*)GetWindowLong(Wea->hWnd,4);
pPD->xSize=LOWORD(Wea->lParam);
pPD->ySize=HIWORD(Wea->lParam);
InvalidateRect(Wea->hWnd,NULL,TRUE);

return 0;
}


long fnWndProc_OnChar(lpWndEventArgs Wea)                //Message Handler For WM_CHAR Message
{
std::wstring* pStr=(std::wstring*)GetWindowLong(Wea->hWnd,0);
*pStr+=+Wea->wParam;
InvalidateRect(Wea->hWnd,NULL,FALSE);

return 0;
}


long fnWndProc_OnMouseMove(lpWndEventArgs Wea)           //Message Handler For WM_MOUSEMOVE Message
{
ProgramData* pPD=(ProgramData*)GetWindowLong(Wea->hWnd,4);
pPD->xMouse=LOWORD(Wea->lParam);
pPD->yMouse=HIWORD(Wea->lParam);
InvalidateRect(Wea->hWnd,NULL,TRUE);

return 0;
}


long fnWndProc_OnLButtonDown(lpWndEventArgs Wea)         //Message Handler For WM_LBUTTONDOWN Message
{
ProgramData* pPD=(ProgramData*)GetWindowLong(Wea->hWnd,4);
pPD->xButton=LOWORD(Wea->lParam);
pPD->yButton=HIWORD(Wea->lParam);
InvalidateRect(Wea->hWnd,NULL,FALSE);

return 0;
}


long fnWndProc_OnPaint(lpWndEventArgs Wea)               //Message Handler For WM_PAINT Message
{
ProgramData* pPD=NULL;
std::wstring* pStr;
TCHAR szBuffer[16];
PAINTSTRUCT ps;
std::wstring s1;
std::wstring s2;
HDC hDC;

hDC=BeginPaint(Wea->hWnd,&ps);
pPD=(ProgramData*)GetWindowLong(Wea->hWnd,4);
s1=_T("xMouse=");
_stprintf(szBuffer,_T("%d"),pPD->xMouse);
s2=szBuffer;
s1+=s2+_T("     yMouse=");
_stprintf(szBuffer,_T("%d"),pPD->yMouse);
s2=szBuffer;
s1+=s2;
TextOut(hDC,0,0,s1.c_str(),s1.length());
if(pPD->xButton||pPD->yButton)
{
    s1=_T("xButton=");
    _stprintf(szBuffer,_T("%d"),pPD->xButton);
    s2=szBuffer;
    s1+=s2+_T("    yButton=");
    _stprintf(szBuffer,_T("%d"),pPD->yButton);
    s2=szBuffer;
    s1+=s2;
    TextOut(hDC,pPD->xButton+12,pPD->yButton,s1.c_str(),s1.length());
    pPD->xButton=0, pPD->yButton=0;
}
s1=_T("Width=");
_stprintf(szBuffer,_T("%d"),pPD->xSize);
s2=szBuffer;
s1+=s2+_T("    Height=");
_stprintf(szBuffer,_T("%d"),pPD->ySize);
s2=szBuffer;
s1+=s2;
TextOut(hDC,0,20,s1.c_str(),s1.length());
pStr=(std::wstring*)GetWindowLong(Wea->hWnd,0);
TextOut(hDC,0,40,pStr->c_str(),pStr->length());
EndPaint(Wea->hWnd,&ps);

return 0;
}


long fnWndProc_OnDestroy(lpWndEventArgs Wea)             //Message Handler For WM_DESTROY Message
{
ProgramData* pPD=NULL;
std::wstring* pStr;

pStr=(std::wstring*)GetWindowLong(Wea->hWnd,0);
delete pStr;
pPD=(ProgramData*)GetWindowLong(Wea->hWnd,4);
if(pPD)
    GlobalFree(pPD);
long iCntr=GetClassLong(Wea->hWnd,0);
iCntr--;
if(iCntr==0)
{
    MessageBox(Wea->hWnd,_T("No More Windows Left!"),_T("Will Now Close..."),MB_OK);
    PostQuitMessage(0);
}
else
    SetClassLong(Wea->hWnd,0,iCntr);

return 0;
}


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea;

for(unsigned int i=0; i<dim(EventHandler); i++)
{
     if(EventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(&Wea);
     }
}

return (DefWindowProc(hwnd, msg, wParam, lParam));
}


int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("ProgEx39d");
TCHAR szCaption[80];
WNDCLASSEX wc;
MSG messages;
HWND hWnd;

wc.lpszClassName=szClassName,                          wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof (WNDCLASSEX),                         wc.style=CS_DBLCLKS;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION),               wc.hInstance=hIns;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION),            wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH),  wc.cbWndExtra=8;
wc.lpszMenuName=NULL,                                  wc.cbClsExtra=4;
RegisterClassEx(&wc);
_tcscpy(szCaption,_T("Try Typing Text, Moving Mouse, Clicking Left Mouse Button, Or Sizing Window!"));
for(unsigned i=0; i<4; i++)
{
     hWnd=CreateWindowEx(0,szClassName,szCaption,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,325,300,0,0,hIns,0);
     ShowWindow(hWnd,iShow);
}
while(GetMessage(&messages,NULL,0,0))
{
    TranslateMessage(&messages);
    DispatchMessage(&messages);
}

return messages.wParam;
}



//Header File For Above

//ProgEx39d.h
#ifndef ProgEx39d_h
#define ProgEx39d_h
#include <Windows.h>
#define dim(x) (sizeof(x) / sizeof(x[0]))

typedef struct WindowsEventArguments
{
HWND                         hWnd;
WPARAM                       wParam;
LPARAM                       lParam;
HINSTANCE                    hIns;
}WndEventArgs, *lpWndEventArgs;

long fnWndProc_OnCreate       (lpWndEventArgs Wea);
long fnWndProc_OnSize         (lpWndEventArgs Wea);
long fnWndProc_OnChar         (lpWndEventArgs Wea);
long fnWndProc_OnMouseMove    (lpWndEventArgs Wea);
long fnWndProc_OnLButtonDown  (lpWndEventArgs Wea);
long fnWndProc_OnPaint        (lpWndEventArgs Wea);
long fnWndProc_OnDestroy      (lpWndEventArgs Wea);

struct EVENTHANDLER
{
unsigned int                 iMsg;
long                         (*fnPtr)(lpWndEventArgs);
};

const EVENTHANDLER EventHandler[]=
{
{WM_CREATE,                  fnWndProc_OnCreate},
{WM_SIZE,                    fnWndProc_OnSize},
{WM_CHAR,                    fnWndProc_OnChar},
{WM_MOUSEMOVE,               fnWndProc_OnMouseMove},
{WM_LBUTTONDOWN,             fnWndProc_OnLButtonDown},
{WM_PAINT,                   fnWndProc_OnPaint},
{WM_DESTROY,                 fnWndProc_OnDestroy}
};

struct ProgramData
{
short int                    xMouse;
short int                    yMouse;
short int                    xSize;
short int                    ySize;
short int                    xButton;
short int                    yButton;
};
#endif


Frederick J. Harris

Here is a PowerBASIC (v10.02) version of the above.  There are some very minor differences.  For example I used HeapAlloc() instead of GlobalAlloc(), and ProgramData is named InstanceData here.  Otherwise - very close.  Size wise this is coming in 14 K instead of the 62 K of the C++ version above.  The C++ String class is really bloating it. 


#Compile Exe   "Form6"          'uses PowerBASIC includes
#Include       "Win32api.inc"

Type MessageHandler             'This Type Associates a message with the
  wMessage As Long              'address of the message handler which
  fnPtr    As Dword             'handles it.  Messages are usually fairly
End Type                        'low numbers, e.g., WM_PAINT is 15.

Type WndEventArgs               'Type for passing window procedure parameters.  Allows me
  wParam           As Long      'to shorten parameter list.  .NET does this all the time.
  lParam           As Long      'See, for example pea for PaintEventArgs in the OnPaint()
  hWnd             As Dword     'Message Handler.
End Type

Type tagInstanceData            'User Defined Type for persisting or storing message
  wWidth   As Word              'information across invocations of fnWndProc.  It is
  wHeight  As Word              'necessary to persist this data because such items of
  wX       As Word              'information as, for example, the window height and
  wY       As Word              'width would be lost after they were obtained in a
  xPos     As Word              'WM_SIZE message after that message handler was exited.
  yPos     As Word              'Then, when a WM_PAINT message was received and it was
  wCharHt  As Word              'desired to update the window with this newly acquired
  szText   As Asciiz*128        'information, it would be gone.
End Type

'Model procedure showing function signature of all message handlers.
Declare Function fnPtr(wea As WndEventArgs) As Long


Function fnWndProc_OnCreate(wea As WndEventArgs) As Long
  Local ptrInstanceData As tagInstanceData Ptr
  Local tm As TEXTMETRIC        'When the main program window is created
  Local hHeap As Dword          'HeapAlloc() is called and a memory request is
  Local hDC As Dword            'made for enough bytes to store a tagInstanceData
                                'Type.  If HeapAlloc()returns a non-zero number
  hHeap=GetProcessHeap()        '(a valid address), a call to GetTextMetrics() is
  ptrInstanceData= _            'made to obtain the height of a character for text
  HeapAlloc _                   'spacing purposes.  Then a Call to SetWindowLong()
  ( _                           'stores the address of the memory acquired by
    hHeap, _                    'HeapAlloc() into the four cbWndExtra bytes of
    %HEAP_ZERO_MEMORY, _        'extra window storage in the Window Class
    SizeOf(tagInstanceData) _   'structure requested when the "Form2B" Class was
  )                             'Registered in WinMain().
  If ptrInstanceData Then
     hDC=GetDC(wea.hWnd)
     Call GetTextMetrics(hDC,tm)
     Call ReleaseDC(wea.hWnd,hDC)
     @ptrInstanceData.wCharHt=tm.tmHeight
     Call SetWindowLong(wea.hWnd,0,ptrInstanceData)
     Local iCntr As Long
     iCntr=GetClassLong(wea.hWnd,0)
     Incr iCntr
     Call SetClassLong(wea.hWnd,0,iCntr)
  Else
     fnWndProc_OnCreate=-1
  End If

  fnWndProc_OnCreate=0
End Function


Function fnWndProc_OnMouseMove(wea As WndEventArgs) As Long  'If a WM_MOUSEMOVE
  Local ptrInstanceData As tagInstanceData Ptr   'message is received a pointer
                                                 'to a tagInstanceData Type is
  ptrInstanceData= _                             'declared.  This variable is
  GetWindowLong _                                'initialized with the address
  ( _                                            'stored in the cbWndExtra bytes.
    wea.hWnd, _                                  'The mouse coordinates are
    0 _                                          'extracted from the lParam
  )                                              'parameter and 'persisted'
  @ptrInstanceData.wX=LoWrd(wea.lParam)          'in the allocated memory using
  @ptrInstanceData.wY=HiWrd(wea.lParam)          'pointer indirection techniques.
  Call InvalidateRect _                          'Finally, an InvalidateRect()
  ( _                                            'Call is made.  This will force
    wea.hWnd, _                                  'a WM_PAINT message to be sent
    ByVal %NULL, _                               'to this Window Procedure in a
    %TRUE _                                      'completely separate WndProc()
  )                                              'Call or invocation.  At that
                                                 'point in the WM_PAINT handler
  fnWndProc_OnMouseMove=0                        'the mouse coordinates will be
End Function                                     'extracted and displayed.


Function fnWndProc_OnSize(wea As WndEventArgs) As Long  'The same pattern
  Local ptrInstanceData As tagInstanceData Ptr   'described just above for the
                                                 'WM_MOUSEMOVE message will be
  ptrInstanceData= _                             'repeated here in WM_SIZE logic
  GetWindowLong _                                'and, in all the other message
  ( _                                            'handlers.  The only thing that
    wea.hWnd, _                                  'will be different is the data
    0 _                                          'transferred in the wParam and
  )                                              'lParam variables, as that is
  @ptrInstanceData.wWidth=LoWrd(wea.lParam)      'dependent on the message
  @ptrInstanceData.wHeight=HiWrd(wea.lParam)     'itself.  The repeating pattern
  Call InvalidateRect(wea.hWnd,ByVal %NULL,%TRUE)'will be to obtain the pointer
                                                 'from the cbWndExtra bytes,
  fnWndProc_OnSize=0                             'store data there, then force
End Function                                     'a paint msg. to display data.


Function fnWndProc_OnChar(wea As WndEventArgs) As Long
  Local ptrInstanceData As tagInstanceData Ptr

  ptrInstanceData=GetWindowLong(wea.hWnd,0)
  @ptrInstanceData.szText=@ptrInstanceData.szText+Chr$(wea.wParam)
  Call InvalidateRect(wea.hWnd,ByVal %NULL,%TRUE)

  fnWndProc_OnChar=0
End Function


Function fnWndProc_OnLButtonDown(wea As WndEventArgs) As Long
  Local ptrInstanceData As tagInstanceData Ptr

  ptrInstanceData=GetWindowLong(wea.hWnd,0)
  If wea.wParam=%MK_LBUTTON Then
     @ptrInstanceData.xPos=LoWrd(wea.lParam)
     @ptrInstanceData.yPos=HiWrd(wea.lParam)
     Call InvalidateRect(wea.hWnd,ByVal 0,%TRUE)
  End If

  fnWndProc_OnLButtonDown=0
End Function


Function fnWndProc_OnPaint(wea As WndEventArgs) As Long
  Local ptrInstanceData As tagInstanceData Ptr
  Local szLine As Asciiz*64
  Local ps As PAINTSTRUCT     'This procedure will be called right after
  Local hDC As Long           'window creation, and every time an
                              'InvalidateRect() call is made when the mouse is
  hDC=BeginPaint(wea.hWnd,ps) 'moved over the window, the window is being
  ptrInstanceData=GetWindowLong(wea.hWnd,0)       'resized, a char key is
  szLine= _                                       'pressed, or a left mouse down
  "MouseX="+Trim$(Str$(@ptrInstanceData.wX)) & _  'is received.  Before the
  "  MouseY="+Trim$(Str$(@ptrInstanceData.wY))    'InvalidateRect() call is made
  TextOut(hDC,0,0,szLine,Len(szLine))             'the data from the specific
  szLine= _                                       'handler that is to be
  "@ptrInstanceData.wWidth="+ _                   'persisted across WndProc()
  Trim$(Str$(@ptrInstanceData.wWidth)) & _        'invocations will be stored in
  " @ptrInstanceData.wHeight=" + _                'the memory pointed to by the
  Trim$(Str$(@ptrInstanceData.wHeight))           'cbWndExtra bytes pointer.
  TextOut _                                       'That pointer will then be
  ( _                                             'retrieved here in WM_PAINT,
    hDC, _                                        'and the data in its totality
    0, _                                          'displayed to the window.  It
    16, _                                         'is one awesome machine.
    szLine, _
    Len(szLine) _
  )
  TextOut(hDC,0,32,@ptrInstanceData.szText,Len(@ptrInstanceData.szText))
  If @ptrInstanceData.xPos<>0 And @ptrInstanceData.yPos<>0 Then
     szLine= _
     "WM_LBUTTONDOWN At (" & Trim$(Str$(@ptrInstanceData.xPos)) & _
     "," & Trim$(Str$(@ptrInstanceData.yPos)) & ")"
     Call TextOut _
     ( _
       hDC, _
       @ptrInstanceData.xPos, _
       @ptrInstanceData.yPos, _
       szLine, _
       Len(szLine) _
     )
     @ptrInstanceData.xPos=0 : @ptrInstanceData.yPos=0
  End If
  Call EndPaint(wea.hWnd,ps)

  fnWndProc_OnPaint=0
End Function


Function fnWndProc_OnDestroy(Wea As WndEventArgs) As Long  'The most noteworthy
  Local ptrInstanceData As tagInstanceData Ptr   'event that must occur here is
  Local hHeap As Dword,blnFree As Long           'that the memory allocated in
  Local iCtr As Long                             'WM_CREATE, a pointer to which
                                                 'is stored in the cbWndExtra
  hHeap=GetProcessHeap()                         'bytes, must be released or
  ptrInstanceData=GetWindowLong(wea.hWnd,0)      'freed.  HeapFree() is used for
  If ptrInstanceData Then                        'that purpose.  Note that since
     blnFree=HeapFree(hHeap,0,ptrInstanceData)   'four windows of class Form6
     MsgBox _                                    'were created in WinMain(), and
     ( _                                         'four separate memory allocations
       "blnFree=" & _                            'were made for the four WM_CREATE
       Trim$(Str$(blnFree)) _                    'messages, four deallocations
     )                                           'will need to be done - one for
     MsgBox _                                    'each window's data.  A counter
     ( _                                         'variable was stored in the class
       "Always Make Sure Memory Deallocates!  Memory Leaks Are Bad!" _
     )                                           'extra bytes to keep track of how
  End If                                         'many windows of the class were
  iCtr=GetClassLong(Wea.hWnd,0)                  'created, and when that counter
  Decr iCtr                                      'decrements to zero through
  If iCtr=0 Then                                 'WM_DESTROY messages, a
     MsgBox("Will Be Calling PostQuitMessage()")
     Call PostQuitMessage(0)                     'PostQuitMessage() needs to be
  Else                                           'stuck in the program's message
     Call SetClassLong(Wea.hWnd,0,iCtr)          'quene so the thing won't hang
  End If                                         'in memory in a loop with no
                                                 'live windows.
  fnWndProc_OnDestroy=0
End Function


Sub AttachMessageHandlers()                'Here is where the numeric value of a specific
  Dim MsgHdlr(6) As Global MessageHandler  'message that is going to be handled is associated
                                           'with the actual address of the message handler.
  MsgHdlr(0).wMessage=%WM_CREATE      : MsgHdlr(0).fnPtr=CodePtr(fnWndProc_OnCreate)
  MsgHdlr(1).wMessage=%WM_MOUSEMOVE   : MsgHdlr(1).fnPtr=CodePtr(fnWndProc_OnMouseMove)
  MsgHdlr(2).wMessage=%WM_SIZE        : MsgHdlr(2).fnPtr=CodePtr(fnWndProc_OnSize)
  MsgHdlr(3).wMessage=%WM_CHAR        : MsgHdlr(3).fnPtr=CodePtr(fnWndProc_OnChar)
  MsgHdlr(4).wMessage=%WM_LBUTTONDOWN : MsgHdlr(4).fnPtr=CodePtr(fnWndProc_OnLButtonDown)
  MsgHdlr(5).wMessage=%WM_PAINT       : MsgHdlr(5).fnPtr=CodePtr(fnWndProc_OnPaint)
  MsgHdlr(6).wMessage=%WM_DESTROY     : MsgHdlr(6).fnPtr=CodePtr(fnWndProc_OnDestroy)
End Sub


Function WndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
  Local wea As WndEventArgs  'Every time a message is retrieved from the message
  Register iReturn As Long   'loop in WinMain() and ends up here, this For loop
  Register i As Long         'will cycle through all the messages it wants to
                             'handle to see if it can make a match.  If it makes
  For i=0 To 6               'a match the message handler will be called through
    If wMsg=MsgHdlr(i).wMessage Then     'a UDT array of function pointers.
       wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
       Call Dword MsgHdlr(i).fnPtr Using fnPtr(wea) To iReturn
       WndProc=iReturn
       Exit Function
    End If
  Next i

  WndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
End Function


Function WinMain(ByVal hIns As Long,ByVal hPrevIns As Long,ByVal lpCmdLine As Asciiz Ptr,ByVal iShow As Long) As Long
  Local szClassName As Asciiz*16
  Local wc As WndClassEx
  Local hWnd,i As Dword
  Local Msg As tagMsg

  Call AttachMessageHandlers()  'Attach User Defined Type array of messages and function pointers.
  szClassName="Form6"           'Note that in the WndClassEx Type the cbWndExtra bytes were set to four.
  wc.cbSize=SizeOf(wc)                               : wc.style=0
  wc.lpfnWndProc=CodePtr(WndProc)                    : wc.cbClsExtra=4
  wc.cbWndExtra=4                                    : wc.hInstance=hIns
  wc.hIcon=LoadIcon(%NULL,ByVal %IDI_APPLICATION)    : wc.hCursor=LoadCursor(%NULL,ByVal %IDC_ARROW)
  wc.hbrBackground=GetStockObject(%WHITE_BRUSH)      : wc.lpszMenuName=%NULL
  wc.lpszClassName=VarPtr(szClassName)               : wc.hIconSm=LoadIcon(%NULL,ByVal %IDI_APPLICATION)
  Call RegisterClassEx(wc)
  For i=1 To 4  'create four main program windows
    hWnd=CreateWindowEx(0,szClassName,"Form6",%WS_OVERLAPPEDWINDOW,%CW_USEDEFAULT,%CW_USEDEFAULT,325,300,%HWND_DESKTOP,0,hIns,ByVal 0)
    Call ShowWindow(hWnd,iShow)
  Next i
  While GetMessage(Msg,%NULL,0,0)
    Call TranslateMessage(Msg)
    Call DispatchMessage(Msg)
  Wend

  WinMain=msg.wParam
End Function