Server Design Issues

[Previous] [Next]

In the first step of the design phase, you should plan which types of clients you want to support. You should split the potential clients of your components into two categories: clients that use direct vTable binding and scripting clients that use late binding. Table 4-1 shows the types of clients produced by a few popular languages.

Table 4-1 Types of Clients Produced by Various Languages

Languages That Produce Direct vTable-bound Clients Languages That Produce Late-bound Clients
Visual Basic 6 VBScript
Visual Basic 5 JavaScript
Visual Basic 4 Visual Basic 3
C++
Delphi

If you can assume that your component will be used by clients from only one category, your life will be easier. If your components will be accessed exclusively by clients that use direct vTable binding, you'll have more options: You'll be able to design components in terms of user-defined interfaces, enumerations, and UDTs, features you should generally avoid when you create components for scripting clients.

Clients that use direct vTable binding rely on information in your server's type library. Scripting clients never use type libraries—they rely on ProgIDs and late binding. We'll concentrate first on clients that use type libraries and address design issues for scripting clients later in the chapter.

When you design a server, you should start by thinking about what you want published in the server's type library. The information in the type library is what the outside world sees and is the means, at the most basic level, by which your components expose their functionality to their clients.

We'll start by looking at high-level attributes for a type library, and then we'll work our way down to attributes for components and methods. You'll also learn how to publish user-defined interfaces, enumerations, and UDTs in your type libraries and use them in method definitions.

Projectwide Type Library Attributes

When you create a new server project, you should immediately open the Project Properties dialog box and change the Project Name and the Project Description, as shown in Figure 4-2. These important pieces of information become high-level attributes of your type library. The Project Name creates a namespace for the components within it. Client applications that reference the type library can refer to components in your server with a fully qualified name such as DogServer.CBeagle. You should also note that the Project Name is always the first part of the ProgID for all components in the server project.

click to view at full size.

Figure 4-2 In the Project Properties dialog box, you can assign a Project Name and a Project Description to your server. These settings become top-level attributes of the server's type library.

The Project Description is used as the friendly description of the type library itself. Once your server has been registered on other developers' workstations, these developers will see this description when they add your server to their projects using the References dialog box. If you don't include a description, Visual Basic uses the Project Name as the description.

When you write client-side code, you should use the fully qualified name DogServer.CBeagle rather than the short name CBeagle. Most of the time, using the short name isn't a problem, but at times it can get you in trouble. For instance, using the short name creates ambiguity if the Visual Basic project for a client application has references to two different type libraries and each has a CBeagle component. Visual Basic simply resolves this ambiguity by selecting the CBeagle component from whichever type library was referenced first. As you can imagine, this approach can produce undesirable results. While the References dialog box allows you to change the priority of referenced type libraries, using the fully qualified name is a much better solution to this problem.

The Component Instancing Property

The Instancing property settings of class modules have a profound effect on what is published in your server's type library: They determine what coclasses and interfaces are seen by the outside world. The following are the available settings ranked in terms of usefulness to a COM+ programmer:

  • MultiUse The class module generates a creatable coclass and a dual interface. This interface is built from the public members and is marked as the default for the coclass. Visual Basic generates a CLSID and an IID for the class module. The class becomes a component that clients can use to instantiate objects through the SCM.
  • PublicNotCreatable The class module generates a noncreatable coclass and a dual interface. This interface is built from the public members and is marked as the default for the coclass. Visual Basic generates a CLSID and an IID for the class module. The class is not a true component because clients can't use it to instantiate objects through the SCM.
  • Private The class module has no effect on what is published in the type library. It has neither a CLSID nor an IID. The class can be used only by client code in the local project. The SCM is never involved when objects are instantiated for the class.
  • GlobalMultiUse This setting is like MultiUse, but it also allows client applications to access methods of the class as if they were global functions.
  • SingleUse (for out-of-process servers only) This setting is like MultiUse, but the server guarantees that each object instantiated from the class lives in its own process. That is, there is a separate ActiveX EXE server process for each instance of this class.
  • GlobalSingleUse This setting is like SingleUse, but it also allows client applications to access methods of the class as if they were global functions.

When you create ActiveX DLLs for a COM+ application, the most common setting for the Instancing property of a class module is MultiUse. In fact, you might never use another setting. MultiUse is also the default setting when you create a new class module. Each MultiUse class you compile in an ActiveX DLL can be configured as a COM+ component.

The other Instancing setting you might use is PublicNotCreatable. One reason to use a PublicNotCreatable class module is to create a user-defined interface. (Chapter 2 discussed how to create and implement an interface defined in a PublicNotCreatable class module.) A PublicNotCreatable class module has an associated IID to identify the interface in the type library. However, unlike with MultiUse classes, Visual Basic doesn't build in support for activation through the SCM. Client applications can't create objects from a PublicNotCreatable class.

If you're creating servers with Visual Basic that you don't intend to run as configured components in the COM+ runtime environment, there are other reasons why you might use a PublicNotCreatable class module. For example, PublicNotCreatable classes can be used to instantiate objects from client code within the same project. Note that the client code must use the New operator to do this. The CreateObject function won't work because a PublicNotCreatable class module doesn't get Registry entries for its CLSID, nor does it get any built-in activation support. When you use the New operator, the Visual Basic runtime instantiates the object without the help of the SCM.

Creating objects from PublicNotCreatable classes can be problematic in COM+ because the new object is not initialized in a valid context. This means that the object can't use any of the built-in services the COM+ runtime offers. For a component to benefit from these COM+ services, its objects must be activated through the SCM. We'll talk more about this topic in Chapter 6, but for now keep in mind that objects created from PublicNotCreatable classes can't take advantage of what COM+ has to offer.

Private classes suffer from the same shortcoming as PublicNotCreatable classes when used in a COM+ application. Private classes don't support activation through the SCM. They also can't define an interface, and they have no IID in the type library. They are invisible to all clients that look at the type library. The only reason to use a Private class is to create a behind-the-scenes object that doesn't use any of the built-in services COM+ provides.

SingleUse classes are of no use to COM+ programmers because they can exist only in ActiveX EXE projects. In COM+, you can use only components compiled into ActiveX DLLs. However, SingleUse classes do serve a purpose. For example, if you're creating a simple ActiveX EXE server that you plan to run on each user's desktop, a SingleUse class has some interesting uses. If you create two objects from a SingleUse class, each object is created in a separate server process. If you're running two instances of a client application on a user's desktop and they activate the same type of object, you can use a SingleUse class to give your objects an extra level of fault protection. Each client activates its object in a different process. SingleUse classes also provide a simple way to achieve multithreading because each process gets its own thread. Once again, you should use this technique sparingly and only with a very limited number of clients.

MultiUse and SingleUse both allow you to make a class "global." This simply makes the class easier for the client to use. If you have a standard MultiUse class that calculates a tax rate, for example, you access the object like this:

 Dim Obj As CTaxCalculator, Tax As Currency Set Obj = New CTaxCalculator Tax = Obj.CalculateTax(28600) 

When you set the instancing property of the class to GlobalMultiUse, the client can access the method as if it were a global function, as shown here:

 Dim Tax As Currency Tax = CalculateTax(28600) 

Global classes save the client the trouble of instantiating an object before calling a method. Visual Basic provides this feature by marking the coclass with the AppObject attribute in the type library. On the client side, Visual Basic creates an invisible object behind the scenes when it is first needed and maps all global calls to method calls on this invisible object. Visual Basic is currently the only development tool that provides the convenience of transparently mapping global method calls on the client side. A programmer creating a client application using C++ or Java can't tell the difference between a class marked GlobalMultiUse and a class marked MultiUse. Global classes simply provide a syntactic sugar for other Visual Basic programmers.

You need to consider a few issues when you're designing with GlobalMultiUse classes. First, they work correctly only when the client code that uses them lives in a different project. One class can't take advantage of the global aspects of a GlobalMultiUse class when both classes are in the same project. Moreover, you don't have much control over when the invisible object gets created or destroyed. It is created on first use and is destroyed when the client application terminates. This can result in inefficient code when a GlobalMultiUse object holds expensive resources. Often, you're much better off instantiating objects from standard MultiUse classes and explicitly setting references to Nothing when you want to release them.

Modifying Procedure Attributes

You can influence how Visual Basic sets attributes for the methods and properties of your classes and interfaces by choosing the Procedure Attributes command from the Tools menu while the code for the class module you want to modify is in the active window. Figure 4-3 shows the dialog box that appears.

Figure 4-3 In the Procedure Attributes dialog box, you can modify the attributes of the methods and properties in a public class module. All the settings that you specify in this dialog box are written into the interface definition in the server's type library.

You use the Description setting to document the semantics of the method or property. Other Visual Basic programmers will see your descriptions when they examine your server's type library with the Object Browser. Providing descriptions is pretty easy to do, and it makes your server seem more polished.

You can also set the Procedure ID to a different value. For instance, if you set the Procedure ID for a property to Default (the value 0), the property will be recognized as the default property of the object. Here's what the resulting IDL looks like:

 [     id(00000000),     propput,     helpstring("Name as it appears on birth certificate") ] HRESULT Name([in] BSTR Name); 

Now a client can access this property both explicitly and implicitly, like this:

 Dim Dog1 As IDog, Dog2 As IDog Set Dog1 = New CRetriever Set Dog2 = New CRetriever ' Name property is accessible explicitly. Dog1.Name = "Milli" ' Name property is also accessible implicitly. Dog2 = "Vanilli" 

As you can see, you can set several options in the Procedure Attributes dialog box. You can also hide methods from other programmers. In short, you can make your server more polished by adding certain method attributes. Note that many of the settings are relevant only to programmers who are building ActiveX controls. Many options, such as Don't Show In Property Browser and User Interface Default, aren't useful to programmers who are creating nonvisual components for the middle tier.

Friend Methods

Marking a method as a friend in a Visual Basic MultiUse class creates an interesting effect. A friend method is accessible to any code in the server project, but it isn't exposed to the outside world. You could say that it's public on the inside and private on the outside. Or you could say that in a public class, private members provide modulewide access, friend members provide projectwide access, and public members are available to any client inside or outside the server. When you mark a method or a property as a friend, it isn't published in a type library. It has nothing to do with COM.

Although friend methods might seem attractive during the design phase, you're better off avoiding them. First of all, many programmers use this friend syntax without realizing that they're breaking encapsulation. Later, after removing or changing a friend method, they end up having to modify all client code that had a dependency on the method.

The second (and more significant) reason to avoid using friend methods is that they don't get published in the type library. This is problematic because the COM runtime uses the interface definitions in the type library to build proxies and stubs. The only time you can use a friend method is when you're absolutely sure you don't need a proxy or stub between the client and the object. As you'll see in Chapter 6, objects created from components configured in a COM+ application are connected to their client through a lightweight proxy. Because of this, you should definitely avoid friend methods when you create configured components.

Using Enumerations

As a COM developer, you should try to use enumerations in your designs as much as possible. Enumerations let you declare logically related sets of constants in your type library. Once these enumerations are defined in the type library, you can use them for method parameter and return value types. You can also use them to define sets of error codes. Here's what an enumeration definition looks like in a Visual Basic class module:

 Enum FetchItemEnum     dsSlippers     dsNewspaper     dsBeer End Enum 

Now let's put this enumeration to work by using it to define a method parameter. If you define a method like this

 Sub Fetch(ByVal Item As FetchItemEnum)     ' Visualize your implementation here. End Sub 

a client can call the method passing one of the enumeration values like this:

 Dim Spot As CRetriever Set Spot = New CRetriever Spot.Fetch dsBeer 

Enumerations make the client code more readable. Moreover, when you use an enumeration for a parameter type, the IntelliSense that's built into Visual Basic provides client-side programmers with a drop-down list of available choices. This makes your components much more convenient to program against.

You must declare enumerations in a public class module to publish them in the server's type library. When you create an ActiveX DLL project, your best choices for a public class module are those with an instancing setting of MultiUse or PublicNotCreatable. Enumerations declared in .BAS modules are private to the project and can't be used in public method definitions.

What's not very intuitive is that the enumerations you declare in a class module are scoped at the type library level, not at the class level. The class instancing property has no effect on an enumeration's scope. If you want to create a separate class module just to hold your enumerations, you should mark it as PublicNotCreatable. You can also simply declare enumerations in a MultiUse class that defines a component. Just realize that all the enumeration values in your project are part of one big namespace. Therefore, every enumeration value that you declare should be given a unique name.

Here's something else to think about: When a client application uses several type libraries, each of which defines its own set of enumerations, all the combined enumerations from every server are visible to the client application in a single projectwide scope. The enumerations from two servers will conflict if they have the same name. The popular convention of using server-specific prefixes was created to prevent such conflicts. The enumerations in the preceding example were defined with the prefix ds to comply with this convention. However, there's still a potential conflict, which you can resolve in the client application by preceding the name of the enumeration with the project name, like this:

 DogServer.dsBeer 

Using UDTs

Prior to Visual Basic 6, you could define only user-defined types (UDTs) that were private to a project. With Visual Basic 6, you can define COM methods using a UDT as the type for a parameter or the return value. (The equivalent construct to a UDT in C and C++ is known as a structure or a struct.)

You face a few important restrictions when you use UDTs in COM methods. First, you must mark all UDT parameters as ByRef rather than ByVal. Second, you must use a version of the universal marshaler that's recent enough to know how to convert between a UDT and a Variant. If you don't have an updated version, the universal marshaler will fail when it attempts to create a proxy or a stub for an IID that contains UDTs. Windows 2000 and Windows 98 computers have the updated version of the universal marshaler. If you're using Windows NT Server, you must install Service Pack 4 or later. If you're using Windows 95, you'll need a recent version of the DCOM upgrade.

Like enumerations, UDTs must be declared in public class modules to be published in a server's type library. Here's a simple example of a UDT definition:

 Type DogInfo     Name As String     Rank As String     SerialNumber As String End Type 

Once you define the UDT, you can use it in a method signature as a parameter or return value type, like this:

 Sub SetDogInfo(ByRef Data As DogInfo)     ' Implementation End Sub Function GetDogInfo() As DogInfo     ' Implementation End Function 

Here's an example of a client calling the SetDogInfo method and passing a UDT as a parameter:

 Dim Spot As CRetriever Set Spot = New CRetriever ' Create and fill UDT instance. Dim SpotInfo As DogInfo SpotInfo.Name = "Spot" SpotInfo.Rank = "Sergeant" SpotInfo.SerialNumber = "K9" ' Pass UDT instance as a parameter. Spot.SetDogInfo SpotInfo 

UDTs are useful in many situations. In Chapter 11, which delves into marshaling and interface design, you'll see various techniques for moving data efficiently across the network using UDTs, arrays, property bags, and even ADO recordsets. For now, it's more important that you understand the basic COM data types that you can define in a type library. So far in this chapter, you've seen that Visual Basic publishes type information for coclasses, interfaces, enumerations, and UDTs.

Remember that some clients can make use of your server's type library and others can't. If you can assume that all your component's clients are written in Visual Basic or C++, you can use all the data types that we've discussed so far in this chapter, including enumerations and UDTs. It also means that you can design your components and applications in terms of abstract and concrete classes. If you want to make use of the design features exploited by user-defined interfaces, that's fine. However, if you'd like to avoid the extra work and complexity associated with user-defined interfaces, you can program in terms of the default (and automatic) interface behind a component. It's your choice.



Programming Distributed Applications with COM+ and Microsoft Visual Basic 6.0
Programming Distributed Applications with Com and Microsoft Visual Basic 6.0 (Programming/Visual Basic)
ISBN: 1572319615
EAN: 2147483647
Year: 2000
Pages: 70
Authors: Ted Pattison

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net