Friday, August 08, 2008  | 
 
Login
 


Forgot Password ?
     
 
List
 
     

Host for the Toronto Ontario FoxPro User's Group

Fox Ridge Software Inc. - The best in custom business software.
 
A plea for re-usable examples!
 
Location: BlogsFoxPro / Visual FoxPro tips, tricks and traps.    
Posted by: myearwood 12/27/2005

Revised January 20th, 2006

I like to see example code - and thankfully there's no shortage of it on the various FoxPro websites. I do wish that some of these examples were organized in such a way that I could use them immediately. As an example, this code from Craig Boyd demonstrates using the Windows QueryPerformanceCounter function as a better way to time code snippets. It's a great example and a valid and useful technique. Don't worry, despite the name, it's not another rumour about the possible demise of VFP. Execution time for VFP.

I can only re-use that code if I copy-and-paste it into various PRGs. What I'd like to do is drop that code into a single PRG and never touch it again - but still be able to re-use it.

If the code was packaged as a class or PRG, I could just drop it into some directory in my VFP search path and instantiate or call it.

I refactored Craig's sample to demonstrate - no offence Craig! First off, here's the calling code.

SET PROCEDURE TO StopWatch.PRG ADDITIVE

LOCAL m.loStopWatch, m.lcStartBuffer, m.lcEndBuffer, m.lcA, m.lnI

m.loStopWatch = CREATEOBJECT("tmrStopWatch")

 

#DEFINE kIterations 5000

m.lcStartBuffer = m.loStopWatch.Start()

FOR m.lnI = 1 TO kIterations

 m.lcA=TRANSFORM(m.lnI)

ENDFOR m.lnI

m.lcEndBuffer = m.loStopWatch.Stop()

? "Elapsed time in seconds TRANSFORM(): " ;

  + TRANSFORM(m.loStopWatch.Elapsed(m.lcStartBuffer,m.lcEndBuffer))

 

m.lcStartBuffer = m.loStopWatch.Start()

FOR m.lnI = 1 TO kIterations

  m.lcA=ALLTRIM(STR(m.lnI))

ENDFOR m.lnI

m.lcEndBuffer = m.loStopWatch.Stop()

? "Elapsed time in seconds ALLTRIM(STR()): " ;

  + TRANSFORM(m.loStopWatch.Elapsed(m.lcStartBuffer,m.lcEndBuffer))

 

As you can see that's almost as simple as using SECONDS(). StopWatch.PRG contains the tmrStopWatch class. Here's Craig's code repackaged as a class.

 

*

*  StopWatch.PRG

*  This .PRG contains the VMP base class definition

*  for the StopWatch class which exposes the Windows

*  QueryPerformanceCounter. This lets you check the

*  elapsed time for anything you need to test with

*  greater precision than SECONDS().

*

*  Thanks to Craig Boyd for the original code.

*  Thanks to Fabio Lunardon for suggested

*  refinements.

*  Thanks to Gene Pasquini for finding an incorrect

*  call to DWord2Num - which should have been

*  QWord2Num.

*

*  Author:  Mike Yearwood

*  FoxRidge Software Inc.

*  http://www.foxridgesoftware.com

*

**********************************************************

**********************************************************

#DEFINE kBuffer REPLICATE(CHR(0),8)

DEFINE CLASS tmrStopWatch AS TIMER

  PROTECTED inTicksPerSecond

  PROTECTED inOverhead

  PROTECTED icQueryPerformanceCounterName

  PROTECTED icQueryPerformanceFrequencyName

  inOverhead = 0

  inTicksPerSecond = 0

 

  HIDDEN PROCEDURE INIT

    THIS.LoadDLLS()

    THIS.SetFrequency()

    THIS.SetOverhead()

  ENDPROC

 

  HIDDEN PROCEDURE Destroy

    THIS.ReleaseDLLS()

  ENDPROC

 

  HIDDEN PROCEDURE LoadDLLS

    LOCAL m.lcRandom

    STORE SYS(2015) TO m.lcRandom

    STORE "QPC" + m.lcRandom TO THIS.icQueryPerformanceCounterName

    DECLARE INTEGER QueryPerformanceCounter ;

      IN kernel32 ;

      AS (THIS.icQueryPerformanceCounterName) ;

      STRING @lpFrequency

 

    STORE "QPF" + m.lcRandom TO THIS.icQueryPerformanceFrequencyName

    DECLARE INTEGER QueryPerformanceFrequency ;

      IN kernel32 ;

      AS (THIS.icQueryPerformanceFrequencyName) ;

      STRING @lpFrequency

  ENDPROC

 

  HIDDEN PROCEDURE ReleaseDLLS

    LOCAL m.lcDLLS

    m.lcDLLS = THIS.icQueryPerformanceCounterName ;

      + "," ;

      + THIS.icQueryPerformanceFrequencyName

    CLEAR DLLS &lcDLLs.

  ENDPROC

 

  HIDDEN PROCEDURE SetFrequency

    LOCAL m.lcCurrentBuffer

    STORE kBuffer TO m.lcCurrentBuffer

    EVALUATE(m.THIS.icQueryPerformanceFrequencyName+"(@m.lcCurrentBuffer)")

    THIS.inTicksPerSecond = QWord2Num(m.lcCurrentBuffer)

  ENDPROC

 

  HIDDEN PROCEDURE SetOverhead

    LOCAL m.lnOverhead, m.lnK

    STORE 0 TO m.lnOverhead

    FOR m.lnK = 0 TO 100

      STORE m.lnOverhead + THIS.Elapsed(THIS.START(),THIS.Stop()) TO m.lnOverhead

    NEXT

    THIS.inOverhead = m.lnOverhead / m.lnK

  ENDPROC

 

  HIDDEN PROCEDURE CurrentTime

    LOCAL m.lcCurrentBuffer

    STORE kBuffer TO m.lcCurrentBuffer

    EVALUATE(THIS.icQueryPerformanceCounterName+"(@m.lcCurrentBuffer)")

    *The state of the buffer must be

    *preserved so pass it back. This permits

    *multiple callers to use this same

    *StopWatch instance simultaneously.

    RETURN m.lcCurrentBuffer

  ENDPROC

 

  PROCEDURE Start

    RETURN THIS.CurrentTime()

  ENDPROC

 

  PROCEDURE Stop

    RETURN THIS.CurrentTime()

  ENDPROC

 

  PROCEDURE Elapsed

    LPARAMETERS m.tcStartBuffer, m.tcEndBuffer

    RETURN (QWord2Num(m.tcEndBuffer) ;

      - QWord2Num(m.tcStartBuffer)) ;

      / THIS.inTicksPerSecond ;

      - THIS.inOverhead

  ENDPROC

ENDDEFINE

 

There's a good reason to remember the initial value of m.lcStartBuffer. Suppose you have various processes running. Further, suppose they're all managed by a single master process. Because of the design of this class, each piece can check the elapsed time without interfering with the elapsed time of the other processes. That would not be possible if the class held the start time as a property. That start time property would be overwritten as each process started.

 

There are two other little .PRGs we need. Instead of putting BUF2NUM right in the cusStopWatch class, I strongly advise you make a separate BUF2NUM.PRG file. Just put the code from Craig's BUF2NUM function into that .PRG and VFP will run it like a function when called as above. That gives you the additional reuse of that .PRG and reduces the existence of multiple copies of that code in your projects. I changed the name of it from BUF2NUM to DWORD2NUM. The function is converting a DWORD to a number.

 

The QueryPerformanceCounter and QueryPerformanceFrequency functions both use 2 buffers, a pair of DWORDs. That's called a QWord (quad word). That means we should have a QWord2Num program.

 

*

*  QWord2Num.PRG

*  Converts an 8 byte/64 bit character representation

*  of a binary value to a numeric value

*

*  Copyright (c) 2005-2008 Fox Ridge Software

*                All Rights Reserved

*                120 Parsell Square

*                Scarborough, ON M1B 2A6

*                416-282-3942

*                http://www.foxridgesoftware.com

*  Author:  Mike Yearwood

*

*  Usage:

*    LOCAL lcBuffer

*    lcBuffer = CHR(65)+CHR(65)+CHR(65)+CHR(65)+CHR(65)+CHR(65)+CHR(65)+CHR(65)

*    ?QWord2Num(m.lcBuffer)

*

*  Parameters

*    tcBuffer (R) Buffer string holding the binary data.

*

LPARAMETERS m.tcBuffer

IF VARTYPE(m.tcBuffer) # "C" ;

     OR LEN(m.tcBuffer) < 8

  ERROR 11

ENDIF

LOCAL m.lnValue

m.lnValue = DWord2Num(SUBSTR(m.tcBuffer, 1,4)) ;

  + (DWord2Num(SUBSTR(m.tcBuffer, 5,4)) * 0x100000000)

RETURN m.lnValue

 

You'll notice this QWord2Num function calls the DWord2Num function:

 

*

*  DWORD2NUM.PRG

*  Converts a 4 byte character representation

*  of a binary value to a numeric value

*

*  Copyright (c) 2005-2008 Fox Ridge Software

*                All Rights Reserved

*                120 Parsell Square

*                Scarborough, ON M1B 2A6

*                416-282-3942

*                http://www.foxridgesoftware.com

*  Author:  Mike Yearwood

*

*  Usage:

*    LOCAL lcBuffer

*    lcBuffer = CHR(65)+CHR(65)+CHR(65)+CHR(65)

*    ?DWORD2NUM(m.lcBuffer)

*

*  Parameters

*    tcBuffer (R) Buffer string holding the binary data.

*

LPARAMETERS m.tcBuffer

IF VARTYPE(m.tcBuffer) # "C" ;

     OR LEN(m.tcBuffer) < 4

  ERROR 11

ENDIF

LOCAL m.lnValue

#IF LEFT(VERSION(4),2)="09"

  m.lnValue = CTOBIN(m.tcBuffer,"R") + 0x80000000

#ELSE

  m.lnValue = ASC(SUBSTR(m.tcBuffer, 1,1)) ;

        + ASC(SUBSTR(m.tcBuffer, 2,1)) * 0x100 ;

        + ASC(SUBSTR(m.tcBuffer, 3,1)) * 0x10000 ;

        + ASC(SUBSTR(m.tcBuffer, 4,1)) * 0x1000000

#ENDIF

RETURN m.lnValue

 

 

I'll make every effort to provide re-usable examples here.

 

December 29th, 2005

 

I just read about the StopWatch class in .Net 2.0. Seems it doesn't permit multiple clients to share one instance. http://www.codinghorror.com/blog/archives/000460.html

 

Permalink |  Trackback
     
Copyright 2005-2009 by Fox Ridge Software, Inc. Privacy StatementTerms Of Use