Module initialization versus component initialization
Global classes have one feature that standard modules desperately need—an initialization event. Chapter 8 discusses some of the hacks required to initialize data in standard modules. Many of those problems disappear with global classes (although I’d trade initialization events for a variable initialization syntax in a minute). With components, you not only have the option of initializing data in Class_Initialize, you can also use the DLL initialization event—better known as Sub Main.
The choice is closely related to the one between instance data and shared data. If you want something to happen just once when the DLL is first loaded, you create a Sub Main in a standard module and do the work there. My convention is to put Sub Main in a shared data module (SHARED.BAS). This module is the place to put any shared variables used by the entire DLL but not tied to any particular module. I also provide a global class (SHARED.CLS) that uses delegation to expose the shared data for outside users. Real instance data should be placed in a separate module (INSTANCE.CLS).
Let’s look at an example where initialization is a big winner. Many of the functions in BYTES.BAS work by calculating powers of two. We’ll be looking at some more interesting functions later, but here’s a simple example:
Function GetBit(ByVal iValue As Long, ByVal iBitPos As Integer) As Boolean
BugAssert iBitPos >= 0 And iBitPos <= 31
GetBit = iValue And (2 ^ iBitPos)
End Function
If you think about this for a minute (as did the source of this tip, Hardcore programmer Nick Malik), there’s no reason to calculate 2 to the nth power every time you come into this function. We already know the powers of 2. They aren’t going to change any time soon, so why not look them up in an array rather than calculate them?
Function GetBit(ByVal iValue As Long, ByVal iBitPos As Integer) As Boolean
BugAssert iBitPos >= 0 And iBitPos <= 31
GetBit = iValue And Power2(iBitPos)
End Function
Power2 is an indexed property that reads a private array containing powers of 2. Lookup functions are a common and easy technique in languages that support initialization of arrays, but it’s annoyingly difficult to fill that array in Visual Basic—especially in a standard module. The MBytes module initializes differently, depending on the fComponent variable.
Private aPower2(0 To 31) As Long
§
Property Get Power2(ByVal i As Integer) As Long
BugAssert i >= 0 And i <= 31
#If fComponent = 0 Then
If aPower2(0) = 0 Then
aPower2(0) = &H1&
aPower2(1) = &H2&
aPower2(2) = &H4&
§
aPower2(31) = &H80000000
End If
#End If
Power2 = aPower2(i)
End Property
#If fComponent Then
Private Sub Class_Initialize()
aPower2(0) = &H1&
aPower2(1) = &H2&
aPower2(2) = &H4&
§
aPower2(31) = &H80000000
End Sub
#End If
In the standard module, we initialize the first time a user calls the Power2 property. Testing for that first occurrence takes time, even after the first time when the test is always going to be false. In the global class, the initialization hap-
pens the first time anybody accesses anything in BYTES.CLS. There’s no extra cost within Power2.
I could have made this code infinitesimally more efficient by initializing the array in Sub Main instead of in the Initialize event. That way, the initialization would be done once for the whole DLL rather than once for each user. After all, the powers of 2 are going to come out the same for all users. The cost of this optimization would be decreased modularity, leading to maintenance difficulties. You would have to put the initialization code in Sub Main rather than in the module where the array is used and exposed. That tradeoff may be worthwhile in some cases, but it doesn’t make much difference here.