July 1999

Host Your Controls in Any Container

You must account for the differences in ActiveX containers to host your VB-created controls in multiple containers.

by Raymond Cirincione

Reprinted with permission from Visual Basic Programmer's Journal, July 1999, Volume 9, Issue 7, Copyright 1999, Fawcette Technical Publications, Palo Alto, CA, USA. To subscribe, call 1-800-848-5523, 650-833-7100, visit www.vbpj.com, or visit The Development Exchange.

Visual Basic's ActiveX controls facilitate code reuse and maintainability. For example, they enable you to encapsulate a piece or pieces of functionality into a control, then reuse that code in future projects simply by dropping a control on a form in any project that can use it.

What you need: Visual Basic 5.0 or 6.0 and/or Visual Studio 6.0
A basic understanding of OLE architecture
Perhaps the best part is that you can use the controls you create in VB in environments other than VB. You can take a VB-compiled ActiveX control and use it in practically any environment (container) that supports COM, including Visual J++, Visual C++, Visual InterDev, Internet Explorer (IE), and Microsoft Office. Microsoft doesn't have a standard COM container, so you must sometimes make subtle changes to your control to make it function properly in multiple environments. You might find it odd that a component adhering to OLE standards can behave differently in different ActiveX containers, but failing to account for these differences can severely restrict your use of a control in some containers or cause it to fail completely in others.

 
Figure 1 Anyway, Anyhow, Anywhere You Choose. Click here

In this article, I'll give you all the information you need to create the next best thing to a universal container: a shell control you can adapt easily to any of the common containers (see Figure 1). The control itself doesn't do anything, but you can use it as the starting point for building controls that function properly wherever you might need to deploy them. You can find the source and a working example of this control here. Note that you can't use the control to make all container-related issues disappear; the specifics that govern a given container can affect how your control will behave within that container. This means you must understand the larger issues involved in hosting your controls in multiple environments, such as when to license a control (see Table 1). Different containers have different licensing requirements, and sometimes you must make choices that limit the functionality of your control to host it in a particular combination of environments. For example, VB caches license information for controls, but Excel and Word do not. Creating controls that work in all these containers might require compromises in how you implement your functionality.

I'll share workarounds for these and other problems you might encounter, including issues with licensing, threading, validation, and scripting support. Some of these workarounds are hard-won. Many of these container differences are not documented in relation to one another, so you can use this article's example to save yourself a considerable amount of toil and tears. Careful planning can help you avoid an incorrect implementation that makes your component behave strangely, makes it difficult to use, or renders it incompatible with a particular development or runtime environment.

License a Control
VB lets you create aggregate controls (a control composed of one or more other components) quickly and easily, simplifying the complex task of OLE programming. When you create a new project in VB, you can create a workspace for building a new component by choosing the "ActiveX Control" option. The first decision you need to make is whether you should license your component.

Licensing your component can significantly affect how it behaves in particular containers. A component operates in two distinct environments: runtime and design-time. A licensed component includes several interfaces that limit an individual's access to the component in a design-time environment. A user who lacks the proper license can use a component only in a runtime environment; he or she cannot use it at design time.

You create a licensed VB component by checking "Require License Key" from the Properties dialog, which you find in the Project menu. Selecting this option causes VB to add the necessary interfaces and creates a license file (VBL). This license file allows the Setup Wizard to deploy your component on a client machine. It also adds the proper Registry entries, enabling you to use the control in design mode.

Note that requiring a license key can impact the container or containers you want to host your component in. Development tools often cache the component's license key within a project when the appropriate licensing Registry entry is available. Unfortunately, the licensing Registry entry isn't available when you deploy the application to a client site. In this case, the application uses the cached license key to validate the component. This works fine on the desktop, but some environments provide no mechanism for caching this license information.

For example, the Internet and intranet provide no mechanism for transmitting license information securely through HTML. You can get around this limitation by using a Microsoft utility called LPK_TOOL.exe, which is available as part of the Microsoft Internet Client SDK. This utility packages license files in an encrypted file you can refer to in your HTML document. Internet Explorer can retrieve the licensed information from the LPK file (rather than the Registry) when it prepares to instantiate a licensed component:

<OBJECT CLASSID=
"clsid:5220cb21-c88d-11cf-b347-00aa00a28331">
   <PARAM 
      NAME="LPKPath"
      VALUE="LPKfilename.LPK">
</OBJECT>

 
Figure 2 Don't Drive a Control Without a License. Click here

Microsoft Office documents—including Excel and Word—do not cache licensing information for controls either. This prevents someone from using your control in a document, then transmitting the document (with your control) to other users who would also be able to use your control (see Figure 2). You need to distribute both the control and its corresponding license information if you intend to deploy a component in Microsoft Office using VBA.

These limitations mean you should probably avoid licensing your component if you intend to deploy it within a corporation (in-house), on the Internet, or on a local intranet; or if your main audience will be VBA developers using Microsoft Office. However, you should take advantage of licensing if you intend to sell a commercial product or want to restrict access to your component's design-time capabilities.

Overcome Threading Issues
Microsoft's pair of threading models, single and apartment, also complicates using a control in multiple containers. Single-threaded components execute all objects on a single thread; apartment-threaded components can execute an object on any thread at any time.

You face a significant problem when you share resources in an apartment-model threaded environment. For example, two threads attempting to use the same resource at the same time can lead to data corruption or unexpected results. The question you must answer: How does a container know whether your component is apartment-model thread safe? A component writes a value into the Registry during its class factory's UpdateRegistry call. You select a threading model in VB in the Properties dialog under the Project menu; the default threading model is Apartment Threaded.

It might appear that you could solve this problem simply by changing this value to Single Threaded, indicating your component is not apartment-model safe. However, you should always support apartment-model threading in your components if you want to support them in as many containers as possible. For example, some development containers, such as VJ++, require components to support apartment-model threading. Also, apartment-model threading allows IE to make more efficient use of ActiveX components when it spawns new browser windows.

You can get around the problem of sharing data or a unique resource between instances and threads with careful programming that uses semaphores and critical sections to prevent two threads from accessing critical data at the same time.

Validating field values can also prove tricky. For example, many user-modifiable components allow the user to validate the component's contents. This validation typically occurs when the user finishes editing one component, then moves to another. Controls you create in VB let you respond to two procedures: EnterFocus and ExitFocus. Your component should provide a way for any programmer using the component to be notified of these important events. Some development tools, such as VB, automatically provide events when a component gains or loses focus; other containers do not. You must add your own custom events to ensure your component always gives a programmer the chance to respond to this event.

You must also add prototypes for events the control fires when your component gains or loses focus. Next, add code to fire your custom events in the EnterFocus and ExitFocus procedures:

' in general declarations section
Public Event ctlGotFocus()
Public Event ctlLostFocus()

' control function
Private Sub UserControl_EnterFocus()
   RaiseEvent ctlGotFocus ()
End Sub

Private Sub UserControl_ExitFocus()
   RaiseEvent ctlLostFocus ()
End Sub

These simple steps enable you to add validation to your component, regardless of the container.

It's Safe If You Say It Is
The list of container discrepancies you must account for does not stop with licensing, threading, and validation; you must also account for container differences in marking your component as safe to use. An ActiveX component you build with VB has complete access to the entire Windows API and unlimited access to resources on the client machine. So, how does a user who downloads your control know he or she can use the component safely? You might code-sign your CAB or OCX file using your Software Publishers Credentials (SPC). Code signing enables Internet browsers to verify the publisher of the ActiveX component. You can obtain an SPC from a Certificate Authority such as VeriSign. I highly recommend you adopt this approach, but obtaining and signing your component is beyond the scope of this article. You can find more information on obtaining an SPC from VeriSign's Web site.

You can take other steps to mark your component as safe for Internet use. Using a control in an HTML or ASP page usually requires some kind of scripting language, such as VBScript, to make the control perform some useful task. For example, assume someone creates a useful component for file and directory management that exposes a method called DeleteDir. This method enables you to delete a directory on command. If an instance of this control is created within an HTML document, a malicious programmer could use VBScript code to cause irreparable harm to your client machine:

ctl.DeleteDir("\windows")

Similarly, Web authors use PARAM tags within the HTML to initialize your component when the Internet browser creates it. A component that supports directory deletion and exposes the property DirToDeleteOnStartup could also cause irreparable damage to your client upon initialization.

You can differentiate your component from one that can cause damage to a client machine by marking your component as Safe for Scripting and Safe for Initialization. Do this by adding special values to the Registry called Component Categories. A control typically makes these Registry changes during its self-registration routine. Unfortunately, VB hides the implementation of a component's self-registration routine. You cannot modify this routine, but you can use a registration entry file to add additional Registry information for your component (see Listing 1). You can also use the Package & Deployment Wizard to create a CAB file that you set explicitly as Safe for Scripting and Safe for Initialization. Note that marking your control as safe does not in fact make your control safe; it remains up to you, the programmer, to make sure your control follows the rules for these categories. If you don't make these Registry entries, IE might disable functionality or display a warning message that says your control is not safe when someone views your component in an HTML document.

Incorporate Events and Scripts
Now assume you want to build an ActiveX control that performs a lengthy operation under certain circumstances, but gives the container an opportunity to cancel the operation. You might add an event that passes by reference a parameter called Cancel. In other words, you want to allow the recipient of the event to change the value of Cancel. The control would then periodically fire this event to the container, and you could inspect the value of Cancel inside the control after the fired event returns. The control would terminate the lengthy operation if Cancel is set to True.

Unfortunately, this won't work in all environments. For example, VBScript doesn't support parameters passed ByRef for ActiveX control events. You can get around this by adding a method called CancelOperation. Examining this internal flag when the event returns can help you decide whether you should abort the action in progress. This is functional, but you can accomplish this more elegantly by passing an automation object as a parameter of the event. The automation object supports CancelOperation and other properties and methods for providing feedback on the operation in progress, such as the amount of work completed.

Creating an automation object isn't difficult. Start by adding a class module to your ActiveX control project. The VB Class Builder add-in lets you create a new module with properties and methods. Setting the Instancing property to Public Not Creatable means only your control can create an instance of this module. Next, add three mock properties to fill out the class (see Listing 2).

Instantiating and using this OLE object is straightforward:

' in general declarations section
Public Event GetUserFeedback( _
   Info As ctlProgressEvent)
' private method to perform an 
' operation
Private Sub DoWork()
   Dim Info As New ctlProgressEvent
   Dim bContinue as Boolean
   bContinue = True
   While(MoreWork() And bContinue)
      ' do some work here
      Info.CancelOperation = False
      RaiseEvent GetUserFeedback(Info)
      If (Info.CancelOperation _
         = True) Then
         bContinue = False
      End If
   Wend
End Sub

You must also account for the data type of properties and methods you expose when scripting because VBA and VBScript include only a subset of the VB language. For example, VBScript can accommodate only Variant data types. A Variant type can support a wide range of intrinsic data types, such as integers, floating points, strings, arrays, and so on. However, Microsoft Excel 97 supports only a limited range of data types for Variants, so you need to ensure that you support only a few basic types if you add any properties or methods that use a Variant. These basic types include Boolean, Currency, Date, Single, Double, Integer, Long, Object, and String.

Deploy Your Component
You must solve one final hurdle to deploy your component in multiple containers: packaging. Developing a component for use on the Internet or a local intranet requires that you consider carefully how you package your component. Using VB to create a component for use over the Internet can lead to serious bandwidth problems, including oversized components and the necessity of ensuring the client has the Microsoft VB runtime DLLs installed. Most Windows desktops already have these DLLs installed, but remember some do not.

Cabinet (CAB) files prove an effective solution for both problems. A CAB file is an archive that uses compression to reduce the size of your component. CAB files also enable you to include a setup information (INF) file that performs version checking on the client machine. Version checking ensures the client machine downloads your component only if it isn't already installed or if a newer version exists in the CAB file. A typical INF file contains the component's version number and signature; its Add.Code section provides a list of files present either within the CAB file or located at another site (see Listing 3). The INF file contains important information about each CAB file: the component's class ID, its version number, and a flag that specifies whether the component supports self-registration. Controls you create using VB always support self-registration.

An INF file also lets you refer to the VB runtime DLLs. Microsoft maintains these DLLs on its Web site, which eliminates the need for you to manage and store these files.

You now have all the information you need to host your controls in the most common Microsoft containers. You can find a compiled version of the shell control here. This shell control works in containers as diverse as VB, VC++, VJ++, IE, and Office. Note that this is overkill for most controls, however. In the real world, you'll probably want to host your controls in only two or three different environments.

One optimization technique you might employ that builds on the ideas in this article: Create custom versions of the shell control that incorporate only what it needs for the environments you intend to deploy it to. For example, you might want to use a control only in VB and Office, which means the special code required to host your control in IE is superfluous. This might require some thought and planning on your part, but you could strip out the functionality you don't need with relatively little effort. Keeping your controls as basic as your needs require can help you reduce potential development hassles down the road.


Raymond Cirincione is vice president of research and development and a product manager at ProtoView Development Corp. He has extensive knowledge of designing and building ProtoView's ActiveX product line, including the ProtoView ActiveX Component Suite and InterAct. Reach Ray by e-mail at rayc@protoview.com.