Author Topic: Crash Fix For x64: Charles Petzold's PoePoem Ch 10 Project  (Read 472 times)

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Crash Fix For x64: Charles Petzold's PoePoem Ch 10 Project
« on: December 18, 2021, 11:53:35 pm »
     Charles Petzold's Chapter 10 project PoePoem builds OK with x64 but crashes on startup.  It doesn't 'hang' in memory requiring Task Manager to end it - it simply terminates with no GUI window becoming visible.  After extensively debugging it I found the source of the crash and can provide fixes for it.  I believe it's a Data Execution Prevention (DEP) issue specific to x64, but whether I'm right or not about my diagnosis of ultimate causes, my fix will work.

     Anyway, in terms of background, in all my years of API SDK coding I never availed myself of string tables or custom resources in my code or resource file usage.  Petzold covers this so when I wanted to experiment and learn about it I naturally pulled out my Petzold Windows Programming books, of which I have two - his 4th edition Windows 95 book which is pure asci, and his Windows 98 5th edition where he tried to convert everything to Microsoft's version of UNICODE.   I guess since I learned Windows API coding with his Windows 95 book, I started with his C code from that book.

     Before I get started though, let me say that for possible copyright and intellectual property reasons, I don't want to post any of Charles' original code here.  My programs are quite heavily modified C++ versions of his that use my coding styles and conventions, and quite a lot of different code, and I feel OK about posting these, which I'll do.  However, if you don't have his disks, whether it's legal or ethical or not, I do believe Charles' original C code programs can be found on github, and perhaps elsewhere on the internet.  Here is a github link....

https://github.com/yottaawesome/programming-windows-5th-edition

     Basically, what the PoePoem project is about, is embedding Edgar Allen Poe’s "Annabell Lee" poem (48 lines) in the resource section of an executable.  Also, he uses a string table to hold the name of the app, the caption to appear in the single window created, and the name of the custom resource to be embedded in the executable…

Code: [Select]
// PoePoem5.h header file
#define IDS_APPNAME 0
#define IDS_CAPTION 1
#define IDS_POEMRES 2

Code: [Select]
/*----------------------------
   PoePoem5.rc resource script

   rc /r /v PoePoem5.rc
  ----------------------------*/
#include "PoePoem5.h"

PoePoem5    ICON  PoePoem5.ico
AnnabelLee  TEXT  PoePoem5.txt

STRINGTABLE
{
 IDS_APPNAME, "PoePoem5"
 IDS_CAPTION, """Annabel Lee"" by Edgar Allen Poe"
 IDS_POEMRES, "AnnabelLee"
}

So in WinMain() Charles calls Api function LoadString twice to obtain his szAppName[] and szCaption[] variables from the string data embedded in the resource section of the executable….

Code: [Select]
LoadString(hInstance, IDS_APPNAME, szAppName, sizeof(szAppName));
LoadString(hInstance, IDS_CAPTION, szCaption, sizeof(szCaption));

With these in hand he can do his CreateWindow() call to create his main program window….

Code: [Select]
hWnd=CreateWindow(szAppName,szCaption,WS_OVERLAPPEDWINDOW,400,200,500,700,NULL,NULL,hInstance,NULL);

Of course, at that point code execution will enter a WM_CREATE handler, where Charles runs through his typical drill of extracting TEXTMETRIC data so as to initialize variables he’ll need to set up scrolling.  As is typical of his code, he declares quite a few globals and statics to persist data across Window Procedure function calls.  After doing that, and still in WM_CREATE code, he gets down to the major business of the app, which is to extract Poe’s poem embedded in Asci text file format from the resource section of the executable.  Here is a reasonable facsimile of the code he runs...

Code: [Select]
HRSRC     hSrc        = NULL;
HGLOBAL   hResource   = NULL;
HINSTANCE hInst       = NULL;
Char*     pText       = NULL;
Int       iNumLines   = 0;

hinst     = GetModuleHandle(NULL);                                   // Get HINSTANCE Of App
HSrc      = FindResource(hInst, TEXT ("AnnabelLee"), TEXT ("TEXT")); // Find Text Resource “AnnabelLee"
hResource = LoadResource(hInst, hSrc, TEXT ("TEXT"));                // Load Resource “AnnabelLee”
pText     = (char*)LockResource(hResource);                          // Get char* To Resource
while(*pText != '\\' && *pText != '\0')                              // Run Through Text Data Of Poem Until
{                                                                    // A Forward Slash Or NULL Char Is Hit,
   if(*pText == '\n')                                                // Counting Newlines Along The Way. 
      iNumLines ++;                                                  // AnsiNext() Is Obsolete, But Is Same
   pText = AnsiNext(pText);                                          // As pText++, i.e., Move To Next Char.
}                                                                    // *pText = '\0' Is A Memory Write
*pText = '\0';                                                       // Operation, And It Crashes The App!

     Some context is necessary to understand all this.  Charles decided to use DrawText() to draw the poem in the client area of the app’s window.   He likely chose DrawText() instead of TextOut() because DrawText() translates CRLFs and TextOut() doesn't.  Here is an abbreviated copy of the first and last few verses of the poem (it really is a nice poem, too)....

It was many and many a year ago,
   In a kingdom by the sea,
That a maiden there lived whom you may know
   By the name of Annabel Lee;
And this maiden she lived with no other thought
   Than to love and be loved by me.

I was a child and she was a child
   In this kingdom by the sea,
But we loved with a love that was more than love --
   I and my Annabel Lee --
With a love that the winged seraphs of Heaven
   Coveted her and me.
....
....
....
For the moon never beams, without bringing me dreams
   Of the beautiful Annabel Lee;
And the stars never rise, but I feel the bright eyes
   Of the beautiful Annabel Lee:
And so, all the night-tide, I lie down by the side
Of my darling -- my darling -- my life and my bride,
   In her sepulchre there by the sea --
   In her tomb by the sounding sea.
 
                                       [May, 1849]
/

Note the forward slash at the end of the poem.  I don't know why Charles felt it necessary to add that.  But it is there in the original text file that he embeds in the resources, and in his code he attempts to remove it so it doesn't show up in his DrawText() output.  As you can see in his while loop, hitting the forward slash character is one of the conditions that causes the while loop to terminate, at which point the next line of code is a memory write operation like so where he attempts to place a NULL byte where the slash existed....

Code: [Select]
*pText = '\0';   CRASHES!!!!!

Now, there's nothing wrong, usually, with such memory write operations using pointers - I do it all the time, but here it crashes.  In fact, it doesn't matter where one attempts to write memory in that text file - from the first byte all the way to the last.  It crashes the app no matter what.  Just for further information, here is a byte dump of the end of the 1659 byte file....

1643   32   
1644   32   
1645   32   
1646   91   [
1647   77   M
1648   97   a
1649   121   y
1650   44   ,
1651   32   
1652   49   1
1653   56   8
1654   52   4
1655   57   9
1656   93   ]
1657   13   
1658   10   
1659   47   /
1660   0   

Both Windows 'Properties' and a wcslen() call within the code itself reveal the file to be 1659 bytes.  So what I did to fix the program, and solve the crash, is to simply remove Charles' memory write operation, and modify his DrawText() function call so as to not write the forward slash character.  Here is Charles' DrawText() call in his WM_PAINT handler code...

Code: [Select]
DrawText(hdc, pText, -1, &rect, DT_EXTERNALLEADING);

The -1 for the 3rd parameter tells Windows to assume a NULL terminated string - which gets the '\\' character printed if it's there.  And if we don't over-write it with a NULL, so the app won't crash, it'll be there.  To get rid of it just do a wcslen() call instead where we subtract -3 from the string length, which yields us 1659 - 3 = 1656.  That will get rid of the slash, a line feed, and a carriage return, and back us out to the closing bracket ']' character (see byte dump above).  No more memory write operations and no more crashes!  Here then, would be the fix...

Code: [Select]
DrawText(hdc, pText, wcslen(pText)-3, &rect, DT_EXTERNALLEADING);


Here is my first version of Charles' fixed program, which I've converted to wide character....

Code: [Select]
// PoePoem5.h header file
#define IDS_APPNAME 0
#define IDS_CAPTION 1
#define IDS_POEMRES 2

Code: [Select]
/*----------------------------
   PoePoem5.rc resource script
   rc /r /v PoePoem5.rc
  ----------------------------*/
#include "PoePoem5.h"

PoePoem5    ICON  PoePoem5.ico
AnnabelLee  TEXT  PoePoem5.txt

STRINGTABLE
{
 IDS_APPNAME, "PoePoem5"
 IDS_CAPTION, """Annabel Lee"" by Edgar Allen Poe"
 IDS_POEMRES, "AnnabelLee"
}

Code: [Select]
// Main.h
#ifndef Main_h
#define Main_h

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

#ifndef max
#define max(a,b)                        (((a) > (b)) ? (a) : (b))
#endif

#ifndef min
#define min(a,b)                        (((a) < (b)) ? (a) : (b))
#endif

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

LRESULT CALLBACK fnWndProc_OnCreate     (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnSize       (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnSetFocus   (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnVScroll    (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnPaint      (WndEventArgs& Wea);
LRESULT CALLBACK fnWndProc_OnDestroy    (WndEventArgs& Wea); 

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

const EVENTHANDLER                      EventHandler[]=
{
 {WM_CREATE,                            fnWndProc_OnCreate},
 {WM_SIZE,                              fnWndProc_OnSize},
 {WM_SETFOCUS,                          fnWndProc_OnSetFocus},
 {WM_VSCROLL,                           fnWndProc_OnVScroll},
 {WM_PAINT,                             fnWndProc_OnPaint},
 {WM_DESTROY,                           fnWndProc_OnDestroy}
};

struct                                  ScrollData
{
 HWND                                   hScroll;
 int                                    iPosition;
 int                                    cxChar;
 int                                    cyChar;
 int                                    cyClient;
 int                                    iNumLines;
 int                                    xScroll;
 int                                    iVisible;
 int                                    iRange;
};

struct                                  ResourceData
{
 HGLOBAL                                hResource;
 wchar_t*                               pWText;
 char                                   szPoemRes[16];
};

struct                                  AppData
{
 ScrollData                             sd;
 ResourceData                           rd;
};

#endif 

Code: [Select]
//#define TCLib        // PoePoem5.cpp
#ifndef UNICODE        // cl PoePoem5.cpp /O1 /Os /MT  user32.lib gdi32.lib PoePoem5.res                 // 97,792 Bytes;
   #define UNICODE     // g++ PoePoem5.cpp -luser32 -lkernel32 -lgdi32  -mwindows -m64 -s -Os            // 24,576 Bytes;
#endif                 // cl PoePoem5.cpp /O1 /Os /GR- /GS- TCLib.lib user32.lib gdi32.lib PoePoem5.res  //  9,728 Bytes;
#ifndef _UNICODE       // This code is a redo of Charles Petzold's PoePoem Project from Chapter 10 of his 5th edition
   #define _UNICODE    // book.  His original code builds for me in x64 but crashes on startup.  I think it may be a DEP
#endif                 // (Data Execution Prevention) issue, in that Petzold's original code attempts to modify, i.e.,
#include <Windows.h>   // 'write', memory within the resource section of the built executable.  I believe his original
#ifdef TCLib           // code will build/run x86, where DEP can either be turned off, or an app 'white listed' so as to
   #include "string.h" // get around DEP.  Apparently, x64 closes this loop hole, as I tried to 'white list' it, but
   #include "stdio.h"  // received an operating system message such that x64 binaries can't be 'white listed', or have
   #include "memory.h" // DEP turned off.  If I ever did know that I may have forgotten it, as I haven't really looked
#else                  // at or studied DEP issues for a long time.  Anyway, in my opinion this redo of mine was justified
   #include <string.h> // in that Charles' original code was really a very minimal UNICODE adaptation of his original 
   #include <stdio.h>  // Windows 95 version of his 4th edition book, which was pure ansi.  Seems reasonable to me to pull
   #include <wchar.h>  // ansi (UTF-8) text data into an otherwise UNICODE app, as most text files are still single byte
#endif                 // data, which saves space.   But Charles original code in his UNICODE adaptation still output
#include "Main.h"      // the text, i.e., Poe's poem, in ansi format specifically using DrawTextA().  So what I did in
#include "PoePoem5.h"  // this 'redo' is load the resources with the API 'A' functions - had no choice in this really...


LRESULT CALLBACK fnWndProc_OnCreate(WndEventArgs& Wea)
{
 HDC        hDC       = NULL;   // ...as the resource data was pulled into the executable as ansi, but then I immediately
 HINSTANCE  hInst     = NULL;   // converted everything to wide character using MultiByteToWideChar() Api function.  Also,
 AppData*   pAppData  = NULL;   // I converted everything to C++ (at least my way of doing C++), and eliminated all
 HANDLE     hHeap     = NULL;   // Charles' static and global variables, of which I take a somewhat dim view.  To under-
 HRSRC      hFoundRes = NULL;   // stand my code, see ScrollData, ResourceData, and AppData objects in Main.h.  These
 char*      pText     = NULL;   // objects encapsulate all Charles' globals and statics from his C code.  I HeapAlloc() 
 char*      p         = NULL;   // memory in WM_CREATE handler code (just left and down) to persist this data across
 TEXTMETRIC tm        =  {0};   // function calls, and I release memory in WM_DESTROY handler code.  Further, my code
 int        iStrLen   =   0 ;   // eliminates the switch logic construct in the Window Procedure to map incomming....
 
 hHeap    = GetProcessHeap();                                             //  ...messages to the code (event procedure)
 Wea.hIns = ((LPCREATESTRUCT)Wea.lParam)->hInstance;                      // which handles each respective message.
 pAppData = (AppData*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(AppData));  // Otherwise, a good bit of the code here is
 if(!pAppData)                                                            // right out of Charles' WM_CREATE code.
    return -1;                                                            // Except note below left my use of
 SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pAppData);                         // MultiByteToWideChar() to convert Poe's
 hDC = GetDC(Wea.hWnd);                                                   // poem in the resource data to Windows
 GetTextMetrics(hDC,&tm);                                                 // UNICODE, and store it in heap memory, as
 pAppData->sd.cxChar = tm.tmAveCharWidth;                                 // opposed to utilizing it in it's original
 pAppData->sd.cyChar = tm.tmHeight + tm.tmExternalLeading;                // ansi form in the resource data. 
 ReleaseDC(Wea.hWnd, hDC);
 pAppData->sd.xScroll=GetSystemMetrics(SM_CXVSCROLL);
 pAppData->sd.hScroll=CreateWindow(L"scrollbar",NULL,WS_CHILD|WS_VISIBLE|SBS_VERT,0,0,0,0,Wea.hWnd,(HMENU)1,Wea.hIns,NULL);
 LoadStringA(Wea.hIns,IDS_POEMRES,pAppData->rd.szPoemRes,sizeof(pAppData->rd.szPoemRes));
 hFoundRes=FindResourceA(Wea.hIns,pAppData->rd.szPoemRes,"TEXT");
 pAppData->rd.hResource = LoadResource(Wea.hIns,hFoundRes);
 pText=(char*)LockResource(pAppData->rd.hResource);
 p=pText;
 while(*pText != '\0')           // This code runs through the resource data containing Poe's Annabel Lee poem counting
 {                               // CR-LFs so as to obtain the number of lines in the file, which I believe is somewhere
   if(*pText == '\n')            // around 48.  Note I eliminated Charles' AnsiNext() function (you'll see that if you
      pAppData->sd.iNumLines++;  // have Charles' original code), as it's obsolete, and I don't have a clue what good it
   pText++;                      // ever served.  The number of lines is needed to set up scrolling.
 }
 pText = p;
 SetScrollPos(pAppData->sd.hScroll,SB_CTL,0,FALSE);                         // Here's where we'll use MultiByteToWideChar()
 iStrLen=MultiByteToWideChar(CP_ACP,0,pText,-1,NULL,0);                     // to convert ansi data to wide character. 
 pAppData->rd.pWText=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iStrLen*2); // Note use of said function to obtain buffer
 MultiByteToWideChar(CP_ACP,0,pText,-1,pAppData->rd.pWText,iStrLen);        // size for UNICODE converted string. 
 
 return 0;
}


LRESULT CALLBACK fnWndProc_OnSize(WndEventArgs& Wea)  // The WM_SIZE handler code is important to set up the scrolling
{                                                     // functionality of the app.  One needs to know, for example, the
 AppData* pAppData = NULL;                            // size relationships between the height of a character, and the
                                                      // height of the client area of the window where text will be drawn.
 pAppData=(AppData*)GetWindowLongPtr(Wea.hWnd,0);
 pAppData->sd.iVisible = HIWORD(Wea.lParam)/pAppData->sd.cyChar;
 pAppData->sd.iRange  = pAppData->sd.iNumLines - pAppData->sd.iVisible + 1;
 MoveWindow
 (
  pAppData->sd.hScroll,
  LOWORD(Wea.lParam)-pAppData->sd.xScroll,
  0,
  pAppData->sd.xScroll,
  pAppData->sd.cyClient=HIWORD(Wea.lParam),
  TRUE
 );
 SetScrollRange(pAppData->sd.hScroll,SB_CTL,0,pAppData->sd.iRange,FALSE); 
 SetFocus(Wea.hWnd);
 
 return 0;
}


LRESULT CALLBACK fnWndProc_OnSetFocus(WndEventArgs& Wea)
{
 AppData* pAppData = NULL;
 
 pAppData=(AppData*)GetWindowLongPtr(Wea.hWnd,0);
 SetFocus(pAppData->sd.hScroll);
 
 return 0;
}


LRESULT CALLBACK fnWndProc_OnVScroll(WndEventArgs& Wea)
{
 AppData* pAppData = NULL;
 int iMin;
 int iMax;

 pAppData=(AppData*)GetWindowLongPtr(Wea.hWnd,0);
 switch(Wea.wParam)
 {
   case SB_TOP:
     pAppData->sd.iPosition = 0;
     break;
   case SB_BOTTOM:
     pAppData->sd.iPosition=pAppData->sd.iRange;
     break;
   case SB_LINEUP:
     pAppData->sd.iPosition = pAppData->sd.iPosition - 1;
     break;
   case SB_LINEDOWN:
     if(pAppData->sd.iPosition<pAppData->sd.iRange)
        pAppData->sd.iPosition = pAppData->sd.iPosition + 1;
     break;
   case SB_PAGEUP:
     pAppData->sd.iPosition = pAppData->sd.iPosition - pAppData->sd.cyClient / pAppData->sd.cyChar;
     break;
   case SB_PAGEDOWN:
     pAppData->sd.iPosition = pAppData->sd.iPosition + pAppData->sd.cyClient / pAppData->sd.cyChar;
     if(pAppData->sd.iPosition>pAppData->sd.iRange)
        pAppData->sd.iPosition=pAppData->sd.iRange;
     break;
   case SB_THUMBPOSITION:
     pAppData->sd.iPosition = LOWORD(Wea.lParam);
     break;
 }
 iMin = min(pAppData->sd.iPosition,pAppData->sd.iRange);
 iMax = max(0, iMin);
 pAppData->sd.iPosition = max(0, min(pAppData->sd.iPosition, pAppData->sd.iRange));
 if(pAppData->sd.iPosition != GetScrollPos(pAppData->sd.hScroll, SB_CTL))
 {
    SetScrollRange(pAppData->sd.hScroll,SB_CTL,0,pAppData->sd.iRange,FALSE); 
    SetScrollPos(pAppData->sd.hScroll, SB_CTL, pAppData->sd.iPosition, TRUE);
    InvalidateRect(Wea.hWnd, NULL, TRUE); 
 } 

 return 0;
}


LRESULT CALLBACK fnWndProc_OnPaint(WndEventArgs& Wea)   // Charles decided he wanted to use DrawText() for his app, and to
{                                                       // clip drawing rectangles to implement scrolling.  This is all well
 AppData*    pAppData = NULL;                           // and good, but DrawText() reacts poorly to nulls in the string to
 HDC         hdc      = NULL;                           // be drawn.  Gibberish characters get drawn.  So what Charles
 PAINTSTRUCT ps;                                        // decided to do was to overwrite the terminating NULL char in the
 RECT        rc;                                        // resource data in the executable.  Here's his exact code....
                                                                    //
 hdc      = BeginPaint(Wea.hWnd, &ps);                              //   while (*pText != '\\' && *pText != '\0')
 pAppData = (AppData*)GetWindowLongPtr(Wea.hWnd,0);                 //   {
 GetClientRect(Wea.hWnd, &rc);                                      //      if(*pText == '\n')
 rc.left  = rc.left + pAppData->sd.cxChar;                          //         iNumLines ++ ;
 rc.top   = rc.top+pAppData->sd.cyChar*(1-pAppData->sd.iPosition);  //      pText = AnsiNext (pText) ;
 DrawText                                                           //   }
 (                                                                  //   *pText = '\0' ;
  hdc,                                                              //
  pAppData->rd.pWText,                                  // The line *pText = '\0' is of course a 'write memory' operation, and
  wcslen(pAppData->rd.pWText) - 2,                      // it is the line which crashes the app built as x64.  Of course, x64
  &rc,                                                  // write memory pointer operations are totally valid, but only if that
  DT_EXTERNALLEADING                                    // memory isn't within resource data or other executable code in the
 );                                                     // loaded *.exe file, apparently.  It seems computer programmer
 EndPaint(Wea.hWnd, &ps);                               // etiquette has gone through some evolution between Charles' writtings
                                                        // around Win98 and the advent of DEP with WinXP and later!  So what I
 return 0;                                              // did to fix this was simply to subtract a '-2' from the length of the
}                                                       // pWText string to be drawn, which eliminated the '/' and the NULL.


LRESULT CALLBACK fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 AppData* pAppData = NULL;

 pAppData=(AppData*)GetWindowLongPtr(Wea.hWnd,0);
 FreeResource(pAppData->rd.hResource);
 HeapFree(GetProcessHeap(),0,pAppData);
 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 hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
 HWND       hWnd       = NULL;
 WNDCLASSEX wc         =  {0};
 char       szAppName    [10];
 char       szCaption    [35];
 wchar_t    szWAppName   [16];
 wchar_t    szWCaption   [40];
 MSG        msg;

 LoadStringA(hInstance, IDS_APPNAME, szAppName, sizeof (szAppName));  // It's ansi data in resource section of executable,
 LoadStringA(hInstance, IDS_CAPTION, szCaption, sizeof (szCaption));  // so the 'A' functions are needed to load it.  But
 MultiByteToWideChar(CP_UTF8,0,szAppName,-1,szWAppName,16);           // we'll immediately convert it to wide chars using
 MultiByteToWideChar(CP_UTF8,0,szCaption,-1,szWCaption,40);           // MultiByteToWideChar().
 wc.cbSize        = sizeof(wc),                          wc.style        = CS_HREDRAW | CS_VREDRAW;
 wc.lpfnWndProc   = fnWndProc,                           wc.cbClsExtra   = 0;
 wc.cbWndExtra    = sizeof(void*),                       wc.hInstance    = hInstance;
 wc.hIcon         = LoadIcon(hInstance, szWAppName),     wc.hCursor      = LoadCursor(NULL, IDC_ARROW);
 wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH), wc.lpszMenuName = NULL;
 wc.lpszClassName = szWAppName,                          wc.hIconSm     = LoadIcon(hInstance, szWAppName);
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szWAppName,szWCaption,WS_OVERLAPPEDWINDOW|WS_VISIBLE,400,200,500,700,NULL,NULL,hInstance,NULL);
 while(GetMessage(&msg, NULL, 0, 0))
 {
   TranslateMessage(&msg);
   DispatchMessage(&msg);
 }
 
 return (int)msg.wParam;
}

Believe it or not, I have another version for you!  Another way to fix the memory write failure is to make a copy of the poem's memory somewhere other than in the resource section of the executable, using malloc(), new(), or HeapAlloc(), copy the poem to there, and remove the slash character.  MultiByteToWideChar() can be used once one has the 'writable' memory.  But I think with this version of the program I did one better, that is, I copied the poem lines to an array of lines and used TextOut() for each line instead of DrawText().  I've always personally preferred TextOut() to DrawText() anyway.  And in any case, I think the scroll logic and efficiency is better when one isn't clipping rectangles for scrolling.  Here is another version using this technique (next post)....
« Last Edit: December 19, 2021, 12:01:30 am by Frederick Harris »

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Re: Crash Fix For x64: Charles Petzold's PoePoem Ch 10 Project
« Reply #1 on: December 19, 2021, 12:03:16 am »
Code: [Select]
/*----------------------------
   PoePoem1.rc resource script

   rc /r /v PoePoem1.rc
   WindRes -i PoePoem1.rc -o PoePoem1_res.obj -v
  ----------------------------*/
#include "PoePoem1.h"

PoePoem1    ICON  PoePoem1.ico
AnnabelLee  TEXT  PoePoem1.txt

STRINGTABLE
{
 IDS_APPNAME, "PoePoem1"
 IDS_CAPTION, """Annabel Lee"" by Edgar Allen Poe"
 IDS_POEMRES, "AnnabelLee"
}


Code: [Select]
/*-----------------------
   PoePoem1.h header file
  -----------------------*/
#define IDS_APPNAME 0
#define IDS_CAPTION 1
#define IDS_POEMRES 2


Code: [Select]
// Main.h
#ifndef Main_h
#define Main_h

#define dim(x)                          (sizeof(x) / sizeof(x[0]))
#define TYPEFACE_NAME                   L"Times New Roman"
#define FONT_SIZE                       12

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

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

struct                                  EVENTHANDLER
{
 unsigned int                           iMsg;
 LRESULT                                (CALLBACK* 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;
};

#endif

Code: [Select]
//#define TCLib      // PoePoem1.cpp
#ifndef UNICODE      // cl  PoePoem1.cpp /O1 /Os /MT  user32.lib gdi32.lib PoePoem1.res                 // 99,328 Bytes;
   #define UNICODE   // cl  PoePoem1.cpp /O1 /Os /GR- /GS- TCLib.lib user32.lib gdi32.lib PoePoem1.res  // 10,752 Bytes
#endif               // g++ PoePoem1.cpp PoePoem1_res.obj -o PoePoem1_gcc.exe -mwindows -m64 -s -Os     // 25,600 Bytes
#ifndef _UNICODE
   #define _UNICODE
#endif
#include <Windows.h>
#ifdef TCLib
   #include "string.h"
   #include "stdio.h"
   #include "memory.h"
#else
   #include <string.h>
   #include <stdio.h>
   #include <wchar.h>
#endif
#include "Main.h"
#include "PoePoem1.h"


LRESULT CALLBACK fnWndProc_OnCreate(WndEventArgs& Wea)
{
 ScrollData* pScrDta   = NULL;
 char*       pText     = NULL;
 wchar_t*    pWText    = NULL;
 wchar_t*    p         = NULL;
 HGLOBAL     hResource = NULL;
 HANDLE      hHeap     = NULL;
 HRSRC       hFoundRes = NULL;
 HDC         hdc       = NULL;
 HFONT       hFont     = NULL;
 size_t      iStrLen   = 0;
 char        szPoemRes[16];
 wchar_t     szBuffer[128];
 TEXTMETRIC  tm;

 Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;              // Get HINSTANCE From CREATESTRUCT Object
 LoadStringA(Wea.hIns,IDS_POEMRES,szPoemRes,sizeof(szPoemRes)); // Get Name Of Custom TEXT Resource "AnnabelLee" (It's Asci)
 hFoundRes = FindResourceA(Wea.hIns,szPoemRes,"TEXT");          // Find Custom Resource In Binary Executable
 hResource = LoadResource(Wea.hIns,hFoundRes);                  // Get HANDLE To Resource, So We Can Get Pointer To It.
 pText=(char*)LockResource(hResource);                          // Get Pointer To First Byte Of Custom Resource
 
 hHeap=GetProcessHeap();                                        // Will Need To Allocate Memory For A ScrollData Object
 pScrDta=(ScrollData*)HeapAlloc                                 // (See Main.h) So As To Eliminate Charles Petzold's
 (                                                              // Static And Global C Variables.  We'll Store The
  hHeap,HEAP_ZERO_MEMORY,sizeof(ScrollData)                     // Allocated Object As Part Of The App's WNDCLASSEX
 );                                                             // cbWndExtra Bytes, Which Is Kind Of A C Ism For A
 if(!pScrDta)                                                   // C++ Class Member Variable.  If -1 Is Returned From
    return -1;                                                  // WM_CREATE Handler Code, The CreateWindowEx() Call
 SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pScrDta);                // In WinMain() Will Fail, And App Will Terminate.

 hdc = GetDC(Wea.hWnd);                                         // To Get Scroll Bars And Scrolling Working, One Needs
 hFont=CreateFont                                               // To Know, For Example, The Size (Height) Of A Char-
 (                                                              // acter In Relation To The Size Of The Client Area Of
   -1*(FONT_SIZE*GetDeviceCaps(hdc,LOGPIXELSY))/72,             // The Window Where Text Is To Be Displayed And Scrolled.
   0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,    // So We Need To Create A Font That The App Will Use,
   0,TYPEFACE_NAME                                              // And Get It's Metrics Using GetTextMetrics().  We'll
 );                                                             // Store This Info In The ScrollData Persisted Object,
 if(!hFont)                                                     // And Let It Up To The WM_SIZE Handler Code To Deal
    return -1;                                                  // With The Screen Size Stuff.  We Gotta Be Careful To
 HFONT hTmp=(HFONT)SelectObject(hdc,hFont);                     // Delete The Dynamically Allocated Resource.
 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);
 
 iStrLen=MultiByteToWideChar(CP_ACP,0,pText,-1,NULL,0);         // We Need To Convert Asci "Annabel Lee" Poem Read From
 pWText=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iStrLen*2);  // Executable Resource To Wide Character For GUI Output
 MultiByteToWideChar(CP_ACP,0,pText,-1,pWText,iStrLen);         // In WM_PAINT Handler Code.  We'll Use MultiByteToWideChar().
 
 p=pWText;      // Run Through Annabel Lee Poem Counting Line Feed Characters Because, For The Scroll Logic This App
 while(*p)      // Employs Using TextOut() In WM_PAINT Handler Code, We Need To Break The Poem Up Into An Array Of Lines. 
 {              // We'll Persist The Count Obtained Here In Our ScrollData Object, Which Becomes Part Of The
   if(*p==13)   // WNDCLASSEX::cbWndExtra Bytes.  This Eliminates Charles Petzold's statics and globals.
      pScrDta->iNumLines++;
   p++;
 }
 pScrDta->pPtrs=(wchar_t**)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,pScrDta->iNumLines*sizeof(wchar_t**));
 if(pScrDta->pPtrs)
 {                                              // This Code Will Break The CR-LF Delimited Poem Lines From The Resources
    size_t j;                                   // In The Executable Into An Array Of Character Pointers -- All Contained
    p=pWText;                                   // Within The ScrollData Object.  Wouldn't Surprise Me If There Was
    for(size_t i=0; i<pScrDta->iNumLines; i++)  // Something Or Other In The C++ Specific Part Of The Standard Library To
    {                                           // Do This Easier, But Since I'm A Hard Core C Coder Code Like This Doesn't
        wmemset(szBuffer,'\0',128);             // Bother Me. Especially Since I Can Build This x64 Using My Own Custom C
        j=0;                                    // Runtime In 10 K, As Opposed To A Megabyte Sized Executable If Linked
        while(true)                             // Against MS's LibCmt.  So What The Code Does Is Run Through The Poem Byte
        {                                       // By Byte Detecting When A NewLine Sequence (CR-LF) Has Delimited A Line.
           if(*p==10)                           // Up Until That Point, Ordinary Printable Characters Are Accumulated In
              p++;                              // szBuffer[], But When A Newline Is Encountered The CR Is Replaced By A       
           if(*p==13)                           // NULL Byte Terminating The String In szBuffer.  Then We Make A Memory Alloc-
           {                                    // ation For The Delimited Line And Store It In ScrollData.
              szBuffer[j]=L'\0';
              iStrLen=wcslen(szBuffer);
              if(iStrLen>pScrDta->iMaxWidth)
                 pScrDta->iMaxWidth=iStrLen;
              pScrDta->pPtrs[i]=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iStrLen*2+2);
              wcscpy(pScrDta->pPtrs[i],szBuffer);
              p++;
              break;
           }
           szBuffer[j]=*p;
           p++,j++;
           
        }
    }
    pScrDta->iMaxWidth=pScrDta->iMaxWidth*pScrDta->cxChar;
 }
 
 return 0;
}


LRESULT CALLBACK 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 CALLBACK 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 CALLBACK 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:              // case SB_THUMBPOSITION:
           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 CALLBACK 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 CALLBACK 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*(FONT_SIZE*GetDeviceCaps(hdc,LOGPIXELSY))/72,0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,0,TYPEFACE_NAME
    );
    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;
}


LRESULT CALLBACK fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 ScrollData* pScrDta = NULL;
 HANDLE hHeap        = NULL;
 
 hHeap=GetProcessHeap();
 pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
 if(pScrDta->pPtrs)
 {
    for(size_t i=0; i<pScrDta->iNumLines; i++)
        HeapFree(hHeap,0,pScrDta->pPtrs[i]);
    HeapFree(hHeap,0,pScrDta->pPtrs);
 }
 HeapFree(hHeap,0,pScrDta);
 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 hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
 char       szAppName[64];
 char       szCaption[64];
 wchar_t    szWAppName[16];
 wchar_t    szWCaption[40];
 HWND       hWnd = NULL;
 WNDCLASSEX wc   = {0};
 MSG        msg;
 
 LoadStringA(hInstance, IDS_APPNAME, szAppName, sizeof(szAppName));
 LoadStringA(hInstance, IDS_CAPTION, szCaption, sizeof(szCaption));
 MultiByteToWideChar(CP_UTF8,0,szAppName,-1,szWAppName,16);
 MultiByteToWideChar(CP_UTF8,0,szCaption,-1,szWCaption,40);
 wc.cbSize        = sizeof(wc);
 wc.style         = CS_HREDRAW | CS_VREDRAW;
 wc.lpfnWndProc   = fnWndProc;
 wc.cbClsExtra    = 0;
 wc.cbWndExtra    = sizeof(void*);
 wc.hInstance     = hInstance;
 wc.hIcon         = LoadIcon(hInstance, szWAppName);
 wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
 wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
 wc.lpszMenuName  = NULL;
 wc.lpszClassName = szWAppName;
 wc.hIconSm       = LoadIcon(hInstance, szWAppName);
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szWAppName,szWCaption,WS_OVERLAPPEDWINDOW|WS_VISIBLE,300,150,500,800,NULL,NULL,hInstance,NULL);
 UpdateWindow(hWnd);
 while(GetMessage(&msg, NULL, 0, 0))
 {
   TranslateMessage(&msg) ;
   DispatchMessage(&msg) ;
 }
 
 return (int)msg.wParam;
}

     I’ve played with this on and off for a couple weeks, and have quite a few versions on my laptops.  To use my code to build the apps you may want to change filenames to suit yourself.  And you’ll need Charles’ PoePoem.ico and PoePoem.txt or PoePoem.asc files.  You’ll need to change file names in a lot of places to get it to all work out, come up with the right icon, etc.  Easiest thing to do would be to use my file names, and just rename his PoePoem.ico and PoePoem.txt or PoePoem.asc files.