• Welcome to Jose's Read Only Forum 2023.
 

Moving TCLib Into Mainstream

Started by Frederick J. Harris, April 02, 2016, 12:03:13 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

     Still working on TCLib.lib issues, and trying to move things out of the 'proof of concept' stage to a point where this all could be used in real world application development.  All or most of my test programs so far have been fairly trivial things with under 40 lines of code.  For the past several days though I've been working on getting an app to work that I consider to be fairly complex, and that would be the one I posted here last summer...

http://www.jose.it-berater.org/smfforum/index.php?topic=5058.0

...where you can find a PowerBASIC and C++ version.   In that app I have a main thread with a GUI (Graphical User Interface), and a worker thread which performs heavy duty string processing involving well over a gigabyte of data.  As about 100 separate string processing jobs are completed in the worker thread, the program writes progress reports to a continuously scrolling output window to inform the user of the progress and details of each separate job - kind of like Microsoft's major download/installation programs.  The programming is such that a cancel button allows the user to terminate the jobs prematurely, as is the case with major downloads and program installations.

      The first problem I ran into in getting this program to run with TCLib was that I have no implementation of C Runtime rand() in there.  That random number generator function was used to come up with string sizes to process for the 100 separate jobs.   I wanted string sizes to process between 1 million and 25 million characters.  I believe the C Runtime rand() function returns numbers between 1 and 32767, so I had to develop a 'blow up' or scaling function to modify those numbers to fall within a range of my choosing, just like the PowerBASIC Rnd(a, b) function...


y = RND(a, b)


Here's my implementation of that, and the rand() function I found on the internet...


int rand(void)
{             
next = next * 1103515245 + 12345;
return (unsigned int)(next>>16) & RAND_MAX;
}                                                                                                                 


int Rnd(int iMin, int iMax)           
{
double dblRange,dblMaxFactor,dblRandomNumber;

dblRange=iMax-iMin;                         
dblMaxFactor=dblRange/RAND_MAX;             
dblRandomNumber=(double)rand();     

return iMin+dblMaxFactor*dblRandomNumber;
}                                     


I had the Rnd() function previously, as I needed that in the C++ version using the C Runtime I posted previously.  But the rand() I just came up with for this work now.

The string processing to be done on these multi megabyte strings is this....


//   1) Allocate a multi-million byte array of dash characters;
//   2) Change every 7th dash to a 'P';
//   3) Replace every 'P' with a "PU', thereby growing the buffer;
//   4) Replace every remaining dash with an '8';
//   5) Insert a carriage return / line feed every 90 characters,


This is an abbreviated version of the code which is executing within a thread procedure which makes the 100 or so (iLineCount) calls to the DoProcessing() function ...


for(int i=1; i<=iLineCount; i++)
{
    ...
    iNumber=Rnd(1000000, 25000000);
    iTotalBytes=iTotalBytes+iNumber;
    DoProcessing(iNumber,iTickCount);
    ....
}


As you can see, DoProcessing() is within a for loop, iNumber gets the return from the call to Rnd() for a number between 1 million and 25 million, and iTotalBytes keeps a running tally of the total number of characters processed so far for final reporting purposes at the end.  I guess it should be iTotalChars and not iTotalBytes, as the actual code is using hard coded wchar_ts. 

     Having gotten through all that, when I attempted to use TCLib to compile the program it compiled and ran, but when I clicked the button to start the processing nothing happened.  Usually the scrolling lines of output display about every half second, but nothing happened.  I figured it locked up.  Well, that didn't surprise me.  Been fighting for three months with this now, and no part of it came easy. 

     So I figured I'd have to start from scratch and see what was going wrong.  And that means debugging.  But my primary way of debugging is opening an output log file where I can see what values my variables are taking on, and check the returns from function calls.  But I never really implemented i/o file support in TCLib yet.  There is a Mike_V version of Matt Pietrek's LibCTiny over at www.CodeProject.com.  I thought his work would be more helpful to me than it was, and that's where I actually started out in my quest to develop wide character support.  And in that his work did help me.  But he had pretty much reinterpreted the stdio.h FILE structure machinery to get file i/o working.  Or at least I think he did.  I never really tested it because in my interpretation he completely blew the printf/sprintf code, which in my mind is a cornerstone of this whole endeavor.  If you don't have that you haven't anything at all and might as well just quit.

     What I had discovered in my investigations was that a FILE object from stdio.h as used in fprintf and a HANDLE object as returned by kernel32.lib's CreateFile function were one and the same entity.  Now before you go jumping all over me about this let me state I haven't traced the full lineage of that through the circuitous and obscure pathways of the various Windows includes, but am just stating that if you use Windows Api CreateFile() to return a HANDLE to an opened text file, and you pass that HANDLE to fprintf cast to a FILE*, it'll work!!!  But I didn't really do that.  I simply implemented fprintf using kernel32 functions; its still a variadic function and can be used in the same way as the 'real' fprintf, but you've gotta use CreateFile() to open the file - not fopen(), and you've gotta use CloseHandle() to close the file - not fclose().  Here is my fprintf which needs to be added to TCLib.mak....


//=============================================================
//   Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                By Fred Harris, March 2016
//
// cl fprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN   
//=============================================================
// cl sprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdarg.h>
#include "stdio.h"
#define EOF (-1)
#pragma comment(linker, "/defaultlib:user32.lib")

int __cdecl fprintf(HANDLE hFile, const char* format, ...)
{
char szBuff[1024];
DWORD cbWritten;
va_list argptr;
int retValue;

va_start(argptr, format);
retValue = wvsprintfA(szBuff, format, argptr);
va_end(argptr);
WriteFile(hFile, szBuff, retValue, &cbWritten, 0);

return retValue;
}

int __cdecl fwprintf(HANDLE hFile, const wchar_t* format, ...)
{
wchar_t szBuff[1024];
DWORD cbWritten;
va_list argptr;
int retValue;

va_start(argptr, format);
retValue = wvsprintfW(szBuff, format, argptr);
va_end(argptr);
WriteFile(hFile, szBuff, retValue*2, &cbWritten, 0);

return retValue;
}


     So, now with debugging support lets try to see what ails my string processing code.  Here is MemTest1.cpp, where I've isolated the string processing algorithm from the GUI app, and have it in a simple console app.  Upon examination you'll see I have a globally declared HANDLE object to provide my text file output debugging support, and I use it in main() to open a file with CreateFile().  And I have quite a few GetTickCount() calls spread around to see how many ticks its taking to do various things.  In main() you'll see I have a variable iChars set to 100000, and we pass that to DoProcessing() to see what happens.  And it does compile, link, run, and terminate normally.  Here is MemTest1.cpp...


// cl MemTest1.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#define   UNICODE
#define   _UNICODE
#include  <windows.h>
#include  "stdio.h"
#include  "tchar.h"
#define   x64
HANDLE    hFile=NULL;
extern "C" int _fltused=1;


void DoProcessing(int& iNumber, int& iStart)
{
int i=0,iCtr=0,j;
wchar_t* s1=NULL;
wchar_t* s2=NULL;
DWORD iTicks;

_ftprintf(hFile,_T("Entering DoProcessing()\r\n"));
_ftprintf(hFile,_T("  iNumber    = %d\r\n"),iNumber);
iTicks=GetTickCount()-iStart;
int iLineLength=90;
int iNumPs=iNumber/7+1;
int iPuExtLength=iNumber+iNumPs;
int iNumFullLines=iPuExtLength/iLineLength;
int iMaxMem=iPuExtLength+iNumFullLines*2;
s1=(wchar_t*)GlobalAlloc(GPTR,iMaxMem*sizeof(wchar_t));  //Allocate two buffers big enough to hold the original NUMBER of chars
s2=(wchar_t*)GlobalAlloc(GPTR,iMaxMem*sizeof(wchar_t));  //plus substitution of PUs for Ps and CrLfs after each LINE_LENGTH chunk.
_ftprintf(hFile,_T("  iMaxMem    = %d\r\n"),iMaxMem);
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d        After Memory Allocations\r\n"),iTicks);

for(i=0; i<iNumber; i++)                // 1) Create an xMB string of dashes
     s1[i]=L'-';
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d        After Filling Memory With Dashes\r\n"),iTicks);
 
for(i=0; i<iNumber; i++, iCtr++)        // 2) Change every 7th dash to a "P"
{
     if(iCtr==7)
     {
        s1[i]=L'P';
        iCtr=0;
     }
}
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d       After Putting Ps Every 7th Char\r\n"),iTicks);

iCtr=0;                                 // 3) Substitute 'PUs' for 'Ps'
for(i=0; i<iNumber; i++)
{
     if(wcsncmp(s1+i,L"P",1)==0)
     {
        wcscpy(s2+iCtr,L"PU");
        iCtr+=2;
     }
     else
     {
        s2[iCtr]=s1[i];
        iCtr++;
     }
}
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d       After wcsncmp()\r\n"),iTicks);

for(i=0; i<iPuExtLength; i++)          // 4) Replace every '-' with an 8;
{
     if(s2[i]==L'-')
        s2[i]=56;   //56 is '8'
}
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d\r\n"),iTicks);

i=0, j=0, iCtr=0;                      // 5)Put in a CrLf every 90 characters
while(i<iPuExtLength)
{
    s1[j]=s2[i];
    i++, j++, iCtr++;
    if(iCtr==iLineLength)
    {
       s1[j]=13, j++;
       s1[j]=10, j++;
       iCtr=0;
    }
}
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d\r\n"),iTicks);
GlobalFree(s1), GlobalFree(s2);
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d\r\n"),iTicks);
_ftprintf(hFile,_T("Leaving DoProcessing()\r\n\r\n"));
}


int _tmain()
{
int iTicks,iStart,iChars;
TCHAR szBuffer[16];
DWORD cbWritten;
   
hFile=CreateFile(_T("Output.txt"),GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
iStart=GetTickCount();
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("iTicks = %d\r\n\r\n"),iTicks);
iChars=100000;
DoProcessing(iChars,iStart);
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("\r\niTicks = %d\r\n"),iTicks);
_tprintf(_T("Processing Complete!\n"));
CloseHandle(hFile);

return 0;
}


Upon opening the Output.txt file we have this....


iTicks = 0

Entering DoProcessing()
  iNumber    = 100000
  iMaxMem    = 116824
  iTicks     = 0        After Memory Allocations
  iTicks     = 0        After Filling Memory With Dashes
  iTicks     = 0        After Putting Ps Every 7th Char
  iTicks     = 6568     After wcsncmp()
  iTicks     = 6568
  iTicks     = 6568
  iTicks     = 6568
Leaving DoProcessing()

iTicks = 6568


continued...

Frederick J. Harris

#1
     Its taking about six and a half seconds to process about a hundred thousand characters.  That's terribly, terribly slow!  In my original app using either PowerBASIC or C++ and the C Runtime I was processing 100 strings each of which varied in size between 1 million and 25 million characters.  And it was only taking between 12 and 20 seconds for a hundred such strings each of which was 10 to 250 times bigger than the one above!  So what I'm saying is that somehow we have some serious, serious performance problems in TCLib.lib.  Perhaps I've finally found the Achilles Heel of my work on this.  Maybe TCLib can't be used anywhere performance is necessary?  Could it possibly be that the C Runtime when it initializes is creating a heap that is hundreds of times faster than my lack of any memory management in that regard?

     Well, in looking closely at the GetTickCount() results above its possible to see where all the time is being spent.  We start out at 0 ticks in main().  Even after the memory allocations at the top of DoProcessing() using GlobalAlloc() we're still at 0 ticks, so that isn't taking any measurable time at all.  Next GetTickCount() info is after running through the 100,000 wchar_t buffer and filling it with dashes ( '-' ).  And that apparently isn't taking any measurable time as we're still at 0. 

     Next step in the algorithm is to start at the beginning of the buffer again and write a L'P' wchar_t to the buffer every 7th wchar_t.  And we're still at 0!  That's not the bottleneck.  Makes one begin to wonder what's taking all the time!

     Then comes the routine to run through the buffer again, and replace every L'P' encountered with a L"PU".  The 2nd buffer is used for that.  The way that logic works is that s1 is tested each wchar_t at a time from beginning to end for a L'P' character.  wcsncmp() is used for that.  When it encounters one it writes the L"PU" to the s2 buffer, otherwise it just writes the dash found in s1.  Counters keep track of where reads/writes occur in each buffer so that when processing is finished s2 contains everything in s1 but with Ps replaced by PUs, so naturally its longer.  And that is taking 6568 ticks or essentially the time of the whole program run.  So I do believe we've found our bottleneck.  Its strncmp/wcsncmp!!!  So lets take a look at that from strncmp.cpp....


int __cdecl wcsncmp(const wchar_t* str1, const wchar_t* str2, size_t count)
{
size_t iLen1=wcslen(str1);
size_t iLen2=wcslen(str2);
if(count>iLen1)
    return -1;
if(count>iLen2)
    return 1;
wchar_t* pStr1=(wchar_t*)malloc(count*2+2);
wchar_t* pStr2=(wchar_t*)malloc(count*2+2);
wcsncpy(pStr1,str1,count);
wcsncpy(pStr2,str2,count);
pStr1[count]=0;
pStr2[count]=0;
int iReturn=wcscmp(pStr1,pStr2);
free(pStr1);
free(pStr2);

return iReturn;
}


I definitely overdid it there in my initial zest to provide a bullet proof version of strncmp.  Its real easy for me to see what the problem is.  In my string processing algorithm I'm moving along the s1 buffer one wchar_t at a time looking for a L'P' with wcsncmp.  And every time wcsncmp is being called wcslen() is being called on both string parameters!   I believe some absolutely drastic simplification is in order!  Something like this....


//===============================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                            By Fred Harris, January 2016
//
//       cl strncmp.cpp /D "_CRT_SECURE_NO_WARNINGS" /c /W3 /DWIN32_LEAN_AND_MEAN
//===============================================================================================
#include <windows.h>
#include "malloc.h"
#include "string.h"

int __cdecl strncmp(const char* str1, const char* str2, size_t count)
{
for(size_t i=0; i<count; i++)
{
     if(str1[i]<str2[i])
        return -1;
     if(str1[i]>str2[i])
        return 1;
}

return 0;
}

int __cdecl wcsncmp(const wchar_t* str1, const wchar_t* str2, size_t count)
{
for(size_t i=0; i<count; i++)
{
     if(str1[i]<str2[i])
        return -1;
     if(str1[i]>str2[i])
        return 1;
}

return 0;
}


So if you bring up your command prompt window where you have TCLib you can remove the slow version of strncmp like this...


Lib TCLib.lib /remove:strncmp.obj  [ENTER]


And once you compile the new strncmp.cpp above you can insert that in TCLib like this...


Lib TCLib.lib strncmp.obj   [ENTER]


And while you are at it you might want to do the same with fprintf.cpp.  Also, string.h, stdio.h, and tchar.h need to be updated with the fprintf information.

     Having gotten through that and running MemTest1.cpp after recompiling we end up with this Output.txt file...


iTicks = 0

Entering DoProcessing()
  iNumber    = 100000
  iMaxMem    = 116824
  iTicks     = 0        After Memory Allocations
  iTicks     = 0        After Filling Memory With Dashes
  iTicks     = 0        After Putting Ps Every 7th Char
  iTicks     = 0        After wcsncmp()
  iTicks     = 0
  iTicks     = 0
  iTicks     = 0
Leaving DoProcessing()

iTicks = 0


     How's that for improvement?  On a measly 200,000 byte buffer its running so fast now that its underneath the resolution of GetTickCount(), which I believe is somewhere in the neighborhood of 16 to 32 ticks, and its coming in a 0 for the whole run instead of the previous 6568 milliseconds!  On one calculation I made with larger buffers I calculated its now 963 times faster!  So I do believe we'll be good to go on the main multithreaded GUI version, which is this....

continued...

Frederick J. Harris


// T12_TCLib64.cpp
// cl T12_TCLib64.cpp /O1 /Os /GS- /FeT12_TCLib64.exe /link TCLib.lib kernel32.lib user32.lib gdi32.lib
#ifndef UNICODE
    #define UNICODE                   // Program shows how to produce real time scrolling output of data
#endif                                // processing as processing occurs using threads.  The 'main' or
#ifndef _UNICODE                      // GUI thread contains a main program object with a 'Process Data'
    #define _UNICODE                  // and 'Cancel' button.  When the user clicks the 'Process Data'
#endif                                // button a 2nd top level window is opened with a CreateWindowEx()
#include <windows.h>                  // call and that is the 'output' window which displays program
#include "stdio.h"                    // outputs.  The output window of class "Process" is registered
#include "tchar.h"                    // in fnMain_OnCreate(), which is the constructor function, so to
#include "T12_TCLib.h"                // speak, for the "Main" Window Class, RegisterClassEx()'ed...


int rand(void)                        // down in WinMain().  The 'Cancel' button on the main start up...
{                                     // window is for terminating data processing early before it is
next = next * 1103515245 + 12345;    // finished.  The long data processing job I decided upon to demon-
return (unsigned int)(next>>16) & RAND_MAX;   // strate this technique is a follows ...
}                                     //   1) Allocate a multi-million byte array of dash characters;
                                      //   2) Change every 7th space to a 'P';
                                      //   3) Replace every 'P' with a "PU', thereby growing the buffer;
int Rnd(int iMin, int iMax)           //   4) Replace every remaining dash with an '8';
{                                     //   5) Insert a carriage return / line feed every 90 characters,
double dblRange,dblMaxFactor,dblRandomNumber;

dblRange=iMax-iMin;                         
dblMaxFactor=dblRange/RAND_MAX;             
dblRandomNumber=(double)rand();     

return iMin+dblMaxFactor*dblRandomNumber;
}                                     
                                     
                                     
void Format(wchar_t* pBuffer, size_t iNumber) // This algorithm is fully contained within the DoProcessing()
{                                             // function below.  The 1st parameter of DoProcessing() is
wchar_t szBuf1[24];                          // iNumber of type size_t.  The program calls this function
wchar_t szBuf2[24];                          // LINE_BUFFER_COUNT - 2 times.  At this time I have
size_t iDigit=0;                             // LINE_BUFFER_COUNT #defined as 102, so DoProcessing() would
size_t iLen=0;                               // get called 100 times - each with a different iNumber para-
size_t iCtr=1;                               // meter.  Where DoProcessing() gets called from is the worker
size_t j=0;                                  // thread function ProcessingThread().  The worker thread, as 
                                              // well as the output window to which the thread draws output,
memset(szBuf1,0,24*sizeof(wchar_t));         // is created in the button click procedure for the button
memset(szBuf2,0,24*sizeof(wchar_t));         // on the main or start up form/window/dialog described at
#ifdef x64                                   // first above.  That procedure would be fnMain_OnCommand(),
iLen=swprintf(szBuf1,L"%llu",iNumber);       // which handles the program's response to 'Process Data' or
#else                                        // 'cancel' button clicks.  As an aside, the Format() function
iLen=swprintf(szBuf1,L"%u",iNumber);         // just left inserts commas every three places in the iNumber
#endif                                       // parameter of DoProcessing(), e.g., this - 123,456,789,
_wcsrev(szBuf1);                             // instead of this - 123456789.  And the Rnd() function just
for(size_t i=0; i<iLen; i++)                 // above it returns a random integral value between the iMin
{                                            // and iMax parameters.  In the ProcessingThread() function,
     if(iCtr==3)                              // as just described, there is a for loop which runs from 1
     {                                        // to LINE_BUFFER_COUNT -2, i.e., 1 to 98 if LINE_BUFFER_COUNT
        iDigit++;                             // is set to 100, and Rnd() will be called that many times
        szBuf2[j]=szBuf1[i];                  // like so ...
        if(iDigit<iLen)                       //
        {                                     // iNumber = Rnd(10000, 40000);
           j++;                               //
           szBuf2[j]=L',';                    // ... generating 100 random numbers between 10 thousand and 40
        }                                     // thousand which numbers will be passed on to DoProcessing() as
        j++, iCtr=1;                          // follows within the for loop in ProcessingThread()...
     }                                        //
     else                                     // DoProcessing(iNumber,iTickCount);
     {                                        //
        iDigit++;                             // And each call will start my 1) through 5) sequence again
        szBuf2[j]=szBuf1[i];                  // with a new buffer size to do the dashes, P, and PU thing
        j++, iCtr++;                          // as described above.  The Print() function just below
     }                                        // will draw lines of text to the output window such as "It
}                                            // Took 0.218 Seconds To Complete Processing Job Number 32
_wcsrev(szBuf2);                             // Involving 16,913,083 Bytes!".  All in all, its a real mean
wcscpy(pBuffer,szBuf2);                      // data processing machine!
}


void ErrorMemFree(wchar_t** pPtrs, int iNum)  // This little thingie just left likely will never be called.
{                                             // Its purpose is to unravel memory allocations up to the
HANDLE hHeap=NULL;                           // point where a memory allocation failure occurred.

hHeap=GetProcessHeap();
if(hHeap)
{
    for(int i=0; i<iNum; i++)
    {
        if(pPtrs[i])
        {
           HeapFree(hHeap,0,pPtrs[i]);
           pPtrs[i]=NULL;
        }
    }
    HeapFree(hHeap,0,pPtrs);
    pPtrs=NULL;
}
}


void Print(HWND hWnd, ScrollData* pScrDta, size_t& iLine, wchar_t* pszStr)
{
size_t iLen,iRequiredBytes,iWidth;

if(iLine<LINE_BUFFER_COUNT)
{
    if(pszStr)
    {
       iLen=wcslen(pszStr);
       iWidth=iLen*pScrDta->cxChar;
       if(iWidth>(size_t)pScrDta->iMaxWidth)
       {
          pScrDta->iMaxWidth=(int)iWidth;
          SCROLLINFO si;
          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(hWnd, SB_HORZ, &si, TRUE);
       }
       iRequiredBytes=iLen*sizeof(wchar_t)+sizeof(wchar_t);
       pScrDta->pPtrs[iLine]=(wchar_t*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,iRequiredBytes);
       wcscpy(pScrDta->pPtrs[iLine],pszStr);
       if(iLine>=pScrDta->cyClient/pScrDta->cyChar)
       {
          InvalidateRect(hWnd,NULL,FALSE);
          SendMessage(hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);
       }
       else
          InvalidateRect(hWnd,NULL,FALSE);
       iLine++;
    }
    else
    {
       if(iLine>=pScrDta->cyClient/pScrDta->cyChar)
          SendMessage(hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);
       iLine++;
    }
}
}


void DoProcessing(size_t& iNumber, size_t& iTickCount)
{
int i=0,iCtr=0,j;
wchar_t* s1=NULL;
wchar_t* s2=NULL;

DWORD tick=GetTickCount();
int iLineLength=90;
int iNumPs=(int)iNumber/7+1;
int iPuExtLength=(int)iNumber+iNumPs;
int iNumFullLines=iPuExtLength/iLineLength;
int iMaxMem=iPuExtLength+iNumFullLines*2;
s1=(wchar_t*)GlobalAlloc(GPTR,iMaxMem*sizeof(wchar_t));  //Allocate two buffers big enough to hold the original NUMBER of chars
s2=(wchar_t*)GlobalAlloc(GPTR,iMaxMem*sizeof(wchar_t));  //plus substitution of PUs for Ps and CrLfs after each LINE_LENGTH chunk.

for(i=0; i<iNumber; i++)                // 1) Create a  string of dashes
     s1[i]=L'-';

for(i=0; i<iNumber; i++, iCtr++)        // 2) Change every 7th dash to a "P"
{
     if(iCtr==7)
     {
        s1[i]=L'P';
        iCtr=0;
     }
}

iCtr=0;                                 // 3) Substitute 'PUs' for 'Ps'
for(i=0; i<iNumber; i++)
{
     if(wcsncmp(s1+i,L"P",1)==0)
     {
        wcscpy(s2+iCtr,L"PU");
        iCtr+=2;
     }
     else
     {
        s2[iCtr]=s1[i];
        iCtr++;
     }
}

for(i=0; i<iPuExtLength; i++)          // 4) Replace every '-' with an 8;
{
     if(s2[i]==L'-')
        s2[i]=56;   //56 is '8'
}

i=0, j=0, iCtr=0;                      // 5)Put in a CrLf every 90 characters
while(i<iPuExtLength)
{
    s1[j]=s2[i];
    i++, j++, iCtr++;
    if(iCtr==iLineLength)
    {
       s1[j]=13, j++;
       s1[j]=10, j++;
       iCtr=0;
    }
}
iTickCount=GetTickCount()-tick;
GlobalFree(s1), GlobalFree(s2);
}


DWORD ProcessingThread(LPVOID hProcess)
{
wchar_t szSeconds[8],szJob[128],szJobNum[8],szNumber[32];
size_t iLineCount,iNumber,iTickCount,iLine=0;
double dblSeconds,dblTotal=0.0;
ScrollData* pScrDta=NULL;
size_t iTotalBytes=0;

pScrDta=(ScrollData*)GetWindowLongPtr((HWND)hProcess,0);
if(!pScrDta)
    return FALSE;
iLineCount=pScrDta->iNumLines-2;
for(int i=1; i<=iLineCount; i++)
{
     if(GetWindowLongPtr((HWND)hProcess,1*sizeof(void*)))
     {
        iNumber=Rnd(1000000, 25000000);
        iTotalBytes=iTotalBytes+iNumber;
        DoProcessing(iNumber,iTickCount);
        dblSeconds=(double)iTickCount/(double)1000.0;
        dblTotal=dblTotal+dblSeconds;
        FltToTch(szSeconds, dblSeconds, 8, 3, _T('.'),true);
        wcscpy(szJob,(wchar_t*)L"It Took ");
        wcscat(szJob,szSeconds);
        wcscat(szJob,(wchar_t*)L" Seconds To Complete Processing Job # ");
        swprintf(szJobNum,L"%d",i);
        wcscat(szJob,szJobNum);
        wcscat(szJob,(wchar_t*)L" Involving ");
        Format(szNumber,(size_t)iNumber);
        wcscat(szJob,szNumber);
        wcscat(szJob,(wchar_t*)L" Bytes!");
        Print((HWND)hProcess,pScrDta,iLine,szJob);
     }
     else
        return FALSE;
}
SetWindowLongPtr((HWND)hProcess,1*sizeof(void*),(LONG_PTR)FALSE);
szJob[0]=NULL;
Print((HWND)hProcess,pScrDta,iLine,szJob);
Format(szNumber,iTotalBytes);
wcscpy(szJob,(wchar_t*)L"Processing Complete.  ");
wcscat(szJob,szNumber);
wcscat(szJob,(wchar_t*)L" Bytes Were Processed In ");
FltToTch(szSeconds, dblTotal, 8, 3, _T('.'),true);
wcscat(szJob,szSeconds);
wcscat(szJob,(wchar_t*)L" Seconds.");
Print((HWND)hProcess,pScrDta,iLine,szJob);

return TRUE;
}


long fnProcess_OnCreate(WndEventArgs& Wea)  // Index    Offset    What's Stored There
{                                           // =======================================================
CREATESTRUCT* pCreateStruct=NULL;          // 0        0  -  7   ScrollData* ( pScrDta)
ScrollData* pScrDta=NULL;                  // 1        8  - 15   blnContinue (processing in progress)
HANDLE hHeap=NULL;                         // 2       16  - 23   hMain (Main Wnd HWND)
HFONT hFont=NULL;
TEXTMETRIC tm;
HWND hMain;
HDC hdc;

pCreateStruct=(CREATESTRUCT*)Wea.lParam;
hMain=(HWND)pCreateStruct->lpCreateParams;
SetWindowLongPtr(Wea.hWnd,2*sizeof(void*),(LONG_PTR)hMain);
hHeap=GetProcessHeap();
pScrDta=(ScrollData*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(ScrollData));
if(!pScrDta)
    return -1;
SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pScrDta);
hdc = GetDC(Wea.hWnd);
hFont=CreateFont(-1*(10*GetDeviceCaps(hdc,LOGPIXELSY))/72,0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,0,(wchar_t*)L"Courier New");
if(!hFont)
    return -1;
HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
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);
pScrDta->iNumLines=LINE_BUFFER_COUNT;
pScrDta->pPtrs=(wchar_t**)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(wchar_t*) * pScrDta->iNumLines);
if(!pScrDta->pPtrs)
    return -1;

return 0;
}


long fnProcess_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;
}


long fnProcess_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;
}


long fnProcess_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;
}


long fnProcess_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*(10*GetDeviceCaps(hdc,LOGPIXELSY))/72,0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,0,(wchar_t*)L"Courier New");
    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++)
    {
        if(pScrDta->pPtrs[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;
}


long fnProcess_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;
}


long fnProcess_OnSetCursor(WndEventArgs& Wea)
{
if(GetWindowLongPtr(Wea.hWnd,1*sizeof(void*)))
    SetCursor(LoadCursor(NULL,IDC_WAIT));
else
    DefWindowProc(Wea.hWnd,WM_SETCURSOR,Wea.wParam,Wea.lParam);

return 0;
}


long fnProcess_OnDestroy(WndEventArgs& Wea)
{
HWND hMain=NULL,hBtn=NULL;
ScrollData* pScrDta=NULL;
HANDLE hHeap=NULL;

hMain=(HWND)GetWindowLongPtr(Wea.hWnd,2*sizeof(void*));
hBtn=GetDlgItem(hMain,IDC_BTN_PROCESS_DATA);
EnableWindow(hBtn,TRUE);
SetWindowLongPtr(hMain,0,(LONG_PTR)FALSE);
hHeap=GetProcessHeap();
pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
if(pScrDta->pPtrs)
{
    for(int i=0; i<pScrDta->iNumLines; i++)
    {
        if(pScrDta->pPtrs[i])
           HeapFree(hHeap,0,pScrDta->pPtrs[i]);
    }
    HeapFree(hHeap,0,pScrDta->pPtrs);
}

return 0;
}


LRESULT CALLBACK fnProcess(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea;

for(unsigned int i=0; i<dim(ProcessEventHandler); i++)
{
     if(ProcessEventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*ProcessEventHandler[i].fnPtr)(Wea);
     }
}

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


long fnMain_OnCreate(WndEventArgs& Wea)
{
wchar_t szClassName[]=L"Process";
WNDCLASSEX wc;

Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
CreateWindowEx(0,L"button",L"Process Data",WS_CHILD|WS_VISIBLE,75,20,125,30,Wea.hWnd,(HMENU)IDC_BTN_PROCESS_DATA,Wea.hIns,0);
CreateWindowEx(0,L"button",L"Cancel",WS_CHILD|WS_VISIBLE,75,70,125,30,Wea.hWnd,(HMENU)IDC_BTN_CANCEL,Wea.hIns,0);
memset(&wc,0,sizeof(wc));
wc.lpszClassName = szClassName;                  wc.lpfnWndProc   = fnProcess;
wc.cbSize        = sizeof (WNDCLASSEX);          wc.hInstance     = Wea.hIns;
wc.hCursor       = LoadCursor(NULL,IDC_ARROW),   wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.cbWndExtra    = 3*sizeof(void*),              wc.style         = CS_HREDRAW | CS_VREDRAW;
RegisterClassEx(&wc);

return 0;
}


long fnMain_OnCommand(WndEventArgs& Wea)
{
wchar_t szClassName[]=L"Process";
HWND hProcess=NULL;

switch(LOWORD(Wea.wParam))
{
   case IDC_BTN_PROCESS_DATA:
     {
        DWORD lpThreadId=0;
        hProcess=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,450,100,850,392,0,0,GetModuleHandle(NULL),Wea.hWnd);
        if(hProcess)
        {
           ShowWindow(hProcess,SW_SHOWNORMAL);
           SetWindowLongPtr(hProcess,1*sizeof(void*),(LONG_PTR)TRUE);
           SetWindowLongPtr(Wea.hWnd,0*sizeof(void*),(LONG_PTR)hProcess);
           EnableWindow((HWND)Wea.lParam,FALSE);
           HANDLE hThread=CreateThread(NULL,0,ProcessingThread,hProcess,0,&lpThreadId);

        }
        break;
     }
   case IDC_BTN_CANCEL:
     {
        hProcess=FindWindow(szClassName,szClassName);
        if(hProcess)
        {
           SetWindowLongPtr(hProcess,1*sizeof(void*),(LONG_PTR)FALSE);
           SetWindowLongPtr(Wea.hWnd,0*sizeof(void*),(LONG_PTR)FALSE);
        }
        break;
     }
}

return 0;
}


long fnMain_OnSetCursor(WndEventArgs& Wea)
{
if(GetWindowLongPtr(Wea.hWnd,0))
    SetCursor(LoadCursor(NULL,IDC_WAIT));
else
{
    DefWindowProc(Wea.hWnd, WM_SETCURSOR, Wea.wParam, Wea.lParam);
    SetCursor(LoadCursor(NULL,IDC_ARROW));
}

return 0;
}


long fnMain_OnDestroy(WndEventArgs& Wea)
{
PostQuitMessage(0);
return 0;
}


LRESULT CALLBACK fnMain(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)  // Main Window Window Procedure
{
WndEventArgs Wea;

for(unsigned int i=0; i<dim(MainEventHandler); i++)
{
     if(MainEventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*MainEventHandler[i].fnPtr)(Wea);
     }
}

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


int WINAPI _tWinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPTSTR lpszArgument, int iShow)
{
wchar_t szClassName[]=L"Main";
WNDCLASSEX wc;
MSG messages;
HWND hWnd;

memset(&wc,0,sizeof(wc));
wc.lpszClassName=szClassName;                wc.lpfnWndProc=fnMain;
wc.cbSize=sizeof (WNDCLASSEX);               wc.hInstance=hIns;
wc.hCursor=LoadCursor(NULL,IDC_ARROW),       wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;
wc.cbWndExtra=1*sizeof(void*);
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,275,625,280,160,HWND_DESKTOP,0,hIns,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
    TranslateMessage(&messages);
    DispatchMessage(&messages);
}

return messages.wParam;
}


And here is the header...


// T12_TCLib.h
#ifndef Main_h
#define Main_h

#define dim(x)                (sizeof(x) / sizeof(x[0])) // Used in for loops of Window Procedures
#define LINE_BUFFER_COUNT     102                        // How many lines to set up for scrolling
#define IDC_BTN_PROCESS_DATA  1500                       // Control Id For Button to process data
#define IDC_BTN_CANCEL        1505                       // Control Id for Cancel Button
#define RND_MAX               32767

struct WndEventArgs                                      // This struct amalgamates the parameters of the
{                                                        // Window Procedure together into one object.  .NET
HWND                         hWnd;                      // does this to, e.g., 'PaintEventArgs'.
WPARAM                       wParam;                   
LPARAM                       lParam;                   
HINSTANCE                    hIns;                     
};

long fnMain_OnCreate          (WndEventArgs& Wea);       // These function proto-types just left are event
long fnMain_OnCommand         (WndEventArgs& Wea);       // or message handling procedures for the two Window
long fnMain_OnSetCursor       (WndEventArgs& Wea);       // Classes RegisterClassEx()'ed in this App, i.e.,
long fnMain_OnDestroy         (WndEventArgs& Wea);       // "Main" and "Process".  Just below an EVENTHANDLER 

long fnProcess_OnCreate       (WndEventArgs& Wea);       // object is defined, which contains two members. 
long fnProcess_OnSize         (WndEventArgs& Wea);       // The first member, iMsg, is a #define from the
long fnProcess_OnVScroll      (WndEventArgs& Wea);       // various Window's header files, e.g., WM_CREATE
long fnProcess_OnHScroll      (WndEventArgs& Wea);       // is equal to 1; WM_COMMAND is equal to 273.  The
long fnProcess_OnMouseWheel   (WndEventArgs& Wea);       // other member of EVENTHANDLER, fnPtr, is the run-
long fnProcess_OnSetCursor    (WndEventArgs& Wea);       // time address of the event/message handling proc-
long fnProcess_OnPaint        (WndEventArgs& Wea);       // edure associated with the respective or associated
long fnProcess_OnDestroy      (WndEventArgs& Wea);       // message held in iMsg.  An array of these objects

struct EVENTHANDLER                                      // are created to hold each event/event procedure
{                                                        // address pair for each of the two Window Classes
unsigned int                 iMsg;                      // Registered.  For example, MainEventHandler[] has
long                         (*fnPtr)(WndEventArgs&);   // four elements 0 through 3. 
};                                                       // MainEventHandler[0].iMsg=1=WM_CREATE, and

const EVENTHANDLER            MainEventHandler[]=        // MainEventHandler[0].fnPtr will hold the runtime
{                                                        // address of fnMain_OnCreate().  A for loop is used
{WM_CREATE,                  fnMain_OnCreate},          // in fnMain - the Window Procedure for the "Main"
{WM_COMMAND,                 fnMain_OnCommand},         // Window Class, to iterate through each array element
{WM_SETCURSOR,               fnMain_OnSetCursor},       // trying to make a match between the MSG parameter
{WM_DESTROY,                 fnMain_OnDestroy}          // of the Window Procedure, and the iMsg member of
};                                                       // the EVENTHANDLER object.  If a match is made, the

const EVENTHANDLER            ProcessEventHandler[]=     // associated function is called through its address
{                                                        // held in the fnPtr member.  Pretty, d*** slick!
{WM_CREATE,                  fnProcess_OnCreate},       // I wish I'd have been smart enough to figure some-
{WM_SIZE,                    fnProcess_OnSize},         // thing like this out for myself, but I learned it
{WM_VSCROLL,                 fnProcess_OnVScroll},      // from Douglas Boling who writes books for Microsoft
{WM_HSCROLL,                 fnProcess_OnHScroll},      // Press.  And he gives credit for it to Ray Duncan.
{WM_MOUSEWHEEL,              fnProcess_OnMouseWheel},   // So what you are really seeing here is an alter-
{WM_SETCURSOR,               fnProcess_OnSetCursor},    // native to the switch construct usually used in
{WM_PAINT,                   fnProcess_OnPaint},        // Window Procedures to map incoming messages to the
{WM_DESTROY,                 fnProcess_OnDestroy}       // code which handles each respective message.  Its
};                                                       // a much more elegant solution to the problem.

struct ScrollData                                        // Further note how the dim macro is used in the for
{                                                        // loop to determine the number of elements in each
wchar_t**                    pPtrs;                     // array.
int                          iNumLines;
int                          cxChar;
int                          cxCaps;
int                          cyChar;
int                          cxClient;
int                          cyClient;
int                          iMaxWidth;
};

extern "C" int _fltused  =    1;
static unsigned int next =    1;
#endif


continued...

Frederick J. Harris

     I'll amalgamate all this into a zip, and the new TCLib.lib will have all the improvements and additions.  But before I close, I'd like to mention that with the addition of fprintf.obj to TCLib.lib, I can now present a debug version of ec02a.cpp where defining Debug when TCLib is being used will result in debug output.  Here is the output file created when Debug is defined...


Entering fnWndProc_OnCreate()
  Wea.hWnd         = 00000000003105F0
  sizeof(LRESULT)  = 8
  blnDpiAware      = 1
  pDpiData         = 000001DD230C0AD0
  dpiX             = 96
  dpiY             = 96
  pDpiData->rx     = 1.000
  pDpiData->ry     = 1.000
  iCxScreen        = 1920
  iCyScreen        = 1080
  x                = 791
  y                = 380
Leaving fnWndProc_OnCreate()

Entering fnWndProc_OnDestroy()
  Wea.hWnd         = 00000000003105F0
  pDpiData         = 000001DD230C0AD0
  blnFree          = 1
  hFont1           = 0000000000000000
  blnFree(hFont1)  = 1
  hFont2           = 0000000000000000
  blnFree(hFont2)  = 1
Leaving fnWndProc_OnDestroy()


Here's the new ec02a.cpp and Form1.h headers for this example of Jim's ...


// Uses/links with  LIBCMT:   cl ec02a.cpp /O1 /Os /MT kernel32.lib user32.lib gdi32.lib
// Uses/links with TCLib:     cl ec02a.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib user32.lib gdi32.lib
//  5,632 Bytes               TCLib, x86, VC19, UNICODE
//  6,144 Bytes               TCLib, x64, VC15, UNICODE
//  7,168 Bytes               TCLib, x64, VC19, UNICODE
// 19,968 Bytes               GCC 4.8, x64, UNICODE
// 88,064 Bytes               VC15, x64 Std. UNICODE, Build With C Runtime
#define TCLib
#ifndef UNICODE
   #define UNICODE            // App demonstrates Dpi Aware coding.  Note, you can't build with both
#endif                        // #define TCLib and #define Debug.  As of yet I have not provided
#ifndef _UNICODE              // FILE* i/o capabilities in TCLib.  If TCLib is defined, TCLib specific
   #define _UNICODE           // versions of stdio.h and tchar.h must be used.  Hence, those files
#endif                        // need to be placed in the app's directory, or pathed specifically.
#include <windows.h>
#ifdef TCLib                  // I'm explicitely calling SetProcessDPIAware() in SetMyProcessDpiAware()
   #include "stdio.h"         // because that function isn't available in all build environments
   #include "tchar.h"         // out there.
#else   
   #include <stdio.h>
   #include <tchar.h>
#endif   
#include "Form1.h"            // If Debug is defined an Output.txt Log File is created, and debug
#define Debug                 // information logged to there.
#ifdef Debug
   HANDLE fp=NULL;
#endif


void SetMyProcessDpiAware()   // This function isn't available on the older GCC versions I have.
{                             // Calling this function tells Windows the app is High DPI Aware,
BOOL (__stdcall* pFn)(void); // and so Windows won't virtualize fonts and sizes of things and
                              // wreck them.
HINSTANCE hInstance=LoadLibrary(_T("user32.dll"));
if(hInstance)
{
    pFn=(BOOL (__stdcall*)(void))GetProcAddress(hInstance,"SetProcessDPIAware");
    if(pFn)
       pFn();
    FreeLibrary(hInstance);
}
}


LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)
{
BOOL blnDpiAware=FALSE;      // This is the creation/instantiation function, i.e., Constructor
DpiData* pDpiData=NULL;      // for the "Form1" Class registered in WinMain, i.e., the main
TCHAR szBuffer[6];           // and only app window or start up form/dialog/window.  In WinMain
HANDLE hHeap=NULL;           // I allocate three 'spots' for app specific data in the
HFONT hFont1=NULL;           // WNDCLASS::cbWndExtra bytes.  That would be 3 * sizeof(void*) or
HFONT hFont2=NULL;           // 24 bytes in x64 and 12 bytes in x86.  I store the DpiData* in
int dpiX,dpiY;               // offset #1, hFont1 in offset #2, and hFont2 in offset #3.
HDC hDC=NULL;               
HWND hCtl;

#ifdef Debug
fp=CreateFile(_T("Output.txt"),GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
_ftprintf(fp,_T("Entering fnWndProc_OnCreate()\r\n"));
_ftprintf(fp,_T("  Wea.hWnd         = %p\r\n"),Wea.hWnd);
_ftprintf(fp,_T("  sizeof(LRESULT)  = %Iu\r\n"),sizeof(LRESULT));
#endif
Wea.hIns    = ((LPCREATESTRUCT)Wea.lParam)->hInstance;
hHeap       = GetProcessHeap();
blnDpiAware = IsProcessDPIAware();
#ifdef Debug
_ftprintf(fp,_T("  blnDpiAware      = %d\r\n"),blnDpiAware);
#endif

// Deal With DPI                                 // This code will normalize, fix, whatever you want
hDC          = GetDC(NULL);                      // to call it, the bad things that happen on modern
dpiX         = GetDeviceCaps(hDC, LOGPIXELSX);   // displays when the user alters DPI settings through
dpiY         = GetDeviceCaps(hDC, LOGPIXELSY);   // Control Panel.  If code like this isn't implemented,
pDpiData     = (DpiData*)HeapAlloc               // and the user alters DPI settings from those used
(                                                // by the coder (you) when the app was developed, text
  hHeap,                                          // and controls will almost certainly be 'clipped', and
  HEAP_ZERO_MEMORY,                               // fonts will be 'virtualized' such that they'll become
  sizeof(DpiData)                                 // fuzzy.  Note I have a DpiData struct defined in
);                                               // Form1.h.  The purpose of that is to persist DPI
if(!pDpiData)                                    // factors across function calls.  Not needed here, but
    return -1;                                    // useful in more complex apps.
SetWindowLongPtr(Wea.hWnd,0*sizeof(void*),(LONG_PTR)pDpiData);
pDpiData->rx = dpiX/96.0;
pDpiData->ry = dpiY/96.0;
ReleaseDC(Wea.hWnd,hDC);
#ifdef Debug
_ftprintf(fp,_T("  pDpiData         = %p\r\n"),pDpiData);
_ftprintf(fp,_T("  dpiX             = %d\r\n"),dpiX);
_ftprintf(fp,_T("  dpiY             = %d\r\n"),dpiY);
FltToTch(szBuffer, pDpiData->rx, 6, 3, _T('.'),true);
_ftprintf(fp,_T("  pDpiData->rx     = %s\r\n"),szBuffer);
FltToTch(szBuffer, pDpiData->ry, 6, 3, _T('.'),true);
_ftprintf(fp,_T("  pDpiData->ry     = %s\r\n"),szBuffer);
#endif

// Position/Center Main App Window
int iCxScreen=GetSystemMetrics(SM_CXSCREEN);
int iCyScreen=GetSystemMetrics(SM_CYSCREEN);
int x = (iCxScreen/2 - 338/2)/pDpiData->rx;
int y = (iCyScreen/2 - 320/2)/pDpiData->ry;
MoveWindow(Wea.hWnd,SizX(x),SizY(y),SizX(338),SizY(320),FALSE);
#ifdef Debug
_ftprintf(fp,_T("  iCxScreen        = %d\r\n"),iCxScreen);
_ftprintf(fp,_T("  iCyScreen        = %d\r\n"),iCyScreen);
_ftprintf(fp,_T("  x                = %d\r\n"),x);
_ftprintf(fp,_T("  y                = %d\r\n"),y);
#endif
 
// Instantiate Child Window Objects
double dblFont1 = 10.0;
double dblFont2 = 10.0;
DWORD  dwStyles = WS_CHILD|WS_VISIBLE|WS_BORDER|ES_AUTOVSCROLL|ES_WANTRETURN|ES_AUTOHSCROLL|ES_MULTILINE;

hCtl=CreateWindow(_T("edit"),NULL,dwStyles,SizX(15),SizY(15),SizX(295),SizY(112),Wea.hWnd,(HMENU)IDC_EDIT1,Wea.hIns,NULL);
hFont1=CreateFont(-MulDiv(dblFont1,dpiY,72),0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_DONTCARE,_T("Arial"));
SetWindowLongPtr(Wea.hWnd,1*sizeof(void*),(LONG_PTR)hFont1);
SendMessage(hCtl, (UINT)WM_SETFONT, (WPARAM)hFont1, (LPARAM)TRUE);
SetWindowText(hCtl, _T(" Text in this control set to RED ON WHITE"));

hCtl=CreateWindow(_T("edit"),NULL,dwStyles,SizX(15),SizY(160),SizX(295),SizY(112),Wea.hWnd,(HMENU)IDC_EDIT2,Wea.hIns,NULL);
hFont2=CreateFont(-MulDiv(dblFont2,dpiY,72),0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_DONTCARE,_T("Garamond"));
SetWindowLongPtr(Wea.hWnd,2*sizeof(void*),(LONG_PTR)hFont2);
SendMessage(hCtl, (UINT)WM_SETFONT, (WPARAM)hFont2, (LPARAM)TRUE);
SetWindowText(hCtl, _T(" Text in this control set to Green ON BLACK"));
#ifdef Debug
_ftprintf(fp,_T("Leaving fnWndProc_OnCreate()\r\n\r\n"));
#endif

return 0;
}


LRESULT fnWndProc_OnCtlColorEdit(WndEventArgs& Wea)
{
int iCtlId=GetDlgCtrlID((HWND)Wea.lParam);

if(iCtlId==IDC_EDIT1)
{
    SetTextColor((HDC)Wea.wParam, RGB(255, 0, 0));
   SetBkMode((HDC)Wea.wParam,TRANSPARENT);
   return (LRESULT)GetStockObject(WHITE_BRUSH);
}
if(iCtlId==IDC_EDIT2)
{
    SetTextColor((HDC)Wea.wParam, RGB(0, 255, 0));
   SetBkMode((HDC)Wea.wParam,TRANSPARENT);
   return (LRESULT)GetStockObject(BLACK_BRUSH);
}  

return DefWindowProc(Wea.hWnd, WM_CTLCOLOREDIT, Wea.wParam, Wea.lParam);
}


LRESULT fnWndProc_OnDestroy(WndEventArgs& Wea)
{
DpiData* pDpiData=NULL;
BOOL blnFree=FALSE;
HFONT hFont1=NULL;
HFONT hFont2=NULL;
HFONT hFont=NULL;

#ifdef Debug
_ftprintf(fp,_T("Entering fnWndProc_OnDestroy()\r\n"));
_ftprintf(fp,_T("  Wea.hWnd         = %p\r\n"),Wea.hWnd);
#endif
pDpiData=(DpiData*)GetWindowLongPtr(Wea.hWnd,0*sizeof(void*));
blnFree=HeapFree(GetProcessHeap(),0,pDpiData);
#ifdef Debug
_ftprintf(fp,_T("  pDpiData         = %p\r\n"),pDpiData);
_ftprintf(fp,_T("  blnFree          = %d\r\n"),blnFree);
#endif
hFont=(HFONT)GetWindowLongPtr(Wea.hWnd,1*sizeof(void*));
blnFree=DeleteObject(hFont);
#ifdef Debug
_ftprintf(fp,_T("  hFont1           = %p\r\n"),hFont1);
_ftprintf(fp,_T("  blnFree(hFont1)  = %d\r\n"),blnFree);
#endif
hFont=(HFONT)GetWindowLongPtr(Wea.hWnd,2*sizeof(void*));
blnFree=DeleteObject(hFont);
#ifdef Debug
_ftprintf(fp,_T("  hFont2           = %p\r\n"),hFont2);
_ftprintf(fp,_T("  blnFree(hFont2)  = %d\r\n"),blnFree);
#endif
PostQuitMessage(0);
#ifdef Debug
_ftprintf(fp,_T("Leaving fnWndProc_OnDestroy()\r\n"));
CloseHandle(fp);
#endif

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 _tWinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPTSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Form1");
MSG messages;
WNDCLASS wc;
HWND hWnd;

memset(&wc,0,sizeof(WNDCLASS));
SetMyProcessDpiAware();
wc.lpszClassName = szClassName,                     wc.lpfnWndProc   = fnWndProc;
wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION),  wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
wc.hInstance     = hIns,                            wc.hCursor       = LoadCursor(NULL,IDC_ARROW),         
wc.cbWndExtra    = 3*sizeof(void*);
RegisterClass(&wc);
hWnd=CreateWindow(szClassName,_T("Edit Ctrl Colors and Fonts"),WS_OVERLAPPEDWINDOW,0,0,0,0,HWND_DESKTOP,0,hIns,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
    TranslateMessage(&messages);
    DispatchMessage(&messages);
}

return messages.wParam;
}


Form1.h

//Form1.h
#ifndef Form_h
#define Form_h

#define IDC_EDIT1                 2000
#define IDC_EDIT2                 2005
#define dim(x)                    (sizeof(x) / sizeof(x[0]))
#define SizX(x)                   (int)(x * pDpiData->rx)       // For DPI Scaling Calculations And Purposes
#define SizY(y)                   (int)(y * pDpiData->ry)       // For DPI Scaling Calculations And Purposes
#ifdef TCLib
   extern "C" int                 _fltused=1;
#endif
   
struct WndEventArgs
{
HWND                             hWnd;
WPARAM                           wParam;
LPARAM                           lParam;
HINSTANCE                        hIns;
};

LRESULT fnWndProc_OnCreate        (WndEventArgs& Wea);
LRESULT fnWndProc_OnCtlColorEdit  (WndEventArgs& Wea);
LRESULT fnWndProc_OnDestroy       (WndEventArgs& Wea);

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

const EVENTHANDLER EventHandler[]=
{
{WM_CREATE,                      fnWndProc_OnCreate},
{WM_CTLCOLOREDIT,                fnWndProc_OnCtlColorEdit},
{WM_DESTROY,                     fnWndProc_OnDestroy}
};

struct DpiData                   
{                                 
double                           rx;
double                           ry;
};

#endif

     In closing this post I'd like to say that the above program, which requires floating point support to allow high DPI Aware functionality, and my previous Multi-threaded string processing example, seems to move TCLib out of the proof of concept or testing stage, and into a realm where its use in mainstream coding projects could become a reality.   

zip next post

Frederick J. Harris

Here's the zip

James C. Fuller

Fred,
  Good stuff. I'll look at it in the AM.

James

Patrice Terrier

Fred

Once you are done with it, i have some good candidates to check your TCLib with VS Community 2015.

...
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

James C. Fuller

Fred,
  You have UNICODE defined in your Strings.cpp file. I did not think your class was for unicode use only?
Why did you name stricmp _stricmp. All others strcpy, strcat, strcmp are defined without the underscore?

I needed this one :

//=====================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                            By James C. Fuller March 2016
//
//                      cl strchr.cpp /c /W3 /DWIN32_LEAN_AND_MEAN
//=====================================================================================
#include <windows.h>
#include "string.h"

char * __cdecl _strchr (const char *s, int c)
{
    do {
        if (*s == c)
        {
            return (char*)s;
        }
    } while (*s++);
    return (0);
}

wchar_t * __cdecl _wcschr (const wchar_t *s, int c)
{
    do {
        if (*s == c)
        {
            return (wchar_t*)s;
        }
    } while (*s++);
    return (0);
}


I am set up at present to only use and compile 64bit unicode source with TCLib
All your demos except Demo18 I believe are in that format?
Can you do a Demo18 for unicode 64bit?

I needed to tweak a few items but first impressions are good.

James

Frederick J. Harris

Everything should work in either asci or wide, x64 or x86, VS 2008 or VS2015, although to be honest I'm starting to slack off a bit in my testing of x86.

Yes, my String Class is set up to work with either ascii or unicode.  To use unicode the main calling *.cpp files should have #define UNICODE in them, and it should also be defined in Strings.cpp.  If its undefined in both (or multiple) places then the ascii takes over.  The reason I frequently use this construct...


#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif


...is because the Visual Studio IDE predefines the wide character set, so if I didn't use the #ifndef, warnings would be generated on multiple definitions of the equate.  Of course, CodeBlocks doesn't predefine wide character, and I like my sourse to be able to be compiled in both without warnings, so that's where that comes from.  Neither GCC nor VC++ predefine wide character; its just an issue with the Visual Studio IDE.

I've been fighting the whole wide character thing with C/C++ since the beginning in the late 90s.  By fighting with it I mean trying to come up with the easiest way of dealing with it.  I think the end result of that for me is to leave Strings.cpp and Strings.h coded with the tchar macros, which of course are horribly ugly, but in my other source files just do the #defines of UNICODE and _UNICODE, and use the non-tchar forms, i.e., wcslen, wcscat, wcscpy, etc.  And of course preface string literals with L.

I've had philosophical discussions of this issue at www.cplusplus.com with other coders, and that's the way I like to handle it.  Let me pose for a moment some of the issues.  And let me bring up the Forger's Win32 Tutorial (a popular C++ destination for folks learning Win32).  All the code there is quite good, but when folks try to compile it with Visual Studio, they immediately get errors due to Visual Studio pre-defining the wide character set.  For example, CreateWindowEx is used in that tutorial to create windows, and there is no specification in the code as to the character set being used.  Therefore, it will compile fine with CodeBlocks using GCC, but not with Visual Studio due to the latter's propensity to specify the wide character set. 

Of course, to solve the problem one could strictly use the tchar macros for absolutely everything as Petzold's last Win32 book does (version 5 or 6; I forget which).  But they are just so horrible!  The personal solution I have come up with is to use the #ifndef setup I first described.  That allows my code to be correct no matter what Windows compiler is used. 

Frederick J. Harris

In terms of _stricmp, this is from my main C reference written by Peter Aitken and Bradley Jones...

Quote
String comparison functions that are non-case-sensitive operate in the same format as the case sensitive function strcmp.  Most compilers have their own non-case-sensitive comparison functions.  Zortech uses the library function strcmpl().  Microsoft uses a function called _stricmp().  Borland has two functions strcmpi() and stricmp().  You need to check your library reference manual to determine which function is appropriate for your compiler...


Frederick J. Harris

And this link...

https://msdn.microsoft.com/en-us/library/ms235365.aspx

...states that stricmp is deprecated, although to be honest I don't typically let that stop me from using something because I'm deprecated myself! ;D

Frederick J. Harris

Quote
You have UNICODE defined in your Strings.cpp file. I did not think your class was for unicode use only?

Just dawned on me what you are getting at.  Yes, that ought to be in the Strings.h file instead I guess, with the other #defines.  Somehow or other I got in the habit of putting it in the Strings.cpp file instead.   I suspect everything should work as expected if you transfer it to there.

Frederick J. Harris

#12
We'll make your strchr an official addition.  Thanks!  There's probably others that could be added too.

About that _stricmp, stricmp thing.  Had me confused for a bit too!  I can reconsider the naming if you want, but from the research I've done, it looks like _stricmp is the right one for Microsoft coding - even with GCC I'd think.  Not?

James C. Fuller

Quote from: Frederick J. Harris on April 03, 2016, 04:52:06 AM
Quote
You have UNICODE defined in your Strings.cpp file. I did not think your class was for unicode use only?

Just dawned on me what you are getting at.  Yes, that ought to be in the Strings.h file instead I guess, with the other #defines.  Somehow or other I got in the habit of putting it in the Strings.cpp file instead.   I suspect everything should work as expected if you transfer it to there.

I believe  it should be defined (or not ) in your app that includes String.cpp

James

James C. Fuller

Fred,
  re: stricmp, I agree . Some bc9 library routines used stricmp but I overlooked a #define stricmp _stricmp I had in my translations. 

After hacking a bit on the Strings.cpp file I am now able to create non-unicode apps.
I removed the UNICODE defines and added guards for the others
James


#ifndef _WINDOWS_
#include  <windows.h>
#endif

#ifndef string_h
#include  "string.h"
#endif

#ifndef stdio_h
#include  "stdio.h"
#endif

#ifndef Strings_h
#include  "Strings.h"
#endif

#ifndef tchar_h
#include  "tchar.h"
#endif