4.5. Creating ActiveX Components

In the Professional and Enterprise editions of Visual Basic, you can use class modules to create ActiveX components and compile them as either ActiveX DLLs or ActiveX EXEs. The choice you are faced with, effectively, is whether you require an in-process or an out-of-process component.

4.5.1. In-Process Components

An ActiveX DLL is an in-process component. But why is it called "in-process"?

In 16-bit Windows, a DLL effectively became part of the operating system, and all running processes had access to the DLL. A handle to the DLL could be obtained centrally from the operating system, and the operating system knew at any given time how many (if any) handles to a particular DLL had been issued. In 32-bit Windows, a DLL doesn't become part of the operating system. It becomes part of the process space of the application that calls the DLL. A call to a DLL forces the operating system to create a file mapping object for the DLL. This object is then mapped into the process space of the client or calling process. Hence the term in process.

4.5.2. Out-of-Process Components

When you create an ActiveX EXE, you are creating an out-of-process component. An ActiveX EXE runs as a separate process with its own threads. This is ideal for cases in which multiple applications (or multiple clients) are likely to require the functionality offered by the classes included in the ActiveX EXE. Moreover, an ActiveX EXE need not reside on the same system as its client; the Distributed Component Object Model (DCOM) provides location transparency.

While including publicly accessible classes in ActiveX, EXEs can have definite advantages. They do involve launching a process apart from the client, and therefore, usually provide inferior performance compared with ActiveX DLLs.

4.5.3. Class Module Properties

Depending on the type of project in which the class module (.cls) file is included, class modules support the following properties that control their precise behavior:


Instancing

Only available when a class is part of an ActiveX project, the instancing property defines how instances of the class are created. Its values are:


GlobalMultiUse

The class becomes global to the project in which it's defined; references are not necessary. For example, most VB language objects are global; as soon as you load the environment, they are available to be used. Use this property setting to create class modules containing enumerated constants for your object model.


MultiUse

The class has scope (i.e., it's visible) outside the project in which it's defined, and it can be instantiated using the New keyword or the CreateObject function. Use this for top-level objects in a hierarchy or object model.


PublicNotCreateable

Although the class has scope (i.e., it's visible) outside of the project in which it's defined, it can't be instantiated from outside the project using the New keyword or the CreateObject function. Use this for child objects that can be created by accessing a function or property of a higher-level object.


Private

The class can't be "seen" outside the project in which it's defined.


SingleUse

Every call by a client to create the object using either the New keyword or the CreateObject function creates a completely new instance of the object. Only available in ActiveX EXE projects.


GlobalSingleUse

As with the SingleUse property value, every call by a client to create the object using either the New keyword or the CreateObject function creates a completely new instance of the object. However, GlobalSingleUse allows methods and properties to be seen as part of VB. Only available in ActiveX EXE projects.


DataSourceBehavior

(VB6 only) This property, which isn't available when the class is part of an ActiveX EXE project, defines the ability of the class to serve as a data source for other objects. Values are:


vbNone

The class doesn't expose a bindable data interface.


vbDataSource

The class can act as a data source for other objects.


vbOLEDBProvider

The class can act as an OLE DB Simple Provider.


DataBindingBehavior

(VB6 only) This property controls the behavior of the class when it's bound to an external data source. Values are:


vbNone

The class can't be bound to external data sources.


vbSimpleBound

The class can be to bound to a single data field in an external data source.


vbComplexBound

The class can be bound to a row of data in an external data source.


MTSTransactionMode

(VB6 only) Only available when the class is part of an ActiveX DLL project; you should set this property whenever the class is registered within Microsoft Transaction Server. This property automatically sets the Transaction Support property for the object when it is registered in MTS, thereby giving the developer control over how the component is used by MTS. Values are:


NotAnMTSObject


NoTransactions

Sets the Transaction Support property to "Does not support Transactions."


RequiresTransactions

Sets the Transaction Support property to "Requires a Transaction."


UsesTransactions

Sets the Transaction Support property to "Supports Transactions."


RequiresNewTransaction

Sets the Transaction Support property to "Requires a new Transaction."


Persistable

(VB6 only) Only available when a class is part of an ActiveX project, this property determines whether the class can be saved to disk. Values are:


NotPersistable

The class properties can't be saved.


Persistable

The class property values can be saved to a property bag.

4.5.4. Component Creation Hints and Tips

What follows are an assortment of topics, many of them frequently overlooked, to consider when building your own ActiveX components.

4.5.4.1. Including user interfaces in ActiveX components

There is nothing to stop you from including form modules within your ActiveX EXEs and DLLs. However, you should distinguish between an ActiveX component that is designed to run on the client machine and one that will be a remote server.

The rule is that ActiveX remote server components shouldn't contain any UI whatsoever—not only no forms, but no message boxes either. The reason for this is that the UI appears on the remote machine, not on the client machine. You can imagine the uselessness of a message box popping up on some remote application server stuck away in a locked machine room, waiting for someone to click OK!

4.5.4.2. Allowing clients to use the For Each...Next statement

Most of the time, we take the For Each...Next loop for granted as it iterates the members of an array or a collection. It's the fastest, most efficient method of visiting all the members of the collection or array, and we could care less that, by enumerating the members of the collection, the unseen code is actually generating new references to members of the collection with each iteration of the loop. However, as the provider of a collection class, it's up to you to provide an interface that the For Each...Next statement can work with.

This may sound a little daunting, but you'll be pleasantly surprised by how easy it is to implement a property that enumerates members of the collection within your class. First of all, you must create a Property Get procedure called NewEnum with the type of IUnknown. Its syntax is always the same:

Public Property Get NewEnum() As IUnknown
    Set NewEnum = mCol.[_NewEnum]
End Property

where mCol is the name of your private collection object variable.

Second, set the Procedure ID for this Property Get procedure to –4. To do this, select the Procedure Attributes option from the Tools menu, then click the Advanced button on the Procedure Attributes dialog. Enter a Procedure ID of –4. You should also check the "Hide this member" option to prevent the property from appearing in the IntelliSense drop-down list.

For more information about using the For Each...Next statement in a client application, see its entry in Chapter 7.

4.5.4.3. Error handling

More care and thought than normal needs to go into handling errors within a class module. In general you shouldn't do more than pass on the error to the client with the Err.Raise method. However, if your error handling code is going to pass the error number back to the client, you need to decide if the error number should have the vbObjectError constant added to it or not. (vbObjectError is a constant that allows a referencing object to determine that the error was generated in a VB class object.) Another consideration is whether or not and how the Err.Source property is passed back to the client.

A full discussion of error handling and the Err object, including class module error handling, can be found in Chapter 6.

One last point to note is that you should never use the End statement within a class module.

4.5.4.4. Use Dictionary objects rather than Collection objects

When you're creating object models, many of your ActiveX server components will be based upon collections of other objects. Ever since VB4, developers have become accustomed to writing these classes based on the Collection object (in effect, creating a wrapper for the Collection object), taking for granted the fact that the Collection object is slow and expensive in terms of overhead, but also knowing that there was no real alternative. However, if you're using VB6 to create collection classes, I strongly recommend that you use a Dictionary object in place of the Collection object. The Dictionary object is fast in comparison to the Collection, and it has more functionality. For a complete explanation of the Dictionary object, see the Dictionary Object entry in Chapter 7.

4.5.4.5. Provide your own Exists property in collection classes

Collection objects are neat for quickly accessing values and objects given a key value. You can add items to the collection, retrieve a given item from the collection, find out how many items are in the collection, and remove an item from the collection. For example, this simple code retrieves an object from a collection using its key:

Public Property myClass(vVal as Variant) As myClass
   Set myClass = mCol.Item(vVal)
End Property

However, if the key vVal doesn't exist in the collection, you're faced with a runtime error. The gap in the usability of the Collection object is that there is no Exists property. But you can easily plug this gap and provide a way to add a missing property to the collection as follows:

Public Property Exists(sVal as String) As Boolean
   On Error Goto myClass_Err
   Dim oTest As myClass
   Set oTest = mCol.Item(sVal)
   Set oTest = Nothing
   Exists = True
   Exit Property
TryToGetIt:
   If getItemforCollection(sVal) Then
       Exists = True
   Else
       Exists = False    
   End If
   Exit Property
MyClass_Err:
   If Err.Number = 5 Then
      Resume TryToGetIt
   Else
      'over-simplified error handler!
      Err.Raise Err.Number + vbObjectError 
   End If
End Property

Quite simply, you attempt to assign the collection to a test object. If the assignment works, return True; otherwise, handle the error by calling a function, getItemforCollection, that adds the new item to the collection. If getItemforCollection returns False, however, this means that the item doesn't exist, and the Exists property must return False. Using the Exists property, you can preface each call to get an object with a conditional statement like the following:

Set oMyClasses = New myClasses
    If oMyClasses.Exists("xyz") Then
        Set oMyClass = oMyClasses.MyClass("xyz")
    Else
        MsgBox "Sorry it doesn't exist!"
    End if
Set oMyClass = Nothing

4.5.4.6. When is an in-process component out-of-process?

Until recently, you could say with certainty that an ActiveX EXE would run out-of-process, whereas an ActiveX DLL would run in-process. However, if you were now asked if a particular component was in-process or out-of-process, you would have to respond with the question, "Whose process?"

Technically, ActiveX DLLs will always be in-process, but traditionally (if you can call a couple of years a tradition!) the "process" referred to is that of the client application—but not any more! This shift in thinking has come about because of new technologies such as Distributed COM (DCOM), Microsoft Transaction Server (MTS), and Microsoft Distributed Transaction Coordinator (MDTC), which are blurring the traditional process boundaries.

For example, an ActiveX DLL running in MTS is running within the MTS's process, but it's running outside of the client application's process. In fact, boundaries are becoming blurred to such an extent that to the user of the client application, the old boundaries are for the most part completely invisible. During the development of a client-server application recently, I was aware that one of my DLLs was executing, only I wasn't quite sure which one of three machines it was executing on!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset