Instance data versus shared data


When you start to create DLLs, you’re going to run smack into one of the standard problems that programmers in other languages have been dealing with all along. How do you distinguish instance data from shared data?


Imagine you have a program that uses the completed VBCore component. This program uses an ActiveX control that also uses VBCore. The program attaches to VBCore and calls the InstanceCount property in the GInstance global class module. InstanceCount looks like this:

‘ GInstance global class
Private c As Long
Property Get InstanceCount() As Long
InstanceCount = c
c = c + 1
End Property

The first time the program calls InstanceCount, it gets back 0. The second time it gets 1. At this point, the control calls InstanceCount. Will InstanceCount return 0 or 2? In other words, will both users be indirectly accessing the same copy of the private variable c? The answer is…no. The control will get 0 because
it gets a completely separate MInstance object with its own copy of the In­stanceCount property and the c variable.


But what if you want one count for all users? In other words, you want shared data rather than instance data. Most languages have a keyword (usually Static) specifying that data should exist in one and only one place. Visual Basic doesn’t have this feature, but you can get it indirectly by putting the data in a standard module. A standard module always has one and only one instance, whereas class modules of any type (including forms) have a separate instance for each object.


It’s easy enough to declare data in a standard module, but how are you going to make it public to users of your DLL? Standard modules can’t be public, but
you can create a global class that delegates to a standard module. To accomplish this, you’re going to need cooperating module pairs. The GShared module looks like this:

‘ GShared global class module
Property Get SharedCount() As Long
SharedCount = MShared.SharedCount
End Property

The MShared module looks like this:

‘ MShared standard module
Private c As Long
Property Get SharedCount() As Long
SharedCount = c
c = c + 1
End Property

Assume user one calls SharedCount twice. When user two calls SharedCount, it gets back 2, not 0. User two has its own separate copy of the GShared object, but that doesn’t matter because the real work is being done in the MShared module, which has only one instance.


Before we get too carried away with the instance data problem, let’s look at a different situation. Imagine we have two completely different programs that both use the VBCore DLL. Will they use the same copy of the SharedCount data? No, because separate programs are in separate processes, and in 32-bit Windows, separate processes are like separate galaxies. You’ll need more than shared data in a DLL to communicate between processes. Chapter 6 will talk more about processes, and Chapter 11 will discuss inter-process communications.


The instance data versus shared data problem has many variations. It’s not just concerned with data that is made directly available to the outside through properties. It applies to any module-level or static local data, even if that data is
never directly exposed. For example, you often have to use Static variables in procedures to make sure that data is initialized properly. Chapter 8 will discuss many of the techniques required. But whenever you initialize Static data in a component, you should think carefully about whether you want that data to be initialized just once for the whole process or once for every user. If you need to initialize just once, you must use the delegation technique.


In fact, you might be tempted to use delegation as your default means of exposing global procedures in a component. It certainly has some attractions from a maintenance standpoint. For example, assume another module in the VBCore component, GMeToo, calls the SharedCount property. With the delegation technique, there’s no need to qualify the call to SharedCount with a module name. Instead of

c = GShared.SharedCount

the other module can call directly:

c = SharedCount

If an outside user calls SharedCount, the SharedCount in the global class gets called and it calls the standard module version of SharedCount. If another global module calls SharedCount, it calls the standard module SharedCount directly.


If you delegate all your global procedures to standard modules, you’ll only have to maintain the standard module because it does all the work. You have to add new delegation procedures to the global class every time you add a new procedure to the standard module. With the global object technique described earlier, you have to keep separate copies of the code in the standard and global modules. From a maintenance standpoint, the global object technique is a nightmare, while delegation is merely a bad dream. But from a correctness and efficiency standpoint, the global object technique is usually the right thing to do because most DLL data should be instance data rather than shared data.


To illustrate this point, consider the Rnd function from the VBA library. It uses private variables to create a random number sequence. If you call Rnd with the same seed from different users, it will return the same random number sequence for each user. Apparently, the VBA library uses instance data for the variables used to maintain the random number sequence. On the other hand, what about the Screen object? There’s only one screen in the system (at least as far as Visual Basic is concerned), so the DLL could use shared data to maintain one and only one copy of the Screen object. In fact, I don’t know if that’s the case. Visual Basic might use only local data for the Screen object, and there might be multiple identical Screen objects for each client. I do know that the System object we’ll be creating later in this chapter uses shared data to create one and only one instance.


There’s one situation where you have no choice in the matter. Any procedure that uses the AddressOf operator to set up callbacks must delegate the procedure from a standard module. This limitation of the AddressOf operator applies to all classes, not just to global classes, but the difference might be less obvious when you’re converting a standard module to a global class. Code that worked fine in the standard module starts giving errors when you put it in a global class. The fact that you can have one and only one copy of a callback procedure doesn’t change the fact that you might have more than one user trying to use the callback at the same time. You have to consider this possibility and deal with it. The FindAnyWindow function deals with it by locking out any second client until the first client is finished. It’s also possible to make the callback function figure out which object is using it and deal with that object only. We’ll get to that technique later.