Mr. Cluey
: Classes : Handle ClassesI hate HANDLEs. Specifically, I hate working with them.
Manipulating objects with HANDLEs is like driving a car using the vehicle identification number. To change the gears, you would use a command like Shift( vin ). It works, but it is too flexible... you aren't likely to ever shift somebody else's car into a new gear.
The purpose of a handle class is to hide the HANDLE from the programmer. The handle by itself is no use to him - he can't create one, or change it in any way. The only thing he can do with it is pass it, so why make him keep track of it? Give him access to the thing that he really wants, as an object, and do the dirty work for him.
The best reason to hide handles from programmers is that the one thing that he can do with it is screw it up. All HANDLEs are really of the same type, and are interchangeable, as far as the functions are concerned. For instance, it is possible to pass and HFILE to a method that expects an HTIMER. I suspect nothing good will come of it - and if you are lucky, you are lucky your script will throw an exception at once. If you are unlucky....
The basic template of a handle class looks like this
winclass CHandle
{
HANDLE hMyHandle ;
//Add methods here...
}
That's it. Of course, you would never use this basic template for anything. Instead, you derive other classes from it.
I would happily wager that the most frequently used HANDLE in 4Test is HFILE, the handle to a file. There are six File functions in 4Test, all of which can easily be wrapped into the methods of a class.
type CFILE is window ;
winclass CFile : CHandle
{
HFILE hMyHandle ;
Open( string sPath, FILEMODE mode )
{
hMyHandle = FileOpen( sPath, mode ) ;
}
Close ()
{
FileClose( hMyHandle ) ;
}
BOOLEAN ReadLine( out string sLine )
{
return ( FileReadLine( hMyHandle, sLine ) ) ;
}
BOOLEAN ReadValue( out anytype aValue )
{
return ( FileReadValue( hMyHandle, aValue ) ) ;
}
WriteLine( string sLine )
{
FileWriteLine( hMyHandle, sLine ) ;
}
WriteValue( anytype aValue )
{
FileWriteValue( hMyHandle, aValue ) ;
}
}
Notice that the handle itself never escapes from the class - you don't ever have to worry that you'll confuse two handles, because you don't ever touch them. Consider this example
CFILE MyDoc = CFile () ; CFILE MyTxt = CFile () ; MyDoc.Open( "Cluey.doc", FM_WRITE ) ; MyText.Open( "TheText.txt", FM_WRITE ) ; MyDoc.Write( "This line goes in MyDoc" ) ; MyTxt.Write( "This line goes in MyTxt" ) ; MyDoc.Close () ; MyTxt.Close () ;
The CTimer class, in its simplest form, looks much like the CFile class does
type CTIMER is window ;
winclass CTimer
{
HTIMER hMyHandle ;
Create ()
{
hMyHandle = TimerCreate () ;
}
Destroy ()
{
TimerDestroy( hMyHandle ) ;
}
Pause ()
{
TimerPause( hMyHandle ) ;
}
Resume ()
{
TimerResume( hMyHandle ) ;
}
Start ()
{
TimerStart( hMyHandle ) ;
}
TIMERSTATE State ()
{
return ( TimerState ( hMyHandle ) ) ;
}
Stop ()
{
TimerStop( hMyHandle ) ;
}
string Str ()
{
return ( TimerStr(hMyHandle ) ) ;
}
real Value ()
{
return ( TimerValue( hMyHandle ) ) ;
}
}
Thus, a function that used the CTimer class would have code in it that looked much like this:
CTIMER MyTimer = CTimer () ; MyTimer.Create() ; MyTimer.Start () ; print ( MyTimer.State () ) ; MyTimer.Stop () ; print (MyTimer.Value ()) ; MyTimer.Destroy () ;
We've managed to hide the handle, which is a good thing, but this object is still pretty clumsy to use. Read CTimer for ideas on improving the timer interface.
The object which wraps HINIFILE will look a bit like the CFile class. HDRIVER and HMACHINE can also be wrapped, although I'm not convinced that it is very useful to do so.
There is nothing special about the HANDLE datatype - the SEMAPHORE type looks ripe for a simple wrapper, and I wouldn't be at all surprised if wrapping threads saved work for those that use them.
For those who ar curious about such things, the 4Test Language Reference clearly states that "you cannot directly create values of type HANDLE".
This isn't true - you can create a HANDLE; it just doesn't do you any good. The handle probably won't point to anything; passing it will generate some nice E_HANDLE exceptions. Of course, the handle you create might match a legitimate HANDLE already in use, which could lead to some interesting behavior.
The code fragment below creates a handle....
//Don't ever do this.... HANDLE h = [HANDLE]7 ;
| Mr. Cluey : Classes : Handle Classes |