A control resides on a form, but a server doesn't. As such, the server needs to be set up, meaning that a client needs to add a reference to and then create an instance of some server class using code like this:
Dim o As Server.ClassName Set o = New Server.ClassName
Of course, when o goes out of scope, your class instance is going to disappear, but we all know this, right?
With a control, there's no need to create an object variable to hold the instance (it's like a global class in this respect). You create an instance of a control up front by setting its name at design time. For example, Command1, the name you assign to the control at design time, is at run time an object variable that is set to point to an instance of a CommandButton class object. The control's lifetime is the same as the lifetime of the form on which it resides.
"Ah," you say, "but I can have multiple instances of my server." By either using a control array, using the new Add method on the Controls collection, or by loading multiple control-holding forms you can also have multiple instances of a control. "Ah," you say again, "but I can have many clients use one instance of my server object!" "Ah," I say, "so you can with controls; one form serves many consumers." Hands up all of you who have used just one CommonDialog control throughout your application to handle all your common dialogs!
Ok, enough with the comparison for now. Because controls reside on a form we tend wrongly to think of them as having to be based on, or represent, something that's manifestly visual (well, at least I do), although we know deep down that this is not necessarily the case. A CommonDialog control isn't visible at run time. Even so, controls do present some type of user interface, right? After all, the CommonDialog control shows dialog boxes. Not necessarily, though-think about the Timer control, which has no user interface at run time. The Timer is a good example of using an object that is not wrapped in an ActiveX server (though you might conversely argue that it should be). It's just a component that presents to the programmer a particular way of being consumed. The Timer control is also pretty cool in that it sits in your toolbox (which by now might have many panes in it). A toolbox tab can hold all your server controls; just drag-and-drop them as required.
How about using MLP to create this kind of component? Write the controls in Visual B++, Visual J++, or Visual C++ and then use them from Visual B++. You could write a MatrixInversion or a HashTable server control, or whatever you want. So long as the source language can build controls, you have a natural way of consuming them, in-process!
Here are a few more bits and pieces (minutiae, if you will) about controls and servers:
- Controls normally self-register when they are loaded (in the DLL sense), so they are potentially a little easier to deploy since they don't need preregistering before they're used.
- Control references like Command1 cannot be killed by assigning Nothing to them. Such a control reference is always valid, even if the form is unloaded, because if the control is accessed, both the form and the control are recreated (no more Object Variable Not Set messages). Control object references are basically constants (both good and bad, perhaps). If it were legal, we'd have to declare them like this:
Const WithEvents Command1 As New CommandButton
- All controls are always in scope within a project; in other words, you can't make them private and non-accessible from outside the form in which they reside. Conversely, a server's object variable can be declared Private. A control object reference is implicitly declared Public.
- Controls (UserControls, that is) can be brought entirely inside an application or compiled to OCXs. (Servers have the potential to be brought inside and application, also).
- Servers are easier to consume out-of-process than controls.
- Controls are easily consumed by other controls. That is, they easily can be used as the basis of another control (a kind of implementation inheritance vs. the strict interface inheritance available to servers).
- A control's initial state can be set at design time via the Properties pane and, as such, controls do not have to have an implicit null state when they're created. This type of initialization is not possible with servers.
- Controls can save their state to a property bag. With some simple abstraction, this could be modified to provide a type of object persistence for controls.
- Controls are supported by user interfaces. _ The Property Page and Toolbox can be used to interact with them (even before they're created).
- Controls can easily draw or present a user interface-it's their natural "thang," of course. (Imagine seeing how your OLE DB data provider control is linked to other controls at design time!)
- Control instances are automatically inserted into the Controls Collection, which itself is automatically made available.
- Controls have a more standard set of interfaces than servers. For example, controls always have a Name property (and a HelpContextID, and a Tag, and so forth).
- Controls accessed outside their containing form need to be explicity scoped to-they can't be resolved via the References list, in other words. Controls can be made more public by setting an already public control-type object reference to point to them, making them accessible to anyone with access to the object reference.
- Control arrays are, well, they're for controls!
- Controls can run in the IDE when they're added to a project at design time, so they are a little easier to set up and debug early on.
- Making a control work asynchronously requires a nasty kludge or two, because controls can never easily be running out of process. To do any form of asynchronous work they need their own thread (or a timer).
- In-process servers present an extra nice ability, through global classes, to act as repositories for compiled, yet shared, code libraries and can be used to create objects (containing modules) that are as useful as VBA itself (such as in the object reference-VBA.Interaction.MessageBox and so on). This functionality is also available using controls. At TMS we wrapper the entire C run-time library in this way and make the library available through use of a control typically called CLang. So CLang.qsort gets you the Quick Sort routine and so on. Likewise, we also wrapper access to the Registry using a Registry control. So for example you might access the Registry with commands such as RemoteRegistry.ReadKey and LocalRegistry1.WriteValue.
I give no recommendation here as to which you should use where-there are just too many variables and too many terms to the equation. You decide.