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