Path to learn Windows API

Pages: 12
This is an independently published book. I'm often sceptical about these books (why not a publisher - have they rejected it?). But it gets good reviews (4.9/5 from 23 - all 4/5 stars) so I might get a copy. From the index it looks like it's an updated version of the Richter books.
The best way to learn Windows API is to struggle trough MSDN documentation...

In the beginning that's hard, but as you move on from ex. implementing a naked window toward putting some controls on it, things become familiar and easier.

When ever you stuck and don't find information on MSDN docs, simply use google to find out possible solution.
By googling out you don't look for complete solution or sample code (because there often isn't any) but rather look for "search keywords" that will lead to better search results or specific MSDN documentation or maybe even some samples.

There is no better source for starter than petzhold's book, but if you want an easy startup but avoid petzhold then sites such as the following golden ones will help you:
http://winapi.foosyerdoos.org.uk/index.php
http://www.winprog.org/tutorial/
https://docs.microsoft.com/en-us/windows/win32/learnwin32/learn-to-program-for-windows?redirectedfrom=MSDN
http://www.catch22.net/tuts/

To get most out of tutorials on these sites, they should be followed by 3 things:
1. MSDN docs
2. practice
3. re-read and practice tuts again
Also have a look at

http://www.flounder.com/index.htm

and in particular MVP tips
Most of us here are pretty entrenched in our views, but for the sake of any newcomers who might not be, and
for the original poster who is honestly looking for new information to guide his studies, let's take a look at
what is actually going on with Windows Class Frameworks, as opposed to what I term as the SDK style of
Windows coding (Petzold's style). To that end let's start with a very basic and simple SDK style program -
one with just an edit control and a button on it which displays in a MessageBox whatever is typed in the edit
control, and let's write our own basic Class Framework to elucidate the basic mechanisms of what's going
on with Class Frameworks, and then recode the original SDK style app using our new Class Framework.

Here is the SDK style code for the simple app described above….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// cl Form2.cpp /O1 /Os /link Kernel32.lib User32.lib                       // 91,648 Bytes
// g++ Form2.cpp -luser32 -lkernel32 -oForm2_gcc.exe -mwindows -m64 -s -Os  // 17,920 Bytes
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#include <windows.h>
#define  IDC_BUTTON 1500   // Constant Numeric Identifier For Child Window "Retrieve Text" Button
#define  IDC_EDIT   1505   // Constant Numeric Identifier For Child Window Edit Control


LRESULT CALLBACK fnWndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 switch(msg)                                                
 {                                                          
   case WM_CREATE: // This message Received One Time and Is A C Based Object Constructor Call.  This code Actually Runs
     {             // 'Inside' The CreateWindow() Call Down In WinMain() Which Creates The Main App Window.  When Zero Is                                                       
        HINSTANCE hIns = NULL;                     // Returned Just Below, The CreateWindow() Call In WinMain() terminates,
        hIns=((LPCREATESTRUCT)lParam)->hInstance;  // and Execution Of ShowWindow() Occurs.
        CreateWindowEx(WS_EX_CLIENTEDGE,L"edit",L"",WS_CHILD|WS_VISIBLE,50,40,200,25,hWnd,(HMENU)IDC_EDIT,hIns,0);
        CreateWindowEx(0,L"button",L"Retrieve Text",WS_CHILD|WS_VISIBLE,95,90,120,30,hWnd,(HMENU)IDC_BUTTON,hIns,0);
        return 0;
     }   
   case WM_COMMAND:   // Child Windows (edit and button control in this app) Communicate With Their Parent (Main App Window) By                                      
     {                // Sending WM_COMMAND Messages To Their Parent.  Message Specifics (What The User Did With The Control)
        wchar_t szBuffer[256];   // Are Packaged Within The WPARAM And LPARAM Parameters Of The Window Procedure.                                       
        if(LOWORD(wParam)==IDC_BUTTON && HIWORD(wParam)==BN_CLICKED)  
        {                                                             
           GetWindowText(GetDlgItem(hWnd,IDC_EDIT),szBuffer,256); // GetDlgItem() Retrieves The HWND Of A Child Window Control
           MessageBox(hWnd,szBuffer,L"Button Click",MB_OK);       // Given Its Parent And Constant Numeric Define.  GetWindowText()   
        }                                                         // Retrieves The Text From A Control.   
        return 0;	  
     } 
   case WM_DESTROY:                                         
     {                         // When PostQuitMessage() Just Left Executes, The Message Pump Down In WinMain()
        PostQuitMessage(0);    // Terminates And The App Ends.                                                    
        return 0;   	
     }
 }                                                            

 return (DefWindowProc(hWnd, msg, wParam, lParam)); // Default Processing For Messages Not Handled Here.
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 wchar_t szClassName[] =L"Form2";              
 HWND hWnd = NULL;                              
 WNDCLASSEX wc;                                 
 MSG messages;                                  
                                                
 memset(&wc,0,sizeof(wc));                                          
 wc.lpszClassName = szClassName;                
 wc.lpfnWndProc   = fnWndProc;                  
 wc.cbSize        = sizeof(WNDCLASSEX);         
 wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;    
 wc.hInstance     = hInstance;                  
 RegisterClassEx(&wc);                          
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,200,175,320,200,HWND_DESKTOP,0,hInstance,0);
 ShowWindow(hWnd,iShow);                        
 while(GetMessage(&messages,NULL,0,0))          
 {                                       
    TranslateMessage(&messages);         
    DispatchMessage(&messages);          
 }
                                         
 return messages.wParam;
}


Just briefly, the three steps necessary for an SDK style Windows GUI program are...

1) Creation of a Window Class. This is done by declaring a WNDCLASS or WNDCLASSEX object
and filling out a few essential members, such as the name of the class and the Window Procedure
address which Windows the OS will call to inform any instantiated objects of user or system
messages/events, and finally calling RegisterClass() or RegisterClassEx() to inform Windows of the
new class;

2) Calling a 'creation' function. That would be the CreateWindowEx() call in WinMain() which
instantiates one object of the registered class;

3) Dropping into a 'message loop' where the program continually monitors the operating system for
messages/events pertinent to the program.

Note in my above description I specifically used OOP (Object Oriented Programming) terminology to
describe those three steps. The reason I did that was to emphasize that what one is seeing here is Object
Oriented Programming done in C. Realize that when the Windows Application Programming Interface was
first created back around 1984/1985, C++ was in its infancy and was not really a mainstream programming
language yet. I have always suspected that one of the reasons new C++ coders (and maybe even seasoned
ones) are so antagonistic to direct use of the Windows Api is that they have failed to recognize it's object
oriented nature because it is C based. Think about it for just a moment. Just about every GUI based
Windows Api call has for its first parameter a HANDLE to something or other - usually a HWND. This
corresponds exactly to the 'this' pointer in C++ objects. Simply put, it's what C based OOP looks like. In my
opinion, the Windows Api is an extremely elegant OOP design.

Continued...
Now let's build our own class framework to duplicate the above SDK code. First I'll present our Main.cpp
file...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//Main.cpp
//cl Main.cpp CWinClass.cpp CWindow.cpp CButton.cpp CEdit.cpp CForm2.cpp /O1 /Os /FeForm2.exe Kernel32.lib User32.lib
#include "CFrameWork.h"  // 96,256 bytes

LRESULT CALLBACK Form2_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 switch(msg)
 {
   case WM_COMMAND:
     return CForm5::OnCommand(hWnd,wParam,lParam);
   case WM_DESTROY:
     return CForm5::OnDestroy(hWnd);
 }

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

int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 CWinClass wc(Form2_WndProc,L"Form2",0,sizeof(void*),(HBRUSH)(COLOR_WINDOW+1),hIns);
 CForm2    pApp(iShow,L"Form2",L"Form2",WS_OVERLAPPEDWINDOW,200,200,330,135,0,(HMENU)0,hIns,0);
 return pApp.Run();
}


Recall I showed the three essential steps for an SDK style Windows GUI app? They are actually illustrated
very well I have to grudgingly admit in my Main.cpp file just above. I'm counting 21 lines of code as
opposed to the 68 lines of code of my first Form2 SDK project. Big improvement, right? Sure, if you are
an ostrich and your head is buried in the sand and can't see and ignores what's being pulled into the code by
the single #include "CFrameWork.h"...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//CFramework.h
#ifndef CFrameWork_h
#define CFrameWork_h
#ifndef UNICODE
#define UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include "CWinClass.h"
#include "CButton.h"
#include "CEdit.h"
#include "CWindow.h"
#include "CForm2.h"
#endif 


So we'll turn to those files to see where the devil is hiding. The first step was creating a Window Class and
that is wrapped up nicely in my CWinClass.h and CWinClass.cpp files...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//CWinClass.h      
#ifndef CWinClass_h
#define CWinClass_h

class CWinClass
{
 public:
 CWinClass(){}
 CWinClass(WNDPROC fnWndProc, LPCWSTR szClassName, int cbClsExtra,  int cbWndExtra, HBRUSH hBackColor, HINSTANCE hInst);

 private:
 WNDCLASSEX wc;
};
#endif 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//CWinClass.cpp     
#ifndef UNICODE
#define UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include "CWinClass.h"

CWinClass::CWinClass(WNDPROC fnWndProc, LPCWSTR szClassName, int cbClsExtra,  int cbWndExtra, HBRUSH hBackColor, HINSTANCE 
hInst)
{
 wc.cbSize        = sizeof(WNDCLASSEX);
 wc.style         = 0;
 wc.lpfnWndProc   = fnWndProc;
 wc.cbClsExtra    = cbClsExtra;
 wc.cbWndExtra    = cbWndExtra;
 wc.hInstance     = hInst;
 wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
 wc.hIconSm       = 0;
 wc.hCursor       = LoadCursor (0, IDC_ARROW);
 wc.hbrBackground = hBackColor;
 wc.lpszMenuName  = 0;
 wc.lpszClassName = szClassName;
 RegisterClassEx(&wc);
}


My steps two and three - creating a window and falling into the 'message pump', can be seen in CWindow.h
and CWindow.cpp....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//CWindow.h        
#ifndef CWindow_h
#define CWindow_h

class CWindow
{
 public:
 CWindow(){}
 CWindow(int iShow, LPCTSTR szClassName, LPCTSTR szCaption, DWORD dwStyle, int x, int y, int iWidth, int iHeight, HWND hParent, HMENU hMenu, HINSTANCE hIns, void* lpCreateParams);
 HWND Window(void)  {return this->m_hWnd;}
 WPARAM Run(void);
 virtual ~CWindow(){}

 protected:
 HINSTANCE     m_hInst;
 HWND          m_hWnd;
};
#endif 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//CWindow.cpp   
#ifndef UNICODE
#define UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include <cstdio>
#include "CWindow.h"

CWindow::CWindow(int iShow, LPCTSTR szClassName, LPCTSTR szCaption, DWORD dwStyle, int x, int y, int iWidth, int iHeight, HWND hParent, HMENU hMenu, HINSTANCE hIns, void* lpCreateParams)
{
 this->m_hWnd=CreateWindowEx(0,szClassName,szCaption,dwStyle,x,y,iWidth,iHeight,hParent,(HMENU)hMenu,hIns,lpCreateParams);
 this->m_hInst=hIns;
 ShowWindow(this->m_hWnd,iShow);
 UpdateWindow(this->m_hWnd);
}

WPARAM CWindow::Run(void)
{
 MSG msg;

 while(GetMessage(&msg,NULL,0,0))
 {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
 }

 return msg.wParam;
}


If you re-examine the three lines in my Main.cpp WinMain() function you won't find a CWindow class
variable/object. Reason is, like most Class Framework code, I made CWindow a base class. The name of
the app is Form2 so I created a Form2 class which inherits from CWindow. Why make things simple when
you can make them complicated? We're all making the big bucks so why not earn them!

Continued...
So here's the Form2 class (*.h and *.cpp files)...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//CForm2.h
#ifndef CForm2_h
#define CForm2_h
#include "CButton.h"
#include "CEdit.h"
#define  IDC_TEXT     1500
#define  IDC_BUTTON   1505

class CForm2 : public CWindow      //It makes sense to inherit from a base class the types of
{                                  //functionality (member functions) and data members that
 public:                           //will be common to all objects such as the necessity for
 CForm2(){}                        //a CreateWindow() call to create a main program window,
                                   //and a HWND for the main program window.  The things that
 CForm2                            //will make the application or window unique though can go
 (                                 //in a derived class such as this one.  For example, in the
  int iShow,                       //CForm5 Class we'll want a CButton member and a CEdit
  wchar_t const* szClassName,      //member.  Also, we can include here the message handling
  wchar_t const* szCaption,        //functions for the class if we like.
  DWORD dwStyle,
  int x,
  int y,
  int nWidth,
  int nHeight,
  HWND hParent,
  HMENU hMenu,
  HINSTANCE hIns,
  void* lpCreateParams
 );
 static LRESULT OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam);
 static LRESULT OnDestroy(HWND hWnd);
 virtual ~CForm2(void){}

 protected:
 int             m_nCmdShow;
 private:
 CButton*        m_pbtn;            //This class member wraps a Win32 "button" object
 CEdit*          m_ptxt;            //This class member wraps a Win32 "edit" object
};
#endif 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//CForm2.cpp
#ifndef UNICODE
#define UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include <cstdio>
#include "CWindow.h"
#include "CForm2.h"

CForm2::CForm2              //This is the constructor for our CForm5 class.  All the parameters
(                           //of a CreateWindow() call are passed in, plus the iShow parameter
  int iShow,                //from WinMain.  Note in this implementation file in this CForm5
  LPCWSTR szClassName,      //constructor we have a Constructor Initialization List comprising
  LPCWSTR szCaption,        //of only one element and that is a call on the CWindow() constructor
  DWORD dwStyle,            //which initializes the 'base' part of a CForm2 object.  All the
  int x,                    //parameters of the CForm5 constructor are simply passed in as is.
  int y,                    //After the CWindow() constructor returns and the body of the CForm5
  int nWidth,               //constructor is entered, the most noteworthy thing that happens is
  int nHeight,              //that a pointer to the object, i.e., this, is stored at offset zero
  HWND hParent,             //in the already created WNDCLASSEX::cbWndExtra bytes.
  HMENU hMenu,
  HINSTANCE hIns,
  void* lpCreateParams
 ):CWindow(iShow,szClassName,szCaption,dwStyle,x,y,nWidth,nHeight,hParent,hMenu,hIns,lpCreateParams)
{
  SetWindowLongPtr(this->m_hWnd,0,(LONG_PTR)this);
  this->m_nCmdShow=iShow;
  this->m_pbtn=new CButton((wchar_t*)L"Get Text",115,55,80,30,this->m_hWnd,(HMENU)IDC_BUTTON,this->m_hInst);
  this->m_ptxt=new CEdit((wchar_t*)L"",15,15,285,22,this->m_hWnd,(HMENU)IDC_TEXT,this->m_hInst);
}

LRESULT CForm2::OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
 CForm2* pForm2=NULL;

 pForm2=(CForm2*)GetWindowLongPtr(hWnd,0);
 switch(LOWORD(wParam))
 {
  case IDC_BUTTON:
    {
       wchar_t szBuffer[256];
       pForm2->m_ptxt->GetText(szBuffer,256);
       MessageBox(hWnd,szBuffer,L"Here Is Your Text",MB_OK);
    }
    break;
 }

 return 0L;
}

LRESULT CForm2::OnDestroy(HWND hWnd)
{
 CForm2* pForm2=(CForm2*)GetWindowLongPtr(hWnd,0);
 delete pForm2->m_pbtn;
 delete pForm2->m_ptxt;
 PostQuitMessage(0);

 return 0L;
}


We're almost done. Just need a CButton and a CEdit class to wrap button and edit controls....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//CButton.h             
#ifndef CButton_h
#define CButton_h
#include <windows.h>

class CButton
{
 public:
 CButton(LPCWSTR lpCaption, int x, int y, int cx, int cy, HWND hParent, HMENU iCtrlId, HINSTANCE hIns);  
 ~CButton(){}

 private:
 HWND m_hBtn;
};
#endif 


1
2
3
4
5
6
7
8
9
10
11
12
//CButton.cpp         
#ifndef UNICODE
#define UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include "CButton.h"

CButton::CButton(LPCTSTR lpCaption, int x, int y, int cx, int cy, HWND hParent, HMENU iCtrlId, HINSTANCE hIns) //CButton Constructor
{
 this->m_hBtn=CreateWindowEx(0,L"button",lpCaption,WS_CHILD|WS_VISIBLE,x,y,cx,cy,hParent,iCtrlId,hIns,0);
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//CEdit.h 
#ifndef CEdit_h
#define CEdit_h

class CEdit    //CEdit
{
 public:
 CEdit(LPWSTR lpCaption, int x, int y, int cx, int cy, HWND hParent, HMENU iCtrlId, HINSTANCE hIns);
 void SetText(wchar_t* lpszText);
 void GetText(LPWSTR lpszBuffer, int iLenBuf);
 ~CEdit(){}   //CEdit Destructor

 private:
 HWND m_hEdit;
};
#endif 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//CEdit.cpp
#ifndef UNICODE
#define UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include "CEdit.h"

CEdit::CEdit(LPWSTR lpCaption, int x, int y, int cx, int cy, HWND hParent, HMENU iCtrlId, HINSTANCE hIns)  //CEdit Constructor
{
 this->m_hEdit=CreateWindowEx(WS_EX_CLIENTEDGE,L"edit",lpCaption,WS_CHILD|WS_VISIBLE,x,y,cx,cy,hParent,(HMENU)iCtrlId,hIns,0);
}

void CEdit::SetText(LPWSTR lpszText)
{
 SetWindowText(this->m_hEdit,lpszText);
}

void CEdit::GetText(LPWSTR lpszBuffer, int iLenBuf)
{
 GetWindowText(this->m_hEdit,lpszBuffer,iLenBuf);
}


So there it all is. My first SDK style code comprising one file with 68 lines of code and then the same exact
app in Class Framework form comprising 11 files and 274 lines of code. But a class framework enthusiast
might say "Look how much more the Class Framework code does!". Well, it doesn't do one iota more than
the SDK style code does but it takes 10 files and 206 more lines of code to do it!

Continued...
Having said all that one might assume I'm not in favor of anyone using GUI Class Frameworks to code
Windows GUI apps. Well, not true. I think there's a place for them for sure. A bit of a digression...

I'm a COM guy. I love COM (Microsoft's Component Object Model). I first used Windows back around
Windows 95, having moved off DOS very slowly. Back in the 16 bit DOS world I used a bunch of different
BASIC and C based languages. When I got Windows 95 I got Visual Basic 4, then later VB5 and 6 by the
late 90s. Starting with VB5 one could create ActiveX Controls. And it became clear to me that Visual Basic
was COM through and through. I knew COM and OLE was going to be 'my thing'. The only downside for
me of Windows and Windows programming was that I thought Visual Basic and OOP were pretty bloated
stuff.

Then I discovered Petzold and C programming in the Windows environment. Also at that time I discovered
the PowerBASIC programming language and that one of the coders in that 'world' had converted Petzold's
Windows 95 book's code to PowerBASIC. Being the code junkie that I am I lived and breathed Windows
Api code in C and PowerBASIC for years it seemed. I expect that some C++ coders here might be surprised
to know that the Windows Api is usable by multiple programming languages - not just C and C++.
Eventually I taught myself C++ and experimented with MFC and other class frameworks, but I developed an
intense dislike for them as they all seemed like grunge code to me totally lacking in the elegance of the
minimalist SDK Api code a la Petzold. Using MFC though one had a visual RAD drag and drop
programming environment where one could use the fancy ActiveX Controls I had become familiar with
through Visual Basic. But how could one utilize these fancy high powered controls in a Petzold SDK style
app? Of course I tried ATL, which was the C++ coders answer to the problem, but like with MFC I didn't
like it. And I was doing more coding in PowerBASIC than in C or C++ at the time, and ATL would be of no
hope there. And anyway, I'm a purist. I write my own code, and won't use visual designer auto-generated
code. None at all. So to make a long story short, how do you think I finally learned how to utilize ActiveX
Controls in a C, C++ or PowerBASIC SDK app? Well, the answer is I threw a decade at it learning the nitty
gritty details of low level COM.

That's where I feel C++ Class Frameworks have their place, as I realize that there are very, very few C++
coders or coders of any language who can throw a decade at learning some technology like I did in order to
write an application, when one can, for example in the case of an ActiveX Control, use a visual designer
where it takes only a few seconds to get such functionality into one's application complete with auto-
generated code ready to go. And I'm only using COM, OLE, and ActiveX Controls as one example. I
expect the modern graphics libraries (of which I know nothing - I'm not a graphics guy) might be another
example.

The other instance where Class Frameworks have their place is cross platform development, as other posters
have mentioned. In my specific case all my development work occurred in the Windows environment, so
cross platform was of no use to me. But being the purest that I am, had I wanted my apps to work in Linux
for example, I'd have chosen what seemed to me as the best libraries on that platform.

But there's no such a thing as a free lunch in this world. When one erects one software layer on top of
another that's actually doing the work, something is going to be lost (and something gained too – bloat). So
I'm not really amenable to change or compromise on this subject. I'm old, retired, and likely won't last
much longer. It looks like direct use of the Windows Api will go a few more years anyway – maybe longer
than I. So I'm good.

Coding has always been something of an art form to me. I guess I've been fortunate to not have had to
endure coding standards, programming languages, libraries, or styles imposed upon me by an employer, for
example. One of my quirks, stemming no doubt from my age and having started when computers had
virtually no memory by today's standards, is that I like small binaries. And I can't defend that. It's just
simply my 'art'. It's the way I think and work, and the product of my coding labors would give me no
pleasure if it were otherwise.
freddie1,

My first SDK style code comprising one file with 68 lines of code and then the same exact
app in Class Framework form comprising 11 files and 274 lines of code.


I understand your point which is that SDK style programing takes much less LOC, but your bare comparison of LOC with framework style isn't fair.

To understand why it's not fair let's break both styles down into following paradigmas:
1. Reusability (reusable code)
2. Modularity (modular programming)
3. Generic programming
4. Extensibility

Meaning following:

1. First your SDK style code is not reusable because that example is already complete code ready to be run.
You can't reuse that code in another translation unit because this TU is self sustainable ready to be compiled.

2. Your SDK style code has nothing in common with modular programming, which means it's harder to maintain and as already said in first point it thus not reusable.

3. Your SDK style example is also not generic, window dimensions are hardcoded, there is no way a user could specify dimensions, positions or window or button text, it's all hardcoded.

4. Whoever whishes to extend SDK style code will have to waste much more time to first make it modular which is the first precondition to write reusable code.

Now compared to class framework style, all 4 points are already accomplished.
1. All that one library user has to do is inherit your classes and override default behavior if needed, which satisfies reusability

2. Modularity is also accomplished, because your framework style code is broken down to logical pieces, this helps both the library author and library user to maintain specific code sections as needed without much headache, code breaks or additional work.

3. Your class framework style code is generic, library user can specify dimensions, texts etc. as he desires and it ain't break anything, it will just work.

4. And regarding extensibility, that condition is also meet, all it takes to extend your framework style code it to add new functionalities as needed without defining any new symbols because you're working with classes.

Now if we take these important things into account then those extra LOC and extra files are perfectly justified.
They are perfectly justified also because doing these things in C is in particual hard to do, that's what makes C++ language more desireable tool to implement OOP concepts.

Comparing object oriented C code to OOP C++ is what will make your SDK sample take somewhat more LOC and not only that but the price of doing this stuff in C has one additional price which is readability. I think readability should be a point 5 and it would win against SDK style just like other 4 points.

Your samples are short and thus harder to understand those points, but consider if this were real world code that takes a lot more code, who hard or easy would that be?
I think the comparison would be like library code (framework style) vs (program code) SDK style.
Last edited on
Niccolo stated, with regard to Petzold style SDK code...


The amount of work required just to get a basic application running is significant, and quite boilerplate.

I believe I've definitively shown - and even Malibor begrudgingly gave me that point, that it takes a lot more
lines of code to get a class framework app up and running. In my Form2 example just above the ratio was
274 lines of code compared to 68 lines of code for the identical SDK app. And in a 'real' class framework
implementation I'd postulate that the ratio would be even much higher because don't forget that all the
wrapped objects would have multiple overloads on all constructors, and my code didn't implement that. I
guess in the final analysis it breaks down to whether one feels one is getting one's 'money's worth' with the
cost of the extra lines of code and the big dll one has to drag around with the app's binary. Malibor will open
his wallet and buy in, and I it seems won't.

Niccalo further stated, in criticizing SDK code...

You end up with one large function that responds to messages, and that is an unorganized mess to deal with.

Now that's interesting, and I have to agree with him 110% while at the same time 150% disagreeing with
him! Let me elaborate.

It is true that virtually every bit of SDK code I've ever looked at in my life (and in multiple programming
languages where the Windows API can be accessed) contains this massive and in my mind un-maintainable
window procedure with its switch construct. I'm no better than anyone else and I recall my first Windows
SDK program was about the same - a 1000 line program containing two procedures; about 30 lines in
WinMain() and 970 lines in the WndProc(). But the thing is, this is wholly unnecessary! Why SDK coders -
even really experienced ones, keep doing this is a total mystery to me. It's the easiest thing in the world to
create OnCreate(), OnCommand(), OnPaint(), etc., ad infinitum, procedures in one's SDK code just like one
finds in class framework code. That's what I did in the 'quickie' class framework comparison code I provided
earlier in my Form2 example above. So the point is, the blame for the 'unorganized mess' that Niccalo is
referring to in his condemnation of SDK style coding lands at the feet of the stubborn and recalcitrant SDK
coder who puts up with it and won't fix it rather than at the feet of SDK coding. In no way do I consider the
use of a massive switch language construct in the window procedure to be the defining characteristic of
Windows Api SDK coding style. Even if you do use the switch construct to map incoming messages to the
code which needs to run in response to each respective message, the code can be modularized and made
maintainable as described above by simply calling message handling functions from within the various cases
of the switch. But there is a much, much better way to do it!

I first learned of this technique back in my Windows CE coding days in the mid to late 90s and early 2000s.
It was my job to code the data collection software for my employer's hand held data collectors which our
foresters used to collect timber inventory data on the several millions of acres of forest land which we
managed. We bought all the books we could find on Windows CE coding and one of them we ended up
with was a Microsoft Press book by Douglas Boling entitled "Programming Microsoft Windows CE Second
Edition", Copyright 2001. Here is what Doug said in the very beginning of his book about his coding style...


One criticism of the typical SDK style of Windows programming has always been the huge switch statement
in the window procedure. The switch statement parses the message to the window procedure so that each
message can be handled independently. This standard structure has the one great advantage of enforcing a
similar structure across almost all Windows applications, making it much easier for one programmer to
understand the workings of another programmer's code. The disadvantage is that all the variables for the
entire window procedure typically appear jumbled at the top of the procedure.....

I break the window procedure into individual procedures with each handling a specific message. What
remains of the window procedure itself is a fragment of code that simply looks up the message that's being
passed to see whether a procedure has been written to handle that message. If so, that procedure is called. If
not, the message is passed to the default window procedure.

This structure divides the handling of messages into individual blocks that can be more easily understood.
Also, with greater isolation of one message handling code fragment from another, you can more easily
transfer the code that handles a specific message from one program to the next. I first saw this structure
described a number of years ago by Ray Duncan in one of his old "Power Programming" columns in PC
Magazine. Ray is one of the legends in the field of MS-DOS and OS/2 programming. I've since modified
the design a bit to fit my needs, but Ray should get the credit for this program structure.

Let me first describe Doug Boling's technique – which does not use a switch construct! He first defines an
object which associates an unsigned integer (message) with a function pointer having the same signature as
the window procedure…

1
2
3
4
5
struct decodeUINT
{
 UINT     code;
 LRESULT  (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};


The dead give away for a function pointer declaration is that '*' symbol in parentheses. That object's
declaration would go in an app's *.h file. Next he creates a 'lookup table' which is nothing but a const array
of the above objects, such as this….

1
2
3
4
5
6
7
8
const struct decodeUINT MainMessages[]
{
  WM_CREATE,    DoCreateMain,
  WM_PAINT,     DoPaintMain,
  WM_HIBERNATE, DoHibernateMain,
  WM_ACTIVATE,  DoActivateMain,
  WM_DESTROY,   DoDestroyMain
};


So the above array MainMessages contains five elements each of which is a pair of integral numbers, the
first being an equate/define from the Windows.h header, e.g., WM_CREATE, WM_PAINT, etc., and the
second being the runtime address of the message/event handling function determined at program load.

Finally, Doug's window procedure (with no switch) looks like so…

1
2
3
4
5
6
7
8
9
10
11
12
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam) 
{
  int i;

  for(i=0; i<dim(MainMessages); i++)
  {
      if(wMsg==MainMessages[i].Code)
         return (*MainMessages[i].fxn(hwnd,wMsg,wParam,lParam);
  }

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


In his header he has also this macro seen above – dim….

 
#define dim(x) size(x) / sizeof(x[0])) 


continued....
So since there are five messages in his message table, that resolves the upper bound of the for loop to five, so
it iterates from zero to four. If the loop makes a match between the Code member of decodeUINT and the
incoming wMsg of the window procedure parameter list, then the corresponding function address is called as
specified by the decodeUINT::Fxn member. If the for loop iterates through the five members of the array
without making a match, then the call is transferred to DefWindowProc() in the usual manner.

Note that, using this design, the number of lines of code in the window procedure is constant – it will never
change, no matter how few or how many Windows messages a program is coded to handle.

This is a paradigm I've used for about 20 years, but like Doug made some changes to Ray Duncan's original
specifications to suit his programming style, I've made some changes to Doug's to suit my predilections.
First off, Doug coded this all in C. I don't really code in C anymore, as I prefer C++. Secondly, where this
changes things for me is that I amalgamate the window procedure parameters, i.e., HWND, UINT,
WPARAM, LPARAM, into an object like so….

1
2
3
4
5
6
7
struct WndEventArgs
{
 HWND       hWnd;
 WPARAM     wParam;
 LPARAM     lParam;
 HINSTANCE  hIns;
};


…and an instance of these I pass by reference as a single parameter in my function pointer call. I got this
idea from none other than Microsoft's .NET Framework. If you aren't familiar with that, everything in .NET
is a class object. There aren't any PODs (Plain Old Data Types) flying around. All the arguments to
message handling functions are class objects passed by reference. For example, here's a short and sweet
VB.NET program with a WM_PAINT handler whose single parameter is of type PaintEventArgs….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
' vbc /r:System.dll,System.Drawing.dll,System.Windows.Forms.dll /t:winexe T3.vb
' 7,168 bytes VB.NET v4.0.30319
' 150,091,475 bytes v4 .NET Framework
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Class T3
  Inherits Form

  Sub New()
    Me.Text = "Remembering VB.Net"
    Me.BackColor = SystemColors.Window
    Me.ForeColor = SystemColors.WindowText
  End Sub

  Protected Overrides Sub OnPaint(pea As PaintEventArgs)
    Dim br As Brush
    Dim str As String

    str ="Do You Remember How To Write VB.Net Code?"
    br = new SolidBrush(Me.ForeColor)
    pea.Graphics.DrawString(str,Me.Font,br,0,0)
  End Sub
End Class

Module MainModule
  Public Sub Main()
    Application.Run(new T3())
  End Sub
End Module 


I wrote that code with Notepad, and the command line compilation string is at top (as in all my code). It
should build and run. Hopefully. Maybe. If code rot hasn't set in. I just tested it with vbc.exe version
v4.0.30319 and it worked with only a little complaint. But you can see the PaintEventArgs object in the
OnPaint() parameter list. So why not a WndEventArgs in my C++ SDK program??? Compiler only has to
pass/push one address instead of four values! And by the way, this paradigm of Doug's is very, very close to
the 'delegate' concept in .NET, which involves a type safe array of function pointers.

So, to demonstrate all this let's turn to Petzold's Sysmets3 program from his Windows 95 book. Reason I
chose that one is because it's a scrolling demonstration program which handles lots of Windows messages,
and because since Petzold's books exclusively use that miserable switch construct, his window procedure in
that program is particularly ugly. But it's not exactly Petzold's program. I modified it to scroll the source
code file (Main.cpp) instead of the system metrics data from GetSystemMetrics() - which Petzold stored in
an array in a header file.

Regarding scrolling - I find it tricky. When I first studied it from Petzold's Windows 95 book, I tried to put it
all together using my own code and not copying it line for line from Petzold. I'd always get real close, but
end up with some very minor 'glitch' that would render it not as good as Charles' code, like a clipped line at
the bottom, or too much white space there. So eventually I gave up and just used his code exactly 'as is'. So
the code below is exactly Charles' code except as modified to use Doug Boling's and my function pointer
setup in the for loop based window procedure, and as I said it scrolls the source code file named Main.cpp.
Oh! I did away with a header file. So everything is in Main.cpp...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//Main.cpp;
//cl Main.cpp /O1 /Os /W3 /FeScrollWindow.exe kernel32.lib user32.lib gdi32.lib                      110,592 bytes, VC19, x64 LIBCMT
//cl Main.cpp /O1 /Os /W3 /GS- /FeScrollWindow.exe /link TCLib.lib kernel32.lib user32.lib gdi32.lib   8,192 bytes, VC19, x64 TCLib 
//#define TCLib
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include <windows.h>
#ifdef TCLib
   #include "string.h"
   #include "stdio.h"
#else
   #include <string.h>
   #include <cstdio>
#endif
#pragma warning(disable:4996)
#define dim(x) (sizeof(x) / sizeof(x[0]))


struct WndEventArgs
{
 HWND                            hWnd;
 WPARAM                          wParam;
 LPARAM                          lParam;
 HINSTANCE                       hIns;
};

LRESULT fnWndProc_OnCreate       (WndEventArgs& Wea);
LRESULT fnWndProc_OnSize         (WndEventArgs& Wea);
LRESULT fnWndProc_OnVScroll      (WndEventArgs& Wea);
LRESULT fnWndProc_OnHScroll      (WndEventArgs& Wea);
LRESULT fnWndProc_OnMouseWheel   (WndEventArgs& Wea);
LRESULT fnWndProc_OnPaint        (WndEventArgs& Wea);
LRESULT fnWndProc_OnDestroy      (WndEventArgs& Wea);

struct EVENTHANDLER
{
 unsigned int                    iMsg;
 LRESULT                         (*fnPtr)(WndEventArgs&);
};

const EVENTHANDLER EventHandler[]=
{
 {WM_CREATE,                     fnWndProc_OnCreate},
 {WM_SIZE,                       fnWndProc_OnSize},
 {WM_VSCROLL,                    fnWndProc_OnVScroll},
 {WM_HSCROLL,                    fnWndProc_OnHScroll},
 {WM_MOUSEWHEEL,                 fnWndProc_OnMouseWheel},
 {WM_PAINT,                      fnWndProc_OnPaint},
 {WM_DESTROY,                    fnWndProc_OnDestroy}
};

struct ScrollData
{
 wchar_t**                       pPtrs;
 int                             iNumLines;
 int                             cxChar;
 int                             cxCaps;
 int                             cyChar;
 int                             cxClient;
 int                             cyClient;
 int                             iMaxWidth;
};

continued...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 wchar_t szBuffer[512];
 wchar_t* cRet=NULL;
 HANDLE hHeap=NULL;
 HFONT hFont=NULL;
 FILE* fp1=NULL;
 TEXTMETRIC tm;
 int iLen=0,i;
 HDC hdc;

 hHeap=GetProcessHeap();
 pScrDta=(ScrollData*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(ScrollData));
 if(!pScrDta)
    return -1;
 SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pScrDta);
 hdc = GetDC(Wea.hWnd);
 hFont=CreateFont
 (
   -1*(10*GetDeviceCaps(hdc,LOGPIXELSY))/72,0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,0,(wchar_t*)L"Lucida Console"
 );
 if(!hFont)
    return -1;
 HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
 GetTextMetrics(hdc, &tm);
 pScrDta->cxChar = tm.tmAveCharWidth;
 pScrDta->cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * pScrDta->cxChar / 2;
 pScrDta->cyChar = tm.tmHeight + tm.tmExternalLeading;
 DeleteObject(SelectObject(hdc,hTmp));
 ReleaseDC(Wea.hWnd, hdc);
 fp1=_wfopen(L"Main.cpp",L"r");
 if(fp1)
 {
    do
    {
      cRet=fgetws(szBuffer,512,fp1);
      if(!cRet)
         break;
      else
         pScrDta->iNumLines++;
    } while(1);
    rewind(fp1);
    if(pScrDta->iNumLines)
    {
       pScrDta->pPtrs=(wchar_t**)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(wchar_t*) * pScrDta->iNumLines);
       if(pScrDta->pPtrs)
       {
          for(i=0; i<pScrDta->iNumLines; i++)
          {
              fgetws(szBuffer,512,fp1);
              iLen=(int)wcslen(szBuffer);
              if(iLen>pScrDta->iMaxWidth)
                 pScrDta->iMaxWidth=iLen;
              pScrDta->pPtrs[i]=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY, sizeof(wchar_t)*iLen+1*sizeof(wchar_t));
              wcscpy(pScrDta->pPtrs[i],szBuffer);
          }
          pScrDta->iMaxWidth=pScrDta->iMaxWidth*pScrDta->cxChar;
       }
    }
    fclose(fp1);
 }
 else
    return -1;

 return 0;
}


LRESULT fnWndProc_OnSize(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;

 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    pScrDta->cxClient = LOWORD(Wea.lParam);
    pScrDta->cyClient = HIWORD(Wea.lParam);
    si.cbSize = sizeof(si) ;
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iNumLines - 1;
    si.nPage  = pScrDta->cyClient / pScrDta->cyChar;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    si.cbSize = sizeof(si);
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iMaxWidth / pScrDta->cxChar;
    si.nPage  = pScrDta->cxClient / pScrDta->cxChar;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
 }

 return 0;
}


LRESULT fnWndProc_OnVScroll(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;

 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    si.cbSize = sizeof(si) ;// Get all the vertial scroll bar information
    si.fMask  = SIF_ALL ;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    int iVertPos = si.nPos; // Save the position for comparison later on
    switch (LOWORD(Wea.wParam))
    {
      case SB_TOP:
           si.nPos = si.nMin ;
           break ;
      case SB_BOTTOM:
           si.nPos = si.nMax ;
           break ;
      case SB_LINEUP:
           si.nPos -= 1 ;
           break ;
      case SB_LINEDOWN:
           si.nPos += 1 ;
           break ;
      case SB_PAGEUP:
           si.nPos -= si.nPage ;
           break ;
      case SB_PAGEDOWN:
           si.nPos += si.nPage ;
           break ;
      case SB_THUMBTRACK:
           si.nPos = si.nTrackPos ;
           break ;
      default:
           break ;
    }
    si.fMask = SIF_POS ;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    if(si.nPos != iVertPos)
    {
       ScrollWindow(Wea.hWnd, 0, pScrDta->cyChar*(iVertPos-si.nPos), NULL, NULL);
       UpdateWindow(Wea.hWnd);
    }
 }

 return 0;
}


LRESULT fnWndProc_OnHScroll(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 SCROLLINFO si;

 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    si.cbSize = sizeof (si);// Get all the horizontal scroll bar information
    si.fMask  = SIF_ALL;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si) ;// Save the position for comparison later on
    int iHorzPos = si.nPos;
    switch (LOWORD(Wea.wParam))
    {
      case SB_LINELEFT:
           si.nPos -= 1 ;
           break ;
      case SB_LINERIGHT:
           si.nPos += 1 ;
           break ;
      case SB_PAGELEFT:
           si.nPos -= si.nPage ;
           break ;
      case SB_PAGERIGHT:
           si.nPos += si.nPage ;
           break ;
      case SB_THUMBTRACK: 
           si.nPos = si.nTrackPos ;
           break ;
      default :
           break ;
    }
    si.fMask = SIF_POS;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si);
    if(si.nPos != iHorzPos)
       ScrollWindow(Wea.hWnd, pScrDta->cxChar*(iHorzPos-si.nPos), 0, NULL, NULL);
 }

 return 0;
}


LRESULT fnWndProc_OnPaint(WndEventArgs& Wea)
{
 int x,y,iPaintBeg,iPaintEnd,iVertPos,iHorzPos;
 ScrollData* pScrDta=NULL;
 HFONT hFont=NULL;
 PAINTSTRUCT ps;
 SCROLLINFO si;
 HDC hdc;

 hdc = BeginPaint(Wea.hWnd, &ps);
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta)
 {
    hFont=CreateFont
    (
     -1*(10*GetDeviceCaps(hdc,LOGPIXELSY))/72,0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,0,(wchar_t*)L"Lucida Console"
    );
    HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
    si.cbSize = sizeof (si) ;// Get vertical scroll bar position
    si.fMask  = SIF_POS ;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si), iVertPos = si.nPos;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si), iHorzPos = si.nPos;
    if(iVertPos+ps.rcPaint.top/pScrDta->cyChar>0)
       iPaintBeg=iVertPos + ps.rcPaint.top / pScrDta->cyChar;
    else
       iPaintBeg=0;
    if(iVertPos + ps.rcPaint.bottom / pScrDta->cyChar < pScrDta->iNumLines - 1)
       iPaintEnd=iVertPos + ps.rcPaint.bottom / pScrDta->cyChar;
    else
       iPaintEnd=pScrDta->iNumLines-1;
    for(int i = iPaintBeg; i<= iPaintEnd; i++)
    {
        x = pScrDta->cxChar * (1 - iHorzPos);
        y = pScrDta->cyChar * (i - iVertPos);
        TextOut(hdc, x, y, pScrDta->pPtrs[i], (int)wcslen(pScrDta->pPtrs[i]));
    }
    DeleteObject(SelectObject(hdc,hTmp));
 }
 EndPaint(Wea.hWnd, &ps);

 return 0;
}


continued...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
LRESULT fnWndProc_OnMouseWheel(WndEventArgs& Wea)
{
 int zdelta=GET_WHEEL_DELTA_WPARAM(Wea.wParam);
 if(zdelta>0)
 {
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEUP,0),0);
 }
 else
 {
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);
 }

 return 0;
}


LRESULT fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 ScrollData* pScrDta=NULL;
 HANDLE hHeap=NULL;

 hHeap=GetProcessHeap();
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta->pPtrs)
 {
    for(int i=0; i<pScrDta->iNumLines; i++)
        HeapFree(hHeap,0,pScrDta->pPtrs[i]);
    HeapFree(hHeap,0,pScrDta->pPtrs);
 }
 PostQuitMessage(0);

 return 0;
}


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

 for(size_t 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 hPrevIns, LPSTR lpszArgument, int iShow)
{
 wchar_t szClassName[]=L"ScrollWindow";
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 wc.lpszClassName = szClassName,          wc.lpfnWndProc  = fnWndProc;
 wc.cbSize        = sizeof(WNDCLASSEX),   wc.style        = CS_HREDRAW|CS_VREDRAW;
 wc.hInstance     = hInstance,            wc.lpszMenuName = NULL;
 wc.cbWndExtra    = sizeof(void*),        wc.cbClsExtra   = 0;
 wc.hIconSm       = (HICON)LoadImage(NULL,IDI_APPLICATION,IMAGE_ICON,0,0,LR_DEFAULTCOLOR); 
 wc.hCursor       = (HCURSOR)LoadImage(NULL,IDC_ARROW,IMAGE_CURSOR,0,0,LR_SHARED);
 wc.hIcon         = (HICON)LoadImage(NULL,IDI_APPLICATION,IMAGE_ICON,0,0,LR_DEFAULTCOLOR);
 wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);                                                              
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,50,50,1275,900,HWND_DESKTOP,0,hInstance,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
 }

 return (int)messages.wParam;
}


So I don't know if this more modularized program structure does anything to quell Malibor's objections to
the SDK style of coding, but my own feelings about it is that it is infinitely better than SDK code using the
switch language construct. Here about a year or so ago I was helping one of the members here named
Anachronon with something or other. I had posted a working example using this structure, which is
something I don't usually do. Usually, when I post a full build-able example to help someone, I recode it
using the switch construct so as not to overwhelm them. And that takes a lot of extra work for me. But I
knew Anachonon was a highly skilled C coder, and figured he could handle it. And he did no problem. But,
we had a lurker following the thread. He was so appalled by what I had done to the window procedure -
figuring I guess that I was some kind of deluded crackpot for devising something like that, that he couldn't
contain himself any longer and joined the forum just to denigrate me and my code. I patiently explained to
him the code came from a copyrighted and published book from Microsoft Press by a very well known
author in computer circles who was also an electrical engineer. He never replied back, cancelled his
membership, and was never seen or heard from again. The reason I'm telling you all this is that I get it if you
find this coding paradigm somewhat startling.

In fact, when I first started working with it around 20 years ago I wondered if there was a performance
penalty to using it. Actually, at that time I was mostly using PowerBASIC for my desktop Windows coding
and that language allowed for pretty much the same structure. So I put together a test app that worked the
processor pretty hard painting a screen one pixel at a time. I recall I managed to put something together that
took a couple seconds to complete the operation entailing piles of WM_PAINT messages. And of course
timings on an operating system like Windows are a tricky business so one has to do many runs and work with
averages. The amazing thing I found out is that the for loop setup was consistently running faster than the
switch setup. Not by much, but always a hair faster when looking at averages of like ten runs.

I posted about this in the PowerBASIC forums and my post was well received, but to make a long story short
I don't believe I changed anyone's mind about it. Steve Hutcheson, who runs the MASM32 website and
forum is an ardent PowerBASIC supporter and user, and he opined that in spite of it all he thinks it better to
stick with the standard switch construct. Other very advanced coders felt the same. I took this all to heart
because I knew many of these people were both more knowledgeable than me and smarter than me, but
nonetheless I adopted this coding style as my own and have used it ever since. I've mission critical
enterprise software running for a very large organization using this architecture and its been running without
fail and even without my support since I retired several years back.

I never tested it's performance with C++ but I expect it would beat standard switch architectures. I've never
looked at the generated assembler, but it wouldn't surprise me a bit to see that the optimizing compiler
unrolled the for loop in the window procedure and generated code similar to that produced by a switch, as
what you really have is a for loop with a fairly small number of iterations whose upper bound is a program
constant - just what an advanced optimizing compiler is looking for in loops to optimize for speed.

Added later...

Just realized - in fnWndProc_OnCreate there is a wfopen() call to read in the source code file which you must name "Main.cpp" (if you want the program to work anyway). It just dawned on me that everybody in the world and likely beyond uses Visual Studio, which seems to attempt to fill up hard drives with all kinds of new directories. So Main.cpp likely won't be in the same directory with the executable, and the program won't work. If you want it to work, you had better edit the code or do whatever you have to do so the program can find "Main.cpp".
Last edited on
Topic archived. No new replies allowed.
Pages: 12