Class gotchas


The Global Wizard makes it so easy to convert a standard module to a global class that it’s easy to forget that you’ve just created a completely different kind of module. But you’ll soon get a rude reminder if your standard module procedures use incompatible types.


You can’t have parameters that are UDTs or fixed-length strings in classes. There’s no easy workaround, but since I generally avoid these types in components, the restriction doesn’t bother me. Forms and Controls are a different matter. I have several functions that take Control parameters. Here’s one from the MWinTool module:

#If fComponent Then
Function LookupItem(ctl As Object, sItem As String) As Long
#Else
Function LookupItem(ctl As Control, sItem As String) As Long
#End If
LookupItem = SendMessageStr(ctl.hWnd, LB_FINDSTRING, -1&, sItem)
End Function

By using the Control type in the standard module version, this function forces at least minimal compile-time checking. It will fail if you pass a form instead of a control. That’s better than nothing, but what you really want is not a Control type but an IListBox type. An IListBox would be an interface that included any kind of listbox control (such as the ComboBox control) but excluded any other control type such as EditBox or CommandButton. Unfortunately, IListBox doesn’t exist, and the Control type is a private class that isn’t recognized in public classes. Therefore, you have to be satisfied with Object (and late binding) in the global class version of LookupItem.


Here’s another little gotcha with global class modules. One of my favorite functions is GetRandom, which returns a random number within the range of two boundary numbers. The normal output of the Rnd function (a floating point number between 0.0 and 1.0) has little to do with my normal use of random numbers, so I used to use the following function instead:

Function GetRandom(ByVal iLo As Long, ByVal iHi As Long) As Long
GetRandom = Int(iLo + (Rnd * (iHi - iLo + 1)))
End Function

But there’s a problem with GetRandom. When a client program uses VBCore, there are two independent instances of the Visual Basic library. The client program uses one. The VBCore component uses another. If you use the Randomize statement in the client, it seeds the random number sequence for the client program, but it has no effect on the component’s GetRandom function. During the development of VBCore, I used GetRandom for several weeks before I noticed that it was always generating exactly the same results no matter what parameters I gave to Randomize. I never had these problems with the standard module because GetRandom was in the client program.


I have three solutions. The first is to call Randomize in an initialization statement in the MUtility module.

#If fComponent Then
Private Sub Class_Initialize()
‘ Seed sequence with timer for each client
Randomize
End Sub
#End If

The default behavior of Rnd is to always use a hard-coded seed number picked by Visual Basic. I always use a seed number picked by fate (in the form of the system timer). This initialization keeps me from forgetting and using the default sequence by mistake.


The second solution is to delegate the Randomize statement to a component version that the client can call to initialize the library.

‘ Seed the component’s copy of the random number generator
Sub CoreRandomize(Optional Number As Long)
Randomize Number
End Sub

Those who want to initialize their own random sequences for nefarious cheating schemes can use this version.


The third solution is to throw Visual Basic’s random number functions in the trash and roll your own. The sidebar that follows tells how I did it.


Really Random


Random number generators are not all created equal. Chapters, if not books, have been written about random number generation. Although Visual Basic documentation doesn’t completely describe its random number generator and the source code is not available, the new compiler makes it easily accessible
to anyone with a debugger who can read assembly code. I looked at it and
would describe it as a first-level generator—better than the one in Microsoft’s
C run-time library but probably not as good as the one I added to the FORTRAN run-time library back in another lifetime.


In those days (circa 1992), the FORTRAN group had a little argument with the C group. The C group provided the random number generator that we used in our random functions, but we wanted an improved version. The C group refused because they wanted compatibility with their existing algorithm. In theory (but rarely in practice), a program created with a new version of the language might need to get the same random number sequence from the same seed as it did in previous versions. The original C generator used a 16-bit algorithm with 16-bit limitations, but the C group didn’t want to update the code for their 32-bit library because they feared breaking client code. When we lost the battle for a new algorithm, we abandoned the C run time and implemented our own improved algorithm, based on research of the extensive literature on random numbers.


The VBCore component provides that better algorithm as an alternative to the Randomize and Rnd procedures. I’m not saying this algorithm is better, just that it’s different and that the pedigree for it is provided in the comments. This is still a first-level randomizer. It randomizes by using some carefully chosen constants to do some multiplication and subtraction operations on the last random number in the sequence. Second-level randomizers usually have a two-level system in which one random number is generated and then used to generate another.


You can check the code in RANDOM.CLS. The Seed sub starts the sequence (like Basic Randomize). The RandomReal function returns a floating-point number between 0 and 1 (like Basic Rnd). The Random function returns an integer within a given range (like GetRandom). Unlike Rnd, my algorithm uses the timer count as the seed by default so that you will come up with a different random sequence each session unless you specifically give a random seed. I use my own random module throughout the book instead of the Basic version.