Well, you guys are way ahead of me, as usual!
I'm going to try to attach a TCLib.rar I just made and checked out yesterday. It's the latest. The only change to it that I didn't converse with James over concerns three obscure entities I added to TCLib to support console I/O in GUI programs. That's kind of an odd issue. When Windows detects that a program is a GUI as opposed to a console program, somehow or other console C Runtime I/O handles are either zeroed out or not initialized. To overcome that, and to be able to do an AllocateConsole() in a GUI program where printf will work, I needed three obscure symbols from msvcrt.dll...
_iob // array of FILE*
_open_osfhandle // Associates a C run-time file descriptor with an existing operating system Api file handle
fdopen(hCrtIn,"r"); // Associates a stream with a file that was previously opened for low-level I/O.
I wouldn't have had to add them to TCLib. It's always a hard choice for me when I agonize over adding something, cuz I don't want to bloat the code. But in the end I decided I really wanted these. Here is a program that shows how it works, followed by the debug output from the console screen if I clicked the button twice to printf to the console....
// AllocateConsole.cpp
// cl AllocateConsole.cpp /O1 /Os /GR- /GS- TCLib.lib kernel32.lib user32.lib
// Size: 7,168 Bytes; Uses TCLib
#include <windows.h>
#include "AllocateConsole.h"
#include "stdio.h"
bool GUI_Console_IO(FILE*& hFileIn, FILE*& hFileOut)
{
HMENU hSysMenu = NULL; // For Re-Initializing CRT Console IO In GUI Apps.
HANDLE hStdOut = NULL; // With Windows, CRT Console IO File Descriptors Are
HANDLE hStdIn = NULL; // Zeroed Out At GUI Initialization. That's why
int hCrtOut = 0; // C Runtime console i/o routines won't work.
int hCrtIn = 0;
if(!AllocConsole()) return false; // Create Console
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); // Get Standard Output Handle
hStdIn = GetStdHandle(STD_INPUT_HANDLE); // Get Standard Input Handle
// Open stdin
hCrtIn = _open_osfhandle((intptr_t)hStdIn,O_TEXT); // Associates a C run-time file descriptor with an existing operating system Api file handle
hFileIn = _fdopen(hCrtIn,"r"); // Associates a stream with a file that was previously opened for low-level I/O.
_iob[0]=*hFileIn; // _iob Is An Array of FILE structs containing stdin, stdout, and stderr
// Open stdout
hCrtOut = _open_osfhandle((intptr_t)hStdOut,O_TEXT); // Associates a C run-time file descriptor with an existing operating system Api file handle
hFileOut = _fdopen(hCrtOut,"w"); // Associates a stream with a file that was previously opened for low-level I/O.
_iob[1]=*hFileOut; // pFile[1] is stdout FILE object. This Call Re-Initializes It.
// Disable Close In Title Bar Of Console Window // Otherwise, clicking the close button on the console window will terminate the whole
hSysMenu = GetSystemMenu(GetConsoleWindow(),0); // process - GUI and all! Not the best thing to happen.
DeleteMenu(hSysMenu,6,MF_BYPOSITION);
return true;
}
LRESULT CALLBACK fnWndProc_OnCreate(WndEventArgs& Wea)
{
HWND hButton = NULL;
printf(" Entering fnWndProc_OnCreate()\n");
printf(" Wea.hWnd = 0x%p\n",Wea.hWnd);
Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
hButton=CreateWindow("button","Printf() To Console",WS_CHILD|WS_VISIBLE,90,50,200,30,Wea.hWnd,(HMENU)IDC_BUTTON1,Wea.hIns,0);
printf(" Leaving fnWndProc_OnCreate()\n\n");
return 0;
}
LRESULT CALLBACK fnWndProc_OnCommand(WndEventArgs& Wea)
{
double dblWeight = 168.0;
printf(" Entering fnWndProc_OnCommand()\n");
if(LOWORD(Wea.wParam)==IDC_BUTTON1)
printf(" My Name Is Fred And I Weight %3.0f Pounds.\n",dblWeight);
printf(" Leaving fnWndProc_OnCommand()\n\n");
return 0;
}
LRESULT CALLBACK fnWndProc_OnClose(WndEventArgs& Wea)
{
printf(" Entering fnWndProc_OnClose()\n");
printf(" Wea.hWnd = %u\n",Wea.hWnd);
DestroyWindow(Wea.hWnd);
printf(" Leaving fnWndProc_OnClose()\n");
return 0;
}
LRESULT CALLBACK fnWndProc_OnDestroy(WndEventArgs& Wea)
{
printf(" Entering fnWndProc_OnDestroy()\n");
printf(" Wea.hWnd = 0x%p\n",Wea.hWnd);
PostQuitMessage(0);
printf(" Leaving fnWndProc_OnDestroy()\n");
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)
{
char szClassName[]="AllocateConsole";
FILE* hFileOut = NULL;
FILE* hFileIn = NULL;
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
if(GUI_Console_IO(hFileIn,hFileOut)) // Re-Initialize CRT I/O Console Handles
{
printf("Entering WinMain()\n");
wc.lpszClassName = szClassName, wc.lpfnWndProc = fnWndProc;
wc.cbSize = sizeof(WNDCLASSEX), wc.style = 0;
wc.hIcon = LoadIcon(NULL,IDI_APPLICATION), wc.hInstance = hIns;
wc.hIconSm = NULL, wc.hCursor = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW, wc.cbWndExtra = 0;
wc.lpszMenuName = NULL; wc.cbClsExtra = 0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,775,600,400,190,HWND_DESKTOP,0,hIns,0);
printf(" hWnd = 0x%p\n\n",hWnd);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
printf("Leaving WinMain()\n\nPress Any Key To Continue....");
getchar();
fclose(hFileIn);
fclose(hFileOut);
FreeConsole();
}
return (int)messages.wParam;
}
#if 0
Output From Run Where I Clicked The 'Write To Console' Button Twice...
Entering WinMain()
Entering fnWndProc_OnCreate()
Wea.hWnd = 0x00000000001F050A
Leaving fnWndProc_OnCreate()
hWnd = 0x00000000001F050A
Entering fnWndProc_OnCommand()
My Name Is Fred And I Weight 168 Pounds.
Leaving fnWndProc_OnCommand()
Entering fnWndProc_OnCommand()
My Name Is Fred And I Weight 168 Pounds.
Leaving fnWndProc_OnCommand()
Entering fnWndProc_OnClose()
Wea.hWnd = 2032906
Entering fnWndProc_OnDestroy()
Wea.hWnd = 0x00000000001F050A
Leaving fnWndProc_OnDestroy()
Leaving fnWndProc_OnClose()
Leaving WinMain()
Press Any Key To Continue....
#endif
Here's AllocateConsole.h...
//AllocateConsole.h
#ifndef Main_h
#define Main_h
#define O_TEXT 0x4000 // From Fcntl.h
#define IDC_BUTTON1 1500
#define dim(x) (sizeof(x) / sizeof(x[0]))
extern "C" int _fltused = 1;
struct WndEventArgs
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
};
LRESULT CALLBACK fnWndProc_OnCreate (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnCommand (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnClose (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnDestroy (WndEventArgs& Wea);
struct EVENTHANDLER
{
unsigned int iMsg;
LRESULT (*fnPtr)(WndEventArgs&);
};
const EVENTHANDLER EventHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_COMMAND, fnWndProc_OnCommand},
{WM_CLOSE, fnWndProc_OnClose},
{WM_DESTROY, fnWndProc_OnDestroy}
};
#endif
Both those files should be in TCLib.rar.
Other thing I did yesterday was experiment with just how small I could make the simplest possible GUI template using various combinations of x86, x64, UNICODE, ASCI and my several versions of Microsoft's compilers. I have VC6 circa 1998 or so (only x86 of course), VC15 from Visual Studio 2008 Professional, and VC19 from VStudio 2019 Community. Here are my results which you can see atop the source code file....
// cl Form1.cpp /O1 /Os /GS- /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- /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- /link TCLib.lib user32.lib // 3,584 Bytes VC15 UNICODE x64 Static Linked TCLib
// cl Form1.cpp /O1 /Os /GS- /link TCLib.lib user32.lib // 4,096 Bytes VC19 UNICODE x64 Static Linked TCLib
// cl Form1.cpp /O1 /Os user32.lib // 92,160 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 wWinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPWSTR 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;
}
So you can see the memory allocation granularity of the compilers is 512 bytes. Sizes ranged from 2,560 bytes to 4,096 bytes. No results above for VC6 because results with that were no better than with my fairly old VC9 from Visual Studio 2008. I think the first version of Visual Studio that did x64 was VStudio 2005. I don't have that, but I still use my VStudio 2008 compiler a lot - that's the VC15 version of the compiler. Little confusion cuz the numbers of the compiler version and VStudio version don't exactly jive!
One issue you may not have ever seen if you examine my command line strings above - and I don't think I've ever discussed this with James, is that there are SDK style programs out there that make no use whatsoever of anything at all in the C Runtime library. For such a program even TCLib is an overkill. One example of that is Charles Petzold's Hello, Windows! program from his Windows 95 book....
/* cl HelloWin.cpp /O1 /Os /GS- /link crt_win_a.obj kernel32.lib user32.lib gdi32.lib winmm.lib 4,096 Bytes */
/* cl HelloWin.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib user32.lib gdi32.lib winmm.lib 4,608 Bytes */
/* cl HelloWin.c /O1 /Os kernel32.lib user32.lib gdi32.lib winmm.lib 92,672 Bytes */
/*------------------------------------------------------------
HELLOWIN.C -- Displays "Hello, Windows 95!" in client area
(c) Charles Petzold, 1996
------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
switch(iMsg)
{
case WM_CREATE:
{
PlaySound("hellowin.wav", NULL, SND_FILENAME | SND_ASYNC);
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = NULL;
RECT rect;
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, "Hello, Windows 95!", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
HWND hWnd=NULL;
WNDCLASSEX wc;
MSG msg;
wc.cbSize = sizeof (wc);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = "HelloWin";
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wc);
hWnd=CreateWindow("HelloWin","The Hello Program",WS_OVERLAPPEDWINDOW,150,150,1200,700,NULL,NULL,hInstance,NULL);
ShowWindow(hWnd, iCmdShow);
UpdateWindow(hWnd);
while(GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam ;
}
For such a program I don't even use TCLib. You can see in the top command line string I used...
/* cl HelloWin.cpp /O1 /Os /GS- /link crt_win_a.obj kernel32.lib user32.lib gdi32.lib winmm.lib 4,096 Bytes */
What I did there was link against crt_win_a.obj instead of TCLib. First I compiled crt_win_a.cpp into crt_win_a.obj, then linked against that. Here is the crt_win_a.cpp file I used....
//========================================================================================
// 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);
}
I took everything out of my TCLib version that initializes stdio, string, stdlib, and math functions, and just called WinMain(). It shaved another 512 bytes off the executable. Not much I admit, but I thought it might be worth noting if one wants to squeeze every bit of un-needed bloat out!