Author Topic: Form8 -- Convenient GUI Debugging With A Console Window And CRT printf()  (Read 2315 times)

Frederick Harris

  • Newbie
  • *
  • Posts: 47
#if 0
Form8 -- Convenient GUI Debugging With A Console Window And CRT printf()

I usually open a log file for GUI debugging purposes, but sometimes just printing to a console screen can be very convenient. 
I've especially used this technique when developing custom controls or ActiveX Controls, where I'd open a log output file for
control output and a console screen for debugging the client/host.  That way I could keep track of exactly what was happening
simultaneously in both interacting binaries. 

This is easy to do by simply allocating a console window in a GUI app, then outputting to it with WriteConsole().  But using
printf() is simply more convenient and direct, in my opinion.  But the catch is, CRT printf() doesn't work in GUI apps.  At
the time of my writing this (11/28/2021) further and more detailed information on this topic can be found here in this very
old article....

https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/105305

Quoting from this article.... 

Quote
When a GUI application is started with the "start" command, the three standard OS handles STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
and STD_ERROR_HANDLE are all "zeroed out" by the console initialization routines. These three handles are replaced by valid
values when the GUI application calls AllocConsole(). Therefore, once this is done, calling GetStdHandle() will always return
valid handle values. The problem is that the CRT has already completed initialization before your application gets a chance to
call AllocConsole(); the three low I/O handles 0, 1, and 2 have already been set up to use the original zeroed out OS handles,
so all CRT I/O is sent to invalid OS handles and CRT output does not appear in the console. Use the workaround described above
to eliminate this problem.

The following code implements the workarounds described in that article.  In WinMain() I call GUI_Console_IO() - the function
I created (just below) to set things up.  If it succeeds one can read/write the console opened by AllocateConsole().  The
program consists simply of a main GUI window with a single command button on it.  When one clicks the button a text string is
output to the console stating my weight in pounds.  I used a double variable and the %f printf format specifier.  The program
additionally outputs entrances and exits from the various message/event handling functions.  Here is what an output run looks
like (copied from the console screen) when I start the app from a shortcut, click twice on the button to write to console, then
X out of the app from the GUI component of it.... 

Entering WinMain()
  Entering fnWndProc_OnCreate()
    Wea.hWnd = 0x00000000000D0620
  Leaving fnWndProc_OnCreate()

  hWnd = 0x00000000000D0620

  Entering fnWndProc_OnCommand()
    My Name Is Fred And I Weight 167.5 Pounds.
  Leaving fnWndProc_OnCommand()

  Entering fnWndProc_OnCommand()
    My Name Is Fred And I Weight 167.5 Pounds.
  Leaving fnWndProc_OnCommand()

  Entering fnWndProc_OnClose()
    Wea.hWnd = 0x00000000000D0620
    Entering fnWndProc_OnDestroy()
      Wea.hWnd = 0x00000000000D0620
    Leaving fnWndProc_OnDestroy()
  Leaving fnWndProc_OnClose()
Leaving WinMain()

Press Any Key To Continue....

Note that when one x's out, DestroyWindow() has already executed, and code execution is at the end of WinMain(), and the message
loop has ended.  But the console window is still 'hanging' open with a CRT getchar() call, a blinking cursor, and a last printf()
output line of....

Press Any Key To Continue....

Further note that in a GUI app of this type where one allocates a console window, the console window will have the typical
minimize, maximize, and close buttons up in the title bar.  If one clicks the close (X) button, it not only closes/destroys the
console window - it takes out the whole process - console window and GUI window.  This is by design, and for console windows
is not a problem.  But it is a problem when shutdown code in the GUI window fails to run (release allocated memory, close
resources, etc.).  So you'll note in GUI_Console_IO() there is code to deactivate the close button of the console window.

Note that this is a TCLib only version.  Due to the sort of irregular nature of all this, I'll have to make a somewhat altered
non-TCLib version I'll call Form8a.cpp, and I'll post that after tis one.
#endif

Code: [Select]
// Form8.cpp
// cl Form8.cpp /O1 /Os /GR- /GS- TCLib.lib kernel32.lib user32.lib
// Size: 7,168 Bytes; Uses TCLib
#include <windows.h>
#include "Form8.h"
#include "stdio.h"           


bool GUI_Console_IO(FILE*& hFileIn, FILE*& hFileOut) 
{                                                                   
 HMENU   hSysMenu        = NULL;                       // For Re-Initializing CRT Console IO In GUI Apps.  With Windows, CRT Console IO File                             
 HANDLE  hStdOut         = NULL;                       // Descriptors Are Zeroed Out At GUI Initialization.  That's why C Runtime console i/o           
 HANDLE  hStdIn          = NULL;                       // Routines Won't Work.  If You Don't Believe Me, Try Creating A GUI App, Call
 int     hCrtOut         = 0;                          // AllocateConsole(), Then Try Using CRT printf(...).  Windows API WriteConsole() Will
 int     hCrtIn          = 0;                          // Work, But CRT printf(...) Won't.  This App Shows How To Fix That Using TCLib.lib.
                                                     
 if(!AllocConsole()) return false;                     // Create Console
 hStdIn  = GetStdHandle(STD_INPUT_HANDLE);             // Get Standard Input Handle
 hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);            // Get Standard Output 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","Write 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 = 167.5;
 
 printf("  Entering fnWndProc_OnCommand()\n");
 if(LOWORD(Wea.wParam)==IDC_BUTTON1)
    printf("    My Name Is Fred And I Weight %5.1f 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 = 0x%p\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[]="Form8";
 FILE*   hFileOut  = NULL;
 FILE*   hFileIn   = NULL;
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 if(GUI_Console_IO(hFileIn,hFileOut))
 {
    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;
}

Here is #include file Form8.h...

Code: [Select]
//Form8.h
#ifndef Form8_h
#define Form8_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

I believe this is my standard stdio.h which I've posted elsewhere, but I'll post it again as it is small.  And I'll reiterate, you can't run this code
with Microsoft's <stdio.h> or <cstdio>...
Code: [Select]
// stdio.h
#ifndef stdio_h
#define stdio_h

#define         SEEK_SET  0
#define         SEEK_CUR  1
#define         SEEK_END  2

struct          FILE
{
 char*          _ptr;
 int            _cnt;
 char*          _base;
 int            _flag;
 int            _file;
 int            _charbuf;
 int            _bufsiz;
 char*          _tmpfname;
};

extern FILE*    _iob;
extern char     (__cdecl* getchar)();
extern FILE*    (__cdecl* fopen)(const char* pszFile, const char* pszMode);
extern FILE*    (__cdecl* _wfopen)(const wchar_t* pszFile, const wchar_t* pszMode);
extern int      (__cdecl* _open_osfhandle)(intptr_t osfhandle, int flags );
extern FILE*    (__cdecl* _fdopen)(int FileDescriptor, const char* mode);
extern int      (__cdecl* fseek)(FILE* stream, long offset, int origin);
extern int      (__cdecl* feof)(FILE* stream);
extern int      (__cdecl* printf)(const char* pFormat, ...);
extern int      (__cdecl* wprintf)(const wchar_t* pFormat, ...);
extern int      (__cdecl* fprintf)(FILE* fp, const char* format, ...);
extern int      (__cdecl* fwprintf)(FILE* fp, const wchar_t* format, ...);
extern int      (__cdecl* fscanf)(FILE* fp, const char* pFormat, ...);
extern int      (__cdecl* fwscanf)(FILE* fp, const wchar_t* pFormat, ...);
extern int      (__cdecl* sprintf)(char* buffer, const char* format, ...);
extern int      (__cdecl* swprintf)(wchar_t* buffer, const wchar_t* format, ...);
extern char*    (__cdecl* fgets)(char* pBuffer, int iSize, FILE* fp);
extern wchar_t* (__cdecl* fgetws)(wchar_t* pBuffer, int iSize, FILE* fp);
extern void     (__cdecl* rewind)(FILE* fp);
extern int      (__cdecl* fclose)(FILE* fp);

#endif

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Form8a -- Non-TCLib Version Of Form8 Project
« Reply #1 on: December 01, 2021, 04:01:59 pm »
#if 0
Here is a version of Form8 that doesn't use TCLib but rather can be built against Microsoft's CRT.  The story
on this is I first learned to do this stuff, i.e., get printf working in GUI apps, way back in the late 90s
early 2000s time frame when we were all using Win95, Win98, or Win2000. At that time I was mostly using VC6
from Visual Studio 98 and Bloodshed's Dev-Cpp with Mingw.  The technique worked well with any of PowerBASIC's
compilers also.  I thought I was good forever with these techniques.

The next version of Visual Studio I used was my Visual Studio 2008 Professional.  It broke my code.  However,
throwing a few hours at the new stdio.h header I was able to figure out the changes Microsoft made, tweak my
code, and get it working again - and I hoped once and for all.

Well, it didn't work out that way once again.  The next version of Visual Studio I obtained was Visual Studio
Community 2015 (I get a new version of Visual Studio every 10 years, whether I need it or not).  It broke my
code again.  This time I threw several days at trying to figure out the changes Microsoft made but I failed
miserably.  I couldn't get it working and I've given up on it.

And that really bugged me.  Bad.  I'm not accostomed to failing and giving up on things that are important to
me.  But it occurred to me that my original solution using VC6 from Visual Studio 98 might be made to work with
Visual Studio's VC19 compiler/linker using the same techniques I used to create my TCLib.lib.  Afterall,
msvcrt.dll was the C Runtime for that old version of Visual Studio where I first got this technique working.
So I thought, why not just get function pointers to _open_osfhandle, _fdopen, and the all important _iob[]
FILE* array from msvcrt.dll, and implement it all using Visual Studio 2019?  By the way, _iob[] stands for
Input/Output Blocks.  So I tried that and it works fine with Microsoft's latest (non-beta) Visual Studio.

But there's probably - almost certainly, an easier and better way to do it.  It's just that I can't figure it
out.  As I said, I threw several days at it in total, and came up empty.  Personal problem, I know.  So if
someone here smarter than me can figure it out and post the method here I'll gladly take it and run with it.
I'm not proud!
 
Only downside to the code below is you can't use Microsoft's stdio.h or <cstdio>.  Absolutely can't!  If you
attempt it you'll get errors to the effect that symbols such as _open_osfhandle, _fdopen, etc., are being
redefined from functions to function pointers.  So if you need anything from stdio.h other than printf, fclose,
or getchar, you are going to have to do as I did in the code below and get it from msvcrt.dll as a function
pointer and use it that way.  To me, that's no big deal, but I wanted to point that out.
#endif

Code: [Select]
// Form8a.cpp
// This non-TCLib (Links Against LibCmt) GUI program creates a debug console at the beginning of WinMain().
// Do not #include stdio.h or <cstdio>.  Won't work, because of definitions and function pointer prototypes
// I put in ConsoleIO.h to work against msvcrt.dll.
// cl Form8a.cpp  /O1 /Os /MT /GA /FeForm8a.exe User32.lib
// Size: 94,720 Bytes VC19 (VStudio 2019);
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE   
   #define _UNICODE
#endif   
#include <windows.h>
#include "Form8.h"
#include "ConsoleIO.h"


bool GUI_Console_IO(HMODULE& hDll, FILE*& hFileIn, FILE*& hFileOut)  // For Re-Initializing CRT Console IO In GUI Apps.
{                                                                    // With Windows, CRT Console IO File Descriptors Are
 HMENU   hSysMenu        = NULL;                                     // Zeroed Out At GUI Initialization.  That's why
 HANDLE  hStdOut         = NULL;                                     // C Runtime console i/o routines won't work.
 HANDLE  hStdIn          = NULL;
 FILE*   pFile           = NULL;
 int     hCrtOut         = 0;
 int     hCrtIn          = 0;
 
 if(!AllocConsole())                                     // Create Console
    return false;
 hDll            = LoadLibrary(L"msvcrt.dll");           // Obtain HANDLE To msvcrt.dll
 if(!hDll)
    return false;
 pFile           = (FILE*)GetProcAddress(hDll,"_iob");   // _iob Is An Array of FILE structs containing stdin, stdout, and stderr
 if(!pFile)
    return false;
 hStdOut         = GetStdHandle(STD_OUTPUT_HANDLE);
 hStdIn          = GetStdHandle(STD_INPUT_HANDLE); 
 _open_osfhandle = (int   (__cdecl*)(intptr_t osfhandle, int flags)) GetProcAddress(hDll,"_open_osfhandle");
 _fdopen         = (FILE* (__cdecl*)(int, const char* mode        )) GetProcAddress(hDll,"_fdopen"        );
 printf          = (int   (__cdecl*)(const char* pFormat, ...     )) GetProcAddress(hDll,"printf"         );
 getchar         = (char  (__cdecl*)(                             )) GetProcAddress(hDll,"getchar"        );
 fclose          = (int   (__cdecl*)(FILE* fp                     )) GetProcAddress(hDll,"fclose"         );
 
 // Open stdin
 hCrtIn   = _open_osfhandle((intptr_t)hStdIn,O_TEXT);    // Associates a C run-time file descriptor with an existing operating system file handle
 hFileIn  = _fdopen(hCrtIn,"r");                         // Associates a stream with a file that was previously opened for low-level I/O.
 pFile[0] = *hFileIn;                                    // pFile[0] is stdin FILE object.  This Call Re-Initializes It.
 
 // Open stdout
 hCrtOut  = _open_osfhandle((intptr_t)hStdOut,O_TEXT);   // Associates a C run-time file descriptor with an existing operating system file handle
 hFileOut = _fdopen(hCrtOut,"w");                        // Associates a stream with a file that was previously opened for low-level I/O.
 pFile[1] = *hFileOut;                                   // pFile[1] is stdout FILE object.  This Call Re-Initializes It.
 
 // Disable Close In Title Bar Of Console Window
 hSysMenu = GetSystemMenu(GetConsoleWindow(),0);
 DeleteMenu(hSysMenu,6,MF_BYPOSITION);
 
 return true;
}
 

LRESULT CALLBACK fnWndProc_OnCreate(WndEventArgs& Wea)
{
 HWND hButton = NULL;
 
 printf("  Entering fnWndProc_OnCreate()\n");
 printf("    Wea.hWnd = %u\n",Wea.hWnd);
 Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
 hButton=CreateWindow(L"button",L"Write 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 = 178.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 = %u\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)
{
 wchar_t    szClassName[]=L"AllocateConsole";
 HMODULE    hDll       = NULL;
 FILE*      hFileOut   = NULL;
 FILE*      hFileIn    = NULL;
 MSG        messages;
 HWND       hWnd;
 WNDCLASSEX wc;
 
 if(GUI_Console_IO(hDll,hFileIn,hFileOut));
 {
    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 = %u\n\n",hWnd);
    ShowWindow(hWnd,iShow);
    while(GetMessage(&messages,NULL,0,0))
    {
       TranslateMessage(&messages);
       DispatchMessage(&messages);
    }
    printf("Leaving WinMain()\n\n");
    printf("Press Any Key To Continue....");
    getchar();
    fclose(hFileIn);
    fclose(hFileOut);
    FreeLibrary(hDll);
    FreeConsole();
 }   
 
 return (int)messages.wParam;
}