Author Topic: Win32 SDK Tutorial - Form1 - Very Simplest Windows Template  (Read 2679 times)

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Win32 SDK Tutorial - Form1 - Very Simplest Windows Template
« on: October 14, 2021, 09:19:43 pm »
Attached to this 1st Form1 post is a *.rar file of all six installments of the series


   Form1

   To create an API (Application Programming Interface) based Windows program using Microsoft's
   Software Development Kit (SDK), one must first fill out several of the more critical members
   of a WNDCLASS or WNDCLASSEX struct ...

Code: [Select]
   struct WNDCLASSEX
   {
     UINT    cbSize;
     UINT    style;
     WNDPROC lpfnWndProc;
     int     cbClsExtra;
     int     cbWndExtra;
     HANDLE  hInstance;
     HICON   hIcon;
     HCURSOR hCursor;
     HBRUSH  hbrBackground;
     LPCTSTR lpszMenuName;
     LPCTSTR lpszClassName;
     HICON   hIconSm;
   };

   ...and then pass this information to Windows by calling RegisterClass() or
   RegisterClassEx()...

   https://msdn.microsoft.com/en-us/library/windows/desktop/ms633587%28v=vs.85%29.aspx

   The three most important members of WNDCLASSEX that must be filled out properly and without
   which an application window can't be created are ...

   WNDCLASSEX::lpszClassName   // The null terminated textural name of the class, here "Form1"
   WNDCLASSEX::fnWndProc       // The address of the Window Procedure
   WNDCLASSEX::cbSize          // The size of the WNDCLASSEX object

   While the program can compile and run without filling out the WNDCLASSEX::hInstance member,
   I'd highly recommend that be filled out, because it could put you in a situation of
   undefined behavior.  Anyway, its provided for you as the 1st parameter of WinMain(), so
   there's no reason not to use it.

   After filling out these members of a WNDCLASSEX struct and calling RegisterClassEx() on that
   object, you can make a call to CreateWindow() or CreateWindowEx() to instantiate an
   instance of the Registered Class.  Note that the 1st parameter of CreateWindow() and the 2nd
   parameter of CreateWindowEx() is a pointer to the textural null terminated C String to which
   WNDCLASSEX::lpszClassName points - here "Form1".  The call to CreateWindow() or
   CreateWindowEx() represents a C based constructor call for an object.

   After calling CreateWindow() the remainder of the code in WinMain() must fall into a message
   loop - sometimes called a 'message pump,' which retrieves messages Windows the Operating
   System places in the program's 'message queue.'  This is a memory structure Windows creates
   for all programs where it places messages informing the program of user interactions with
   the program such as keyboard or mouse input, as well as system wide notifcations. The
   GetMessage() function retrieves the next message from the queue, and the DispatchMessage()
   function within the message pump causes Windows to dispatch (call) the Registered Window
   Procedure associated with the Window Handle of the message.

   The final piece of the puzzle is the Window Procedure itself, which is likely the most
   important concept in Windows Programming. Note that the WNDCLASSEX::lpfnWndProc member is
   typed as WNDPROC.  What that is is a function pointer like so...

   typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

   In x86 LRESULT is a typedef for a LONG and hence is 32 bits, while in x64 LRESULT is a 64
   bit entity.  The CALLBACK symbol is a typedef of __stdcall.  So if the following looks
   better to you, you could think of it as this, at least in x86 ...

   long (__stdcall* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

   In other words, the WNDCLASSEX::lpfnWndProc entity requires a pointer to a function that
   returns a long, uses standard call stack protocol, and has HWND, UINT, WPARAM, and LPARAM
   parameters. Don't let these confuse you.  Their usage will become clear, and I'll cover them
   in Form2 and Form3.  So just accept them for now, awaiting further clarification.  Note that
   our fnWndProc() function just below fullfills our specifications for a Window Procedure, and
   the address of that Window Procedure is assigned to WNDCLASSEX::fnWndProc in WinMain() as
   follows ...

   wc.lpfnWndProc = fnWndProc;

   Note that our fnWndProc() just below only does two things.  First, it tests the msg
   parameter of type unsigned int to see if it equals WM_DESTROY.  You can find WM_DESTROY
   defined in Windows.h as follows ...

   #define WM_DESTROY  0x0002

   So its equal to 2 (its actually in WinUser.h - #included by Windows.h).  Windows sends a
   WM_DESTROY message when you click the X in the Title Bar to close the program.  In that case
   the app calls PostQuitMessage(), which causes the message pump in WinMain() to fall through
   and end.  Then zero is returned in fnWndProc notifying Windows that the message has been
   handled, and to take no further action on it.  The second thing the program's Window
   Procedure does is pass every other message received in the Window Procedure not equal to
   WM_DESTROY to DefWindowProc() for default processing.  In other words, you are telling
   Windows that you aren't interested in that message and are taking no action on it, but that
   the Operating System can do whatever it must with it to make itself happy.  In general, when
   you handle a message by writing code, you make an early exit by returning zero.  Some
   messages do require other entities to be returned though.  Run the code below to make sure
   everything is working for you.

Code: [Select]
// cl  Form1.cpp /O1 /Os /GS- /Gy /link crt_win_a.obj user32.lib             //  2,560 Bytes VC15 ASCI    x64 Static Linked crt_win_a.obj
// cl  Form1.cpp /O1 /Os /GS- /Gy /link crt_win_w.obj user32.lib             //  3,072 Bytes VC15 UNICODE x64 Static Linked crt_win_w.obj
// cl  Form1.cpp /O1 /Os /GS- /Gy/link TCLib.lib user32.lib                  //  3,584 Bytes VC15 UNICODE x64 Static Linked TCLib
// cl  Form1.cpp /O1 /Os /GS- /Gy /link crt_win_w.obj memset.obj user32.lib  //  3,584 Bytes VC19 UNICODE x64 Static Linked crt_win_w.obj
// cl  Form1.cpp /O1 /Os /GS- /Gy /link TCLib.lib user32.lib                 //  3,584 Bytes VC19 UNICODE x64 Static Linked TCLib
// g++ Form1.cpp -luser32 -lkernel32 -oForm1_gcc.exe -mwindows -m64 -s -Os   // 18,944 Bytes TDM-GCC 9.2 UNICODE x64
// cl  Form1.cpp /O1 /Os user32.lib                                          // 87,552 Bytes UNICODE x64 Static Linked MS C Runtime
#ifndef UNICODE
    #define UNICODE
#endif
#ifndef _UNICODE
    #define _UNICODE
#endif
#include <windows.h>

LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 if(msg==WM_DESTROY)       // This is the Window Procedure.  The concept of the
 {                         // Window Procedure is the most important concept to
    PostQuitMessage(0);    // grasp in C/C++ WinApi coding.  You never call this
    return 0;              // function with your code; rather, Windows calls it
 }                         // to inform code here of events occurring.  The events
                           // are reported here as messages.
 return (DefWindowProc(hwnd, msg, wParam, lParam));
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 MSG messages;    // The concept of the Window Class and its associated Window Procedure are
 WNDCLASS wc;     // the most important concepts in Windows Programming.

 wc.lpszClassName = L"Form1",    wc.lpfnWndProc   = fnWndProc; // The Class Name Will Be Form1 And The Symbol fnWndProc
 wc.hInstance     = hInstance,   wc.style         = 0;         // Will Be Resolved At Runtime To The Virtual Address Of
 wc.cbClsExtra    = 0,           wc.cbWndExtra    = 0;         // Form1's Window Procedure, Which Windows Will Call
 wc.hIcon         = NULL,        wc.hCursor       = NULL;      // Through This Address Rather Than Through It's Name.
 wc.lpszMenuName  = NULL,        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
 RegisterClass(&wc);                                           // Register The Window Class With Windows
 CreateWindow(L"Form1", L"Form1", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 200, 100, 325, 300, HWND_DESKTOP, 0, hInstance, 0);
 while (GetMessage(&messages, NULL, 0, 0))   // The message pump retrieves messages from the program's
 {                                           // message queue with GetMessage(), does some translation
    TranslateMessage(&messages);             // work in terms of character messages, then calls the
    DispatchMessage(&messages);              // Window Procedure associated with the HWND of the message
 }                                           // being processed.  Note that an app can have many Window
 
 return (int)messages.wParam;
}

« Last Edit: October 25, 2021, 04:45:29 pm by Frederick Harris »

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Re: Win32 SDK Tutorial - Form1 - Very Simplest Windows Template
« Reply #1 on: October 14, 2021, 10:23:33 pm »
A note on the previous post's file attachment...

The unzipped SDK_Tutorial.rar will produce a \Forms top level directory/folder, and underneath that folder will be six sub-folders - one for each of the Form1 through Form6 projects.  In the top level \Forms folder are include files and such you will need to build the code in the various subfolders.  You will need to copy them to there.  I didn't include them in each folder to save space that would occur through duplicates.

Also, the TCLib.lib and other *.obj files are in the top level \Forms folder.  They too might have to be copied to the various subfolders if you are referencing them in any way, such as building with TCLib as opposed to Microsoft's C Runtime.  If building from the command line my various command line strings at the top of the source code reference various files, so make sure to copy them to whichever is the right folder for whatever you are doing.

I'll attach a text file I wrote up awhile back on command line compiling.  Might be helpful if interested.

Added later:

I just downloaded SDK_Tutorial.rar and un-rared :) it to...

 C:\Users\Frede\Source

...which gave me a...

C:\Users\Frede\Source\Forms

In...

C:\Users\Frede\Source\Forms\Form1

...is the single file Form1.cpp.  If you look at the code there are no includes other than Windows.h.  So if you want to build it against the C Runtime just type...

cl Form1.cpp User32.lib [ENTER]

You'll end up with Form1.exe.  If you want to build it against my TCLib copy TCLib.lib to the Form1 folder and do like so...

cl  Form1.cpp /O1 /Os /GS- /Gy/link TCLib.lib user32.lib

That ought to give you an exe somewhere in the 3.5 to 4k range.

If you want to build it against nothing whatsoever use crt_win_a.obj or crt_win_w.obj.  For crt_win_w.obj you need to change the signature of WinMain() to wWinMain() and the lpszArguement to LPWSTR.
« Last Edit: October 15, 2021, 12:36:19 am by Frederick Harris »

James Fuller

  • Newbie
  • *
  • Posts: 41
Re: Win32 SDK Tutorial - Form1 - Very Simplest Windows Template
« Reply #2 on: October 24, 2021, 10:25:15 pm »
Fred,
  Issue with Form5

James
Code: [Select]
Microsoft Windows [Version 10.0.19042.1288]
(c) Microsoft Corporation. All rights reserved.

C:\Fred\Forms\Form5>"C:\Program Files (x86)\Microsoft Visual Studio\2022\Preview\VC\Auxiliary\Build\vcvarsall.bat" x64
**********************************************************************
** Visual Studio 2022 Developer Command Prompt v17.0.0-pre.6.0
** Copyright (c) 2021 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'

C:\Fred\Forms\Form5>cl Form5.cpp Option1.cpp Option2.cpp Option3.cpp /O1 /Os /MT kernel32.lib user32.lib gdi32.lib /FeForm5_MSVC.exe
Microsoft (R) C/C++ Optimizing Compiler Version 19.30.30704 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

Form5.cpp
Option1.cpp
Option1.cpp(10): fatal error C1083: Cannot open include file: 'Main.h': No such file or directory
Option2.cpp
Option2.cpp(9): fatal error C1083: Cannot open include file: 'Main.h': No such file or directory
Option3.cpp
Option3.cpp(9): fatal error C1083: Cannot open include file: 'Main.h': No such file or directory
Generating Code...

C:\Fred\Forms\Form5>

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Re: Win32 SDK Tutorial - Form1 - Very Simplest Windows Template
« Reply #3 on: October 25, 2021, 04:18:17 pm »
Thanks very much for finding that James.  It should be #include "Form5.h".  Somehow that slipped by me, in spite of all the care I took.  I fixed it in the posted code, and I'll now check the *.rar file to see if it needs to be fixed there too (I expect it does).

Just fixed it in rar file and re-attached it.
« Last Edit: October 25, 2021, 04:46:28 pm by Frederick Harris »

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Re: Win32 SDK Tutorial - Form1 - Very Simplest Windows Template
« Reply #4 on: November 27, 2021, 05:11:26 pm »
#if 0
Form1 -- Most Minimal Windows GUI Which Registers A Custom Window Class

The title says it all.  From Wikipedia, "The term minimalist often colloquially refers to anything that is spare or stripped to its essentials."...

https://en.wikipedia.org/wiki/Minimalism

The term can be applied to lifestyle, music, architecture, visual arts, literature, or even, Windows SDK coding.  The three most fundamental low level operations necessary to create a window in Windows are...

1) Declare a WNDCLASS object, fill out the necessary fields, and Call RegisterClass() on the object;
2) Call CreateWindow() on the registered class, which is a C based object constructor call;
3) Fall into a 'message loop/message pump' to retrieve messages and dispatch them to the all important Window Procedure.

The below program fits that description exactly and manages all of the above in only 30 lines of reasonably formatted code.  Of course, quite a bit of extraneous code has been extracted so as to reduce everything to a bare minimum.  There's no error checking on anything.  There's no C Runtime Library (because there are no C Runtime Library calls except one - and I'll get to that directly).  There are no cursors or window styles specified, the lack of which causes some relatively poor visual behaviors.  But it is minimal.  I can build it to 3,072 bytes x64 Ascii, with VC19, or 2,048 bytes x86 with VC15 from Visual Studio 2008.  Here's the code... 
#endif

Code: [Select]
// cl Form1.cpp /O1 /Os /GS- crt_win_a.obj memset.obj user32.lib   //  3,072 Bytes x64; Ascii; VC19;
#include <windows.h> 

LRESULT CALLBACK fnWndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)   // Window Procedure
{
 if(msg==WM_DESTROY)
 {
    PostQuitMessage(0);
    return 0;
 }

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

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 MSG messages;
 WNDCLASS wc{0};                  // WNDCLASS Object Zero Initialized
 
 wc.lpszClassName = "Form1";      // Fill Out Class Name
 wc.lpfnWndProc   = fnWndProc;    // Specify To Windows OS Address Of WNDPROC
 RegisterClass(&wc);              // Register Window Class
 CreateWindow("Form1","Form1",WS_OVERLAPPEDWINDOW|WS_VISIBLE,200,100,325,300,HWND_DESKTOP,0,hInstance,0);
 while(GetMessage(&messages,NULL,0,0))   
 {
    TranslateMessage(&messages);  // Message Pump
    DispatchMessage(&messages);
 }

 return (int)messages.wParam;
}

#if 0
Now about my command line string above which accomplishes this....

cl Form1.cpp /O1 /Os /GS- crt_win_a.obj memset.obj user32.lib   //  3,072 Bytes x64; Ascii; VC19;

...I'm not using my TCLib - or not much of it anyway, because I'm trying very hard to be as extreme a minimalist as possible, and my TCLib is a minimal implementation of Microsoft's C Runtime Library, and for this program we don't need anything from the C Runtime Library except for one call to support this idiom...

Code: [Select]
WNDCLASS wc{0};                  // WNDCLASS Object Zero Initialized

...which is a fairly recent addition to the C++ language to support zero initialization of POD (Plain Old Data types) or PODs in classes or structures.  It's been my experience (easily verifiable) that this 'idiom' causes the C++ compiler to call memset() 'under the hood', so to speak, to do the actual zero initialization.  And memset() is part of the C Runtime.  So we really need to link the above code not only to the ubiquitous and always needed user32.lib, but also to my crt_win_a.obj and memset.obj files.  Here's a simple implementation of memset() and wmemset() from my TCLib.lib...

Code: [Select]
//=====================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                            By Fred Harris, January 2016
//
//                     cl memset.cpp /c /W3 /DWIN32_LEAN_AND_MEAN
//=====================================================================================
#include "memory.h"

void* __cdecl memset(void* p, int c, size_t count)
{
 char* pCh=(char*)p;
 for(size_t i=0; i<count; i++)
     pCh[i]=(char)c;
 return p;
}

wchar_t* __cdecl wmemset(wchar_t* p, wchar_t c, size_t count)
{
 for(size_t i=0; i<count; i++)
     p[i]=c;
 return p;
}

...and here's an extremely stripped down version of my crt_win_a.cpp file from TCLib which implements the required C/C++ startup code - WinMainCRTStartup(), for a Windows GUI application which creates a window in WinMain()....

Code: [Select]
//========================================================================================
//                 Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                              By Fred Harris, January 2016
//
// cl crt_win_a.cpp /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
//========================================================================================
#include <windows.h>
#pragma comment(linker, "/defaultlib:kernel32.lib")
#pragma comment(linker, "/nodefaultlib:libc.lib")
#pragma comment(linker, "/nodefaultlib:libcmt.lib")
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);

extern "C" void __cdecl WinMainCRTStartup(void)
{
 int iReturn = WinMain(GetModuleHandle(NULL),NULL,NULL,SW_SHOWDEFAULT);
 ExitProcess(iReturn);
}

The two files above can be built to *.obj files at the command line using the included compilation strings, or I suppose all three files could be included in a Visual Studio IDE build - but I haven't personally tried that.  If using the command line to build, once the memset.obj and crt_win_a.obj files have been built, one can use the full command line string above to build a 3 k Form1.exe.

There's a 'twist' to building a 2 k x86 version.  It comes in as 3 k for me in x86 using VC19 from Visual Studio 2019.  So I downgraded to my older but still used VC15 of Visual Studio 2008.  Using that I could get an unbelievable 2k binary!  However, VC15 doesn't know anything about the recent additions to the C++ Standard allowing either this....

Code: [Select]
WNDCLASS wc{0};

...or this....

Code: [Select]
WNDCLASS wc={0};

...in terms of zero initializations.  So that needs to be removed from the code (leaving WNDCLASS wc; however), and a memset() call needs to be added to the code somewhere under the declaration.
#endif