• Welcome to Jose's Read Only Forum 2023.
 

Speed Comparisons - x86 / x64 C++ verses PowerBASIC x86

Started by Frederick J. Harris, July 31, 2015, 07:48:09 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

I can present a pretty good and realistic speed test comparison between x86 PowerBASIC and C++ verses the same code running x64 with MSVC.  What I did was use that old string processing algorithm first presented by John Gleason as follows...

1) Create a 15 MB string of nulls
2) Change every 7th null to a "P"
3) Replace every "P" with a "PU" (hehehe)
4) Replace every null with an "8"
5) Put in a carriage return & line feed every 90 characters.

Only thing I did different was put this code in a procedure where it would be called a user specified number of times - say 50 or 100 times or whatever, and I used a random number generator function to pick numbers of bytes to process between 10 MB and 25 MB.  For the PowerBASIC code I could just use the good 'ole Rnd function, but for the C++ code, not really knowing what C++ coders use for that - there's likely something or other in the C++ Standard Library - but since I personally detest the C++ Standard Library - I fabricated something reasonable out of the C Standard Library's rand() function ...


size_t Rnd(int iMin, int iMax)       
{                                     
double dblRange,dblMaxFactor;       
double dblRandomNumber;             
                                     
dblRange=iMax-iMin;
dblMaxFactor=dblRange/RAND_MAX;             
dblRandomNumber=(double)rand();             
                                             
return iMin+dblMaxFactor*dblRandomNumber;   
}                                             


And I have essentially this same code to post now in PowerBASIC for PB Win 10 and C++.  The same C++ code should be able to be compiled as 32 bit or 64 bit without changes.  I used VC9 from Visual Studio 2008, which came with 32 bit and 64 bit compilers.

The other thing the code does is use a seperate worker thread to do the data processing, and the program - which is a GUI program, displays its output in real time in an output window which scrolls as it fills up.  Here is what an abbreviated output run looks like...

It Took 0.780 Seconds To Complete Processing Job # 1 Involving 48,744,803 bytes!
It Took 0.280 Seconds To Complete Processing Job # 2 Involving 18,068,456 bytes!
It Took 0.484 Seconds To Complete Processing Job # 3 Involving 30,780,639 bytes!
It Took 0.234 Seconds To Complete Processing Job # 4 Involving 15,289,626 bytes! 
....
....
....
It Took 0.655 Seconds To Complete Processing Job # 58 Involving 42,455,802 bytes!
It Took 0.187 Seconds To Complete Processing Job # 59 Involving 12,265,475 bytes!
It Took 0.452 Seconds To Complete Processing Job # 60 Involving 30,219,988 bytes!
     
Processing Complete.  1,970,051,511 Bytes Were Processed In 31.135 Seconds.

In terms of processing speeds and program sizes my PowerBASIC code results in an exe of 24 k.  My MSVC x64 versions are 74 K and 79 K.  I believe the differences there are that I compiled one from the command line which didn't include a manifest in the executable resources, while the other did with the VStudio IDE. 

The x86 C++ version was a bit smaller - about 67 K.  So PowerBASIC wins the size comparison hands down.  There's virtually no comparison.  I might add those are Release builds linked with MT, i.e., the runtime library calls linked into the exe to make them stand alone versions comparable to what PowerBASIC does.

In terms of processing speeds, that's something of a different story.  Here are a PowerBASIC run and an x86 VC++ run...


PowerBASIC Windows 10    1,269,925,803 bytes processed                20.284 seconds
Microsoft C++ - x86      1,256,637,357 bytes processed                19.653 seconds


That's only two runs and each run can be slightly different, but for all practical purposes you can consider they are essentially identical.  PowerBASIC is matching the vaunted MS C++ speedwise tick for tick.  But here's what the same C++ code compiled as x64 yields...


Microsoft C++ - x64      1,256,637,357 bytes processed                12.155 seconds


I'd consider that kind of significant.  It consistently runs in about 60% of the time as the 32 bit code.  One thing I believe C++ compilers are rather good at are optimizations.  Possibly this is to attempt to undo and unravel the grunge code so many C++ coders write where everything is wrapped and obfusicated as much as possible with classes.  I suspect the speed gains are attained by moving data in 8 byte chunks instead of smaller ones.  I could be wrong about that though.  In any case, its significantly faster. 

Most folks here have the PowerBASIC Win 10 compiler, and should be able to compile and test my PowerBASIC code if they like.  Note I used wide characters for all these tests so that might leave those with older compilers out.  Here is the PowerBASIC code.  Its all in one easily compilable source code file...


#if 0
T11.bas

Program shows how to produce incremental scrolling GUI output in real time to show results of lengthy
processing jobs where output is immediately visible as an algorithm proceeds to completion.  To
demonstrate this the program creates two windows of two seperate classes, which operate in a main
GUI thread, and a worker thread where the actual processing algorithm runs.  The main program window 
or start up form has two buttons on it.  The top button is "Process Data" and the bottom button is 
"Cancel".  These objects are created in fnMainWndProc_OnCreate().  When the top or "Process Data"
button is clicked, an output window of class "ProcessData" is instantiated.  That class is also
registered in fnMainWndProc_OnCreate().  In the same button click procedure where the "ProcessData"
instance is created, whose HWND is hProcessData, a worker thread is started and the thread function
for that is ProcessingThread().  In terms of the 'dummy' work that thread does to take up significant
time, I used an algorithm or task first presented by John Gleason as follows....

1) Create a 15 MB string of nulls
2) Change every 7th null to a "P"
3) Replace every "P" with a "PU" (hehehe)
4) Replace every null with an "8"
5) Put in a carriage return & line feed every 90 characters

DONE
   
Except, instead of creating a 15 MB string, I made that number into a variable returned from the
PowerBASIC RND function, where I set it up to return a number between 10 million and 50 million bytes,
i.e... 

iNumber=Rnd(10000000, 50000000)

And the worker thread runs through a loop processing about 60 or %LINE_BUFFER_COUNT of these lengthy
string processing requests.  Here is an abbreviated example of what a full output run displays in
the output window.... 

It Took 0.780 Seconds To Complete Processing Job # 1 Involving 48,744,803 bytes!
It Took 0.280 Seconds To Complete Processing Job # 2 Involving 18,068,456 bytes!
It Took 0.484 Seconds To Complete Processing Job # 3 Involving 30,780,639 bytes!
It Took 0.234 Seconds To Complete Processing Job # 4 Involving 15,289,626 bytes! 
....
....
....
It Took 0.655 Seconds To Complete Processing Job # 58 Involving 42,455,802 bytes!
It Took 0.187 Seconds To Complete Processing Job # 59 Involving 12,265,475 bytes!
It Took 0.452 Seconds To Complete Processing Job # 60 Involving 30,219,988 bytes!
     
Processing Complete.  1,970,051,511 Bytes Were Processed In 31.135 Seconds.

And I must emphasize that this output is produced as the program runs - not all at once when finished
processing 60 requests. 

Further note I used various logic and function calls to handle producing an hourglass wait/busy
cursor, turning it back to an arrow cursor when processing was finished, and for providing for the
eventuality that the user wishes to terminate processing before the job has run to completion.  This
latter logic kicks in if the user clicks the "Cancel" button on the main start up form before all
processing requests are complete. 

Compiles to 24,576 bytes with PB 10.03 in Release mode.  Works with PowerBASIC includes or Jose Roca
includes.
#endif

#Compile               Exe        "T11.exe"
#Dim                   All
%UNICODE               = 1
#If %Def(%UNICODE)
    Macro ZStr         = WStringz
    Macro BStr         = WString
    %SIZEOF_CHAR       = 2
#Else
    Macro ZStr         = Asciiz
    Macro BStr         = String
    %SIZEOF_CHAR       = 1
#EndIf
%LINE_BUFFER_COUNT     = 100
%IDC_BTN_PROCESS_DATA  = 1500
%IDC_BTN_CANCEL        = 1505
#Include               "Windows.inc"
'%Debug                = 1
#If %Def(%Debug)
    Global             fp As Long
#EndIf

Type WndEventArgs
  wParam               As Long
  lParam               As Long
  hWnd                 As Dword
  hInst                As Dword
End Type

Type MessageHandler
  wMessage             As Long
  dwFnPtr              As Dword
End Type

Declare Function FnPtr(Wea As WndEventArgs) As Long


Sub AttachMessageHandlers()
  Dim MainWindowMsgHdlr(3) As Global MessageHandler   'Associate Windows Message With Message Handlers
  Dim ProcessDataHdlr(5)   As Global MessageHandler

  MainWindowMsgHdlr(0).wMessage=%WM_CREATE    :   MainWindowMsgHdlr(0).dwFnPtr=CodePtr(fnMainWndProc_OnCreate)  'For Main Form
  MainWindowMsgHdlr(1).wMessage=%WM_COMMAND   :   MainWindowMsgHdlr(1).dwFnPtr=CodePtr(fnMainWndProc_OnCommand)
  MainWindowMsgHdlr(2).wMessage=%WM_SETCURSOR :   MainWindowMsgHdlr(2).dwFnPtr=CodePtr(fnMainWndProc_OnSetCursor)
  MainWindowMsgHdlr(3).wMessage=%WM_CLOSE     :   MainWindowMsgHdlr(3).dwFnPtr=CodePtr(fnMainWndProc_OnClose)

  ProcessDataHdlr(0).wMessage=%WM_CREATE      :   ProcessDataHdlr(0).dwFnPtr=CodePtr(frmProcessData_OnCreate)   'For Process Data
  ProcessDataHdlr(1).wMessage=%WM_PAINT       :   ProcessDataHdlr(1).dwFnPtr=CodePtr(frmProcessData_OnPaint)
  ProcessDataHdlr(2).wMessage=%WM_SIZE        :   ProcessDataHdlr(2).dwFnPtr=CodePtr(frmProcessData_OnSize)
  ProcessDataHdlr(3).wMessage=%WM_VSCROLL     :   ProcessDataHdlr(3).dwFnPtr=CodePtr(frmProcessData_OnVScroll)
  ProcessDataHdlr(4).wMessage=%WM_SETCURSOR   :   ProcessDataHdlr(4).dwFnPtr=CodePtr(frmProcessData_OnSetCursor)
  ProcessDataHdlr(5).wMessage=%WM_CLOSE       :   ProcessDataHdlr(5).dwFnPtr=CodePtr(frmProcessData_OnClose)
End Sub


Sub ErrorMemFree(Byval pLns As Dword Ptr, Byval iNum As Dword)
  Register i As Long        'If there are any memory allocation errors
                            'anywhere along the way, this little thingy
  For i=0 To iNum           'unravels all the allocations done up to the
    If @pLns[i] Then        'point where the 1st allocation error occurred.
       GlobalFree(@pLns[i])
       @pLns[i]=0
    End If
  Next i
  GlobalFree(pLns)
End Sub


Sub Prnt(Byval hWnd As Dword, Byval ptrLines As Dword Ptr, Byref iLine As Long, Byval pszStr As ZStr Ptr)
  Local iLen,iRequiredBytes As Long

  If iLine < %LINE_BUFFER_COUNT Then                                                ' This code here is what causes the output screen to display
     If pszStr Then                                                                 ' text as processing occurs and to scroll.  The way it works
        iLen=Len(@pszStr)                                                           ' is as follows.  When the user clicks the button to process
        iRequiredBytes = iLen * %SIZEOF_CHAR + %SIZEOF_CHAR                         ' a timber sale in TimberBeast.bas, a CreateWindowEx() call is
        @ptrLines[iLine] = GlobalAlloc(%GPTR, iLen * %SIZEOF_CHAR + %SIZEOF_CHAR)   ' made in the procedure DoSaleProcessing() to create a window
        If @ptrLines[iLine] Then                                                    ' of the "Process" Class.  That will of course trigger the
           CopyMemory(@ptrLines[iLine], pszStr, iRequiredBytes)                     ' constructor function for the "Process" Class, which is
           If iLine>=GetWindowLong(hWnd,12) Then                                    ' frmProcess_OnCreate().  That brings us into this code module.
              Call InvalidateRect(hWnd, Byval 0, %FALSE)                            ' In frmProcess_OnCreate() there is a GlobalAlloc() call that
              Call SendMessage(hWnd,%WM_VSCROLL,MakDwd(%SB_LINEDOWN,0),0)           ' creates a %LINE_BUFFER_COUNT*4 byte buffer, and a pointer to
           Else                                                                     ' that buffer is stored at WNDCLASSEX::cbWndExtra bytes at
              Call InvalidateRect(hWnd,Byval 0,%FALSE)                              ' offset 4.  That buffer will hold pointers to each line of
           End If                                                                   ' text.  The last parameter of this function is the address of
           Incr iLine                                                               ' the new string to store/display.  This function allocates a
        Else                                                                        ' buffer for that string, and copies it to the pointer to that
           MessageBox(hWnd, "Memory Allocation Error!","Not Good!", %MB_ICONERROR)  ' buffer just allocated.  Then it stores the pointer in the
           Call ErrorMemFree(ptrLines,iLine)                                        ' pointer to pointers buffer which is the one allocated in
           Exit Sub                                                                 ' frmProcess_OnCreate().  The address of the pointer to a
        End If                                                                      ' pointer buffer is passed into here as ptrLines.  The iLine
     Else                                                                           ' parameter of this function indicates the line number where
         If iLine>=GetWindowLong(hWnd,12) Then                                      ' the text pszStr is to be stored.  When all this is done an
            Call SendMessage(hWnd,%WM_VSCROLL,MakDwd(%SB_LINEDOWN,0),0)             ' InvalidateRect() call forces a WM_PAINT which causes the
         End If                                                                     ' new text to become visible.  If the iLine number is beyond
         Incr iLine                                                                 ' the size of the screen, a WM_VSCROLL message is sent to
     End If                                                                         ' scroll the screen.  Got all that?  This is all running in
     'Sleep(500)                                                                    ' its own worker thread.  You can have fun with the Sleep()
  End If                                                                            ' call at left.  If you make the Sleep call long enough, it will
End Sub                                                                             ' cause the program to run so slow you'll be able to read each
                                                                                    ' line as it is processed.

Sub DoProcessing(Byval iNumber As Long, Byref iTickCount As Long)
  Local iNumFullLines,iPUExtLength,iLineLength,iRightBlock,iMaxMem,iNumPs,iCtr As Long
  Local pZStr As ZStr Ptr
  Local s1,s2 As Word Ptr
  Local t1 As Dword
  Register i As Long
  Register j As Long

  iLineLength   = 90
  iRightBlock   = 4000
  iNumPs        = iNumber / 7 + 1
  iPUExtLength  = iNumber + iNumPs
  iNumFullLines = iPUExtLength / iLineLength
  iMaxMem       = iPUExtLength + iNumFullLines * 2 + 1
  t1            = GetTickCount()
  s1            = GlobalAlloc(%GPTR, iMaxMem * %SIZEOF_CHAR)
  If s1 Then
     s2=GlobalAlloc(%GPTR, iMaxMem * %SIZEOF_CHAR)
     If s2 Then                                            ' 1)  Create a iMaxMem Unicode Dashes;
        For i=0 To iNumber-1
          @s1[i]=&H002D
        Next i
        For i = 0 To iNumber-1
          Incr j
          If j = 7 Then
             j=0
             @s1[i]=80                                     ' 2)  Change every 7th dash to a "P";
          End If
        Next i
        j=0
        For i=0 To iNumber-1
          If @s1[i]=80 Then
             @s2[j]=80 : Incr j                            ' 3)  Replace every "P" with a "PU" (hehehe);
             @s2[j]=85 : Incr j
          Else
             @s2[j]=@s1[i] : Incr j
          End If
        Next i
        For i=0 To iPUExtLength
          If @s2[i]=45 Then                                ' 4)  Replace every dash with an "8";
             @s2[i]=56
          End If
        Next i
        j=0
        For i=0 To iPUExtLength-1
          @s1[j]=@s2[i]
          Incr j : Incr iCtr
          If iCtr=iLineLength Then
             @s1[j]=13 : Incr j                            ' 5) Put in a CrLf (Carriage Return / Line Feed
             @s1[j]=10 : Incr j                            '    Every 90 Characters To Break It Into Lines;
             iCtr=0
          End If
        Next i                                             ' 6) Output last 4K to Message Box.  Point an ZStr Ptr at it,
        @s1[j]=0                                           '    stick a NULL MAX_MEM bytes out, and call it a String!
        pZStr=Varptr(@s1[j])-iRightBlock * %SIZEOF_CHAR    ' NOTE - here i didn't do the MsgBox() thingie.
        iTickCount=GetTickCount()-t1
        GlobalFree(s2)
     End If
     GlobalFree(s1)
  End If
End Sub


Function ProcessingThread(Byval hProcessData As Dword) As Dword
  Local iNumber,iTickCount,iLine,iLineCount As Long
  Local pPtrBuffer,dwTotalBytes As Dword
  Local dblSeconds,dblTotal As Double
  Local strProcessingJob As BStr
  Register i As Long

  #If %Def(%Debug)
  Print #fp, "Entering ProcessingThread()"
  #EndIf
  iLineCount=GetWindowLong(hProcessData,0)
  pPtrBuffer=GetWindowLong(hProcessData,4)
  iLineCount=iLineCount-2
  #If %Def(%Debug)
  Print #fp, "  hProcessData = " hProcessData
  Print #fp, "  iLineCount   = " iLineCount
  Print #fp, "  pPtrBuffer   = " pPtrBuffer
  #EndIf
  For i=1 To iLineCount
    If GetWindowLong(hProcessData,20) Then
       iNumber=Rnd(1000000, 25000000)
       dwTotalBytes=dwTotalBytes+iNumber
       Call DoProcessing(iNumber,iTickCount)
       dblSeconds=iTickCount/1000
       dblTotal=dblTotal+dblSeconds
       strProcessingJob="It Took " & Format$(dblSeconds,"0.000") & _
       " Seconds To Complete Processing Job #" & Str$(i) & " Involving " & _
       Format$(iNumber,"###,###,###,###") & " bytes!"
       Prnt(hProcessData,pPtrBuffer,iLine,StrPtr(strProcessingJob))
    Else
       Function=%FALSE : Exit Function
    End If
  Next i
  SetWindowLong(hProcessData,20,%FALSE)
  strProcessingJob="" 
  Prnt(hProcessData,pPtrBuffer,iLine,StrPtr(strProcessingJob))
  strProcessingJob="Processing Complete.  " & Format$(dwTotalBytes,"###,###,###,###") & _
  " Bytes Were Processed In " & Format$(dblTotal,"##.###") & " Seconds."
  Prnt(hProcessData,pPtrBuffer,iLine,StrPtr(strProcessingJob))
  #If %Def(%Debug)
  Print #fp, "Leaving ProcessingThread()"
  Print #fp,
  #EndIf

  Function=%TRUE
End Function


' Offset    What's Stored There
' ================================
' 0  -  3   iLineCount
' 4  -  7   ptrPtrBuffer
' 8  -  11  cyChar
' 12 -  15  iLinesVisible
' 16 -  19  hMain
' 20 -  23  Boolean To Continue Processing

Function frmProcessData_OnCreate(Wea As WndEventArgs) As Long
  Local pCreateStruct As CREATESTRUCT Ptr
  Local hFont,hTmp,hDC,hMain As Dword
  Local ptrLines As Dword Ptr
  Local tm As TEXTMETRIC
  Local iReturn As Long

  #If %Def(%Debug)
  Print #fp, "  Entering frmProcessData_OnCreate()"
  Print #fp, "    hProcessData       = " Wea.hWnd
  #EndIf
  MousePtr 11
  hDC=GetDC(Wea.hWnd)
  hFont=CreateFont(16,0,0,0,%FW_BOLD,0,0,0,0,0,0,2,0,"Courier New")
  hTmp=SelectObject(hDC,hFont)
  Call GetTextMetrics(hDC,tm)
  Call SetWindowLong(Wea.hWnd,8,tm.tmHeight)
  Call DeleteObject(SelectObject(hDC,hTmp))
  Call ReleaseDC(Wea.hWnd,hDC)
  pCreateStruct=Wea.lParam
  hMain=@pCreateStruct.lpCreateParams
  SetWindowLong(Wea.hWnd,16,hMain)
  ptrLines=GlobalAlloc(%GPTR,%LINE_BUFFER_COUNT*4)
  If ptrLines Then
     Call SetWindowLong(Wea.hWnd,0,%LINE_BUFFER_COUNT)
     Call SetWindowLong(Wea.hWnd,4,ptrLines)
     #If %Def(%Debug)
     Print #fp, "    %LINE_BUFFER_COUNT = " %LINE_BUFFER_COUNT
     Print #fp, "    ptrLines           = " ptrLines
     #EndIf
  Else
     iReturn=MsgBox("Couldn't Allocate Memory!",%MB_ICONERROR,"Must Abort!")
     frmProcessData_OnCreate=-1
  End If
  #If %Def(%Debug)
  Print #fp, "    hMain              = " hMain
  Print #fp, "  Leaving frmProcessData_OnCreate()" : Print #fp,
  #EndIf

  frmProcessData_OnCreate=0
End Function


Function frmProcessData_OnSize(Wea As WndEventArgs) As Long
  Local iLinesVisible,iLineCount,cyChar As Long
  Local ScrInf As SCROLLINFO

  '#If %Def(%Debug)
  '    Print #fp, "  Entering frmProcessData_OnSize()"
  '#EndIf
  iLineCount     = GetWindowLong(Wea.hWnd,0)
  cyChar         = GetWindowLong(Wea.hWnd,8)
  iLinesVisible  = Fix(HIWRD(Wea.lParam)/cyChar)
  Call SetWindowLong(Wea.hWnd,12,iLinesVisible)
  ScrInf.cbSize  = Sizeof(SCROLLINFO)
  ScrInf.fMask   = %SIF_RANGE Or %SIF_PAGE
  ScrInf.nMin    = 0
  ScrInf.nMax    = iLineCount-1
  ScrInf.nPage   = iLinesVisible
  If ScrInf.nMax < 0 Then
     ScrInf.nMax=0
  End If
  Call SetScrollInfo(Wea.hWnd, %SB_VERT, ScrInf, %TRUE)
  If iLinesVisible <= iLineCount Then
     Call InvalidateRect(Wea.hWnd, Byval %NULL, %TRUE)
  End If
  '#If %Def(%Debug)
  '    Print #fp, "    iLinesVisible = " iLinesVisible
  '    Print #fp, "  Leaving frmProcessData_OnSize()"
  '    Print #fp,
  '#EndIf

  frmProcessData_OnSize=0
End Function


Function frmProcessData_OnVScroll(Wea As WndEventArgs) As Long
  Local iVScrollPos As Long
  Local ScrInf As SCROLLINFO

  ScrInf.cbSize=SizeOf(SCROLLINFO)
  ScrInf.fMask=%SIF_ALL
  Call GetScrollInfo(Wea.hWnd, %SB_VERT, ScrInf)
  iVScrollPos=ScrInf.nPos
  Select Case As Long Lowrd(Wea.wParam)
    Case %SB_LINEUP
      If ScrInf.nPos>ScrInf.nMin Then
         Decr ScrInf.nPos
      End If
    Case %SB_PAGEUP
      ScrInf.nPos=ScrInf.nPos-ScrInf.nPage
    Case %SB_LINEDOWN
      If ScrInf.nPos<ScrInf.nMax Then
         Incr ScrInf.nPos
      End If
    Case %SB_PAGEDOWN
      ScrInf.nPos=ScrInf.nPos+ScrInf.nPage
    Case %SB_THUMBTRACK
      ScrInf.nPos=ScrInf.nTrackPos
  End Select
  ScrInf.fMask=%SIF_POS
  Call SetScrollInfo(Wea.hWnd, %SB_VERT, ScrInf, %TRUE)
  Call GetScrollInfo(Wea.hWnd, %SB_VERT, ScrInf)
  If ScrInf.nPos <> iVScrollPos Then
     Call ScrollWindow(Wea.hWnd, 0, GetWindowLong(Wea.hWnd,8) * (iVScrollPos - ScrInf.nPos), Byval %NULL, Byval %NULL)
  End If

  frmProcessData_OnVScroll=0
End Function


Function frmProcessData_OnPaint(Wea As WndEventArgs) As Long
  Local hDC,iLineCount,iStart,iFinish,iLine,iPos,hFont,hTmp As Dword
  Local ptrBuffer As Dword Ptr
  Local ScrInf As SCROLLINFO
  Local pszStr As ZStr Ptr
  Local ps As PAINTSTRUCT
  Local cyChar As Long
  Register i As Long

  '#If %Def(%Debug)
  '    Print #fp, "Entering frmProcessData_OnPaint()"
  '#EndIf
  hDC=BeginPaint(Wea.hWnd,ps)
  iLineCount=GetWindowLong(Wea.hWnd,0)
  ptrBuffer=GetWindowLong(Wea.hWnd,4)
  cyChar=GetWindowLong(Wea.hWnd,8)
  ScrInf.cbSize = SizeOf(SCROLLINFO)
  ScrInf.fMask  = %SIF_POS
  Call GetScrollInfo(Wea.hWnd, %SB_VERT, ScrInf)
  iPos=ScrInf.nPos
  iStart=Fix(ps.rcPaint.nTop/cyChar)
  iFinish=ps.rcPaint.nBottom/cyChar
  hFont=CreateFont(16,0,0,0,%FW_BOLD,0,0,0,0,0,0,2,0,"Courier New")
  hTmp=SelectObject(hDC,hFont)
  For i=iStart To iFinish
    iLine=iPos+i
    If @ptrBuffer[iLine] Then
       If iLine<iLineCount Then
          pszStr=@ptrBuffer[iLine]
          Call TextOut(hDC,0,i*cyChar,Byval @ptrBuffer[iLine],Len(@pszStr))
       End If
    End If
  Next i
  Call DeleteObject(SelectObject(hDC,hTmp))
  Call EndPaint(Wea.hWnd,ps)
  '#If %Def(%Debug)
  '    Print #fp, "Leaving frmProcessSale_OnPaint()"
  '#EndIf

  frmProcessData_OnPaint=0
End Function


Function frmProcessData_OnSetCursor(Wea As WndEventArgs) As Long
  If GetWindowLong(Wea.hWnd,20) Then
     SetCursor(LoadCursor(Byval %NULL,Byval %IDC_WAIT))
  Else
     DefWindowProc(Wea.hWnd, %WM_SETCURSOR, Wea.wParam, Wea.lParam)
  End If

  frmProcessData_OnSetCursor=0
End Function


Function frmProcessData_OnClose(Wea As WndEventArgs) As Long
  Local iLineCount,blnFree,fp1,iCtr As Long
  Local ptrPtrBuffer As Dword Ptr
  Local hMain,hBtn As Dword
  Local pszStr As ZStr Ptr
  Local strOutput As BStr
  Register i As Long

  #If %Def(%Debug)
      Print #fp, "Entering frmProcessData_OnClose()"
  #EndIf
  iLineCount=GetWindowLong(Wea.hWnd,0)
  ptrPtrBuffer=GetWindowLong(Wea.hWnd,4)
  hMain=GetWindowLong(Wea.hWnd,16)                          ' HWND of main start up form stored at WNDCLASSEX::cbWndExtra bytes @ 16
  hBtn=GetDlgItem(hMain,%IDC_BTN_PROCESS_DATA)              ' We'll want to enable that disabled button
  EnableWindow(hBtn,%TRUE)                                  ' Enable it
  SetWindowLong(hMain,0,%FALSE)                             ' Since this window is being destroyed, put zero at offset o back in main
  SetWindowLong(Wea.hWnd,20,%FALSE)                         ' This will halt the processing early and turn the cursor back to an arrow
  #If %Def(%Debug)
      Print #fp, "  iLineCount            = " iLineCount
      Print #fp, "  ptrPtrBuffer          = " ptrPtrBuffer
      Print #fp,
      For i=0 To iLineCount-1
        If @ptrPtrBuffer[i] Then
           pszStr=@ptrPtrBuffer[i]
           Print #fp, " " i, @ptrPtrBuffer[i], @pszStr
        Else
           Print #fp, " " i, "NULL"
        End If
      Next i
      Print #fp,
  #EndIf
  For i=0 To iLineCount-1
    If @ptrPtrBuffer[i] Then
       #If %Def(%Debug)
           Print #fp, " " i, GlobalFree(@ptrPtrBuffer[i])
       #Else
           GlobalFree(@ptrPtrBuffer[i])
       #EndIf
    Else
       #If %Def(%Debug)
           Print #fp, " " i, "Not Allocated!"
       #EndIf
    End If
  Next i
  #If %Def(%Debug)
      Print #fp,
  #EndIf
  If ptrPtrBuffer Then                                       ' Let's try not to leak any memory!
     blnFree=GlobalFree(ptrPtrBuffer)
     ptrPtrBuffer=0
     #If %Def(%Debug)
         Print #fp, "  blnFree(ptrPtrBuffer) = " blnFree
     #EndIf
  End If
  Call DestroyWindow(Wea.hWnd)
  #If %Def(%Debug)
      Print #fp, "Leaving frmProcessData_OnClose()" : Print #fp,
  #EndIf

  frmProcessData_OnClose=0
End Function


Function fnProcessData_WndProc(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  Local Wea As WndEventArgs
  Register iReturn As Long
  Register i As Long

  For i=0 To UBound(ProcessDataHdlr)
    If wMsg=ProcessDataHdlr(i).wMessage Then
       Wea.hWnd=hWnd: Wea.wParam=wParam: Wea.lParam=lParam
       Call Dword ProcessDataHdlr(i).dwFnPtr Using FnPtr(Wea) To iReturn
       fnProcessData_WndProc=iReturn : Exit Function
    End If
  Next i

  fnProcessData_WndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
End Function


Function fnMainWndProc_OnCreate(Wea As WndEventArgs) As Long   ' This is the 'Constructor' code for the "Main" Window Class.
  Local pCreateStruct As CREATESTRUCT Ptr                      ' It Registers the "ProcessData" Class and creates the two
  Local szClassName As ZStr*16                                 ' buttons on the main start up form.
  Local wc As WNDCLASSEX
  Local hCtl As Dword

  #If %Def(%Debug)
  fp=Freefile : Open "Output.txt" For Output As #fp
  Print #fp, "Entering fnMainWndProc_OnCreate()"
  Print #fp, "  Wea.hWnd = " Wea.hWnd
  #EndIf
  pCreateStruct=Wea.lParam  : Wea.hInst=@pCreateStruct.hInstance
  szClassName      = "ProcessData"
  wc.lpszClassName = VarPtr(szClassName)
  wc.lpfnWndProc   = CodePtr(fnProcessData_WndProc)
  wc.hInstance     = Wea.hInst
  wc.cbSize        = SizeOf(wc)
  wc.hbrBackground = GetStockObject(%WHITE_BRUSH)
  wc.cbWndExtra    = 24
  wc.hCursor       = LoadCursor(%NULL, ByVal %IDC_ARROW)
  Call RegisterClassEx(wc) 'Register Process Class
  hCtl=CreateWindow("button","Process Data",%WS_Child Or %WS_Visible,75,20,125,30,Wea.hWnd,%IDC_BTN_PROCESS_DATA,Wea.hInst,ByVal 0)
  hCtl=CreateWindow("button","Cancel",%WS_Child Or %WS_Visible,75,70,125,30,Wea.hWnd,%IDC_BTN_CANCEL,Wea.hInst,ByVal 0)
  #If %Def(%Debug)
  Print #fp, "Leaving fnMainWndProc_OnCreate()"
  Print #fp,
  #EndIf

  fnMainWndProc_OnCreate=0
End Function


Function fnMainWndProc_OnCommand(Wea As WndEventArgs) As Long     ' This procedure handles button clicks on the
  Local hProcessData,hThread,lpThreadId As Dword                  ' two buttons of the main start up form.  The
  Local szClassName As ZStr*16                                    ' top button is to start the worker thread and
                                                                  ' lengthy string processing, and the bottom button
  Select Case As Long Lowrd(Wea.wParam)                           ' is to prematurely halt that processing before
    Case %IDC_BTN_PROCESS_DATA                                    ' it runs to completion.
      If Hiwrd(Wea.wParam)=%BN_CLICKED Then
         #If %Def(%Debug)
         Print #fp, "Entering fnMainWndProc_OnCommand() -- Case  IDC_BTN_PROCESS_DATA  >> BN_CLICKED"
         #EndIf
         szClassName="ProcessData"
         hProcessData=CreateWindowEx(0,szClassName,szClassName,%WS_OVERLAPPEDWINDOW,450,100,625,300,0,0,GetModuleHandle(Byval %NULL),Byval Wea.hWnd)
         If hProcessData Then                             ' In the ProcessingThread() function, offset 20 in .cbWndExtra bytes is tested for 0 or 1
            Call ShowWindow(hProcessData, %SW_SHOWNORMAL) ' to see if thread is still running
            SetWindowLong(hProcessData,20,%TRUE)          ' Store %TRUE at offset 20 of window of "ProcessData" class to indicate that the procedure is stil running
            SetWindowLong(Wea.hWnd,0,hProcessData)        ' Store HWND of "ProcessData" class at offset zero in main wnd WNDCLASSEX::cbWndExtra bytes
            EnableWindow(Wea.lParam,%FALSE)               ' Disable 'Process Data' button (HWND in LPARAM) because we've already had it clicked to start processing.
            hThread=CreateThread(Byval %NULL, 0, CodePtr(ProcessingThread), hProcessData, 0, lpThreadId)
         End If
         #If %Def(%Debug)
         Print #fp, "  hProcessData = " hProcessData
         Print #fp, "Leaving fnMainWndProc_OnCommand() -- IDC_BTN_PROCESS_DATA  >> BN_CLICKED"
         Print #fp,
         #EndIf
      End If
    Case %IDC_BTN_CANCEL                                  ' If the 'Cancel' button is clicked while the ProcessingThread() function is running, a %FALSE
      szClassName="ProcessData"                           ' value is set at offset 20 of the WNDCLASSEX::cbWndExtra bytes of the "ProcessData" class
      hProcessData=FindWindow(szClassName,szClassName)    ' window, once FindWindow() locates one.  Also, offset zero in the main window's .cbWndExtra
      If hProcessData Then                                ' bytes is used just below in fnMainWindowProc_OnSetCursor() to determine whether to put up
         SetWindowLong(hProcessData,20,%FALSE)            ' the 'wait' hourglass cursor.  That needs to be turned off.
         SetWindowLong(Wea.hWnd,0,%FALSE)
      End If
  End Select

  fnMainWndProc_OnCommand=0
End Function


Function fnMainWndProc_OnSetCursor(Wea As WndEventArgs) As Long      ' When data processing is occurring due to the presence of an active
  If GetWindowLong(Wea.hWnd,0) Then                                  ' window of "ProcessData" class, the HWND of this latter window is
     SetCursor(LoadCursor(Byval %NULL,Byval %IDC_WAIT))              ' stored at offset zero of the WNDCLASSEX::cbWndExtra bytes of the
  Else                                                               ' main window's instance.  This procedure tests that to determine
     DefWindowProc(Wea.hWnd, %WM_SETCURSOR, Wea.wParam, Wea.lParam)  ' if there is a need to produce a wait hourglass cursor or the
     SetCursor(LoadCursor(Byval %NULL, Byval %IDC_ARROW))            ' standard non-busy arrow cursor, which will be forthcomming if
  End If                                                             ' DefWindowProc() is called.  DefWindowProc() displays the cursor
                                                                     ' stored in the registered class data.
  fnMainWndProc_OnSetCursor=0
End Function



Function fnMainWndProc_OnClose(Wea As WndEventArgs) As Long
  #If %Def(%Debug)
  Print #fp, "Entering fnMainWndProc_OnClose()"
  Print #fp, "  Wea.hWnd = " Wea.hWnd
  #EndIf
  Call PostQuitMessage(0)
  Call SendMessage(Wea.hWnd,%WM_DESTROY,0,0)
  #If %Def(%Debug)
  Print #fp, "Leaving fnMainWndProc_OnClose()"
  Close #fp
  #EndIf

  fnMainWndProc_OnClose=0
End Function


Function fnMainWndProc(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  Local Wea As WndEventArgs
  Register iReturn As Long
  Register i As Long

  For i=0 To UBound(MainWindowMsgHdlr)
    If wMsg=MainWindowMsgHdlr(i).wMessage Then
       Wea.hWnd=hWnd: Wea.wParam=wParam: Wea.lParam=lParam
       Call Dword MainWindowMsgHdlr(i).dwFnPtr Using FnPtr(Wea) To iReturn
       fnMainWndProc=iReturn : Exit Function
    End If
  Next i

  fnMainWndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
End Function


Function WinMain(ByVal hIns As Long, ByVal hPrev As Long, ByVal lpCL As ZStr Ptr, ByVal iShow As Long) As Long
  Local szAppName As ZStr*18
  Local wc As WndClassEx
  Local Msg As tagMsg
  Local hWnd As Dword

  szAppName="Main"
  Call AttachMessageHandlers()
  wc.lpszClassName=VarPtr(szAppName)               : wc.lpfnWndProc=CodePtr(fnMainWndProc)
  wc.cbSize=SizeOf(wc)                             : wc.style=0
  wc.cbClsExtra=0                                  : wc.cbWndExtra=4
  wc.hInstance=hIns                                : wc.hIcon=LoadIcon(%NULL, ByVal %IDI_APPLICATION)
  wc.hCursor=LoadCursor(%NULL, ByVal %IDC_ARROW)   : wc.hbrBackground=%COLOR_BTNFACE+1
  wc.lpszMenuName=%NULL                            : wc.hIconSm=LoadIcon(hIns, ByVal %IDI_APPLICATION)
  Call RegisterClassEx(wc)
  hWnd=CreateWindowEx(0,szAppName,szAppName,%WS_OverlappedWindow Xor %WS_MaximizeBox,150,500,280,160,0,0,hIns,ByVal 0)
  Call ShowWindow(hWnd,iShow)
  While GetMessage(Msg,%NULL,0,0)
    Call TranslateMessage(Msg)
    Call DispatchMessage(Msg)
  Wend

  Function=msg.wParam
End Function


Frederick J. Harris

Now I'll post the C++ code.  Should be able to compile with x86 or x64 compiler....


//Main.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     100                        // 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

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;
};
#endif



// Main.cpp
// cl Main.cpp kernel32.lib user32.lib gdi32.lib /O1 /Os /MT /GA /FeT12_x64.exe
#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 <cstdio>                     // outputs.  The output window of class "Process" is registered
#include "Main.h"                     // in fnMain_OnCreate(), which is the constructor function, so to...
                                 

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

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 100, so DoProcessing() would
size_t iLen=0;                               // get called 98 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(1000000, 25000000);
           j++;                               //
           szBuf2[j]=L',';                    // ... generating 98 random numbers between 1 and 25 million,
        }                                     // 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;
        swprintf(szSeconds,L"%5.3f",dblSeconds);
        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 ");
swprintf(szSeconds,L"%6.3f",dblTotal);
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,800,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 WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR 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;
}