ObjReader Community
Frederick Harris section => TCLib => Topic started by: Frederick Harris 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…
// PoePoem5.h header file
#define IDS_APPNAME 0
#define IDS_CAPTION 1
#define IDS_POEMRES 2
/*----------------------------
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….
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….
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...
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....
*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...
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...
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....
// PoePoem5.h header file
#define IDS_APPNAME 0
#define IDS_CAPTION 1
#define IDS_POEMRES 2
/*----------------------------
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"
}
// 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
//#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)....
-
/*----------------------------
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"
}
/*-----------------------
PoePoem1.h header file
-----------------------*/
#define IDS_APPNAME 0
#define IDS_CAPTION 1
#define IDS_POEMRES 2
// 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
//#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.