Dynamic web pages with ActiveX and VBScript



ActiveX is a lighter version of COM components since they are meant to be included in web pages to be dowloaded and run on the user's computer. Once this component has been downloaded, it will run locally every time the user accesses a page which makes use of this component.

Arguments against using...

Another good point about moving the logic into downloadable components to run on the client host is that your application can be accessed offline by just downloading your site with an offline-browser, and accessing the off-line archive via eg. C:\site\index.htm as the URL. Great if your customers experience occasional problems connecting to the Net.

Stuff that is missing in OCX as used in IE:

Finally, since the application run on the client host, data can also be located on the customer's hosts instead of your web server. Few companies feel confortable having their business data over in some database server on the Net...If your needs are small, I recommend that you take a look at SQLite, a high-performance, open-source SQL engine based on a single 200KB DLL, which can be distributed through a CAB as part of the fixed cost stuff that must be installed on all client hosts prior to using your web application.

By default, VB does not ensure binary compatibility, which is a must if you don't want to update a control's CLSID in its web page every time you recompile. You must, however, update its version number in the web page (and the INF if you package a control in a CAB.)

FYI, I took a quick look at ActiveX Documents (EXEs, but DLLs work the same, expect they run in IE's space), and witnessed frequent IE crashes.

Important: If copy/pasting samples from a web page, in case a control doesn't load, make sure you replace all spaces, which may be turned into invisible characters that prevent correct parsing of the web page by IE (been there...)

Developing an ActiveX control

Since the compile-update INF-update HTML-restart IE is too much work when you're still working on the code... make use of VB's capability to host more than one project in the IDE (File | Add new project), so you can have a regular EXE project with a single form, which will contain the ActiveX control you are working on. The IDE will recompile (reinterpret ;-)) the OCX every time you hit F5 to run its contained, ie. the EXE, since an ActiveX control just needs a container, and doesn't care whether it's the VB IDE or Internet Exploder.


Step-by-step procedure to write, compile, and upload an ActiveX control

Running an ActiveX control implies that a few components be installed once and for all before this control, such as the VB run-time, Automation, etc. As of now, just install the VB IDE on the client host where you wish to test this first sample; We'll see later how to package your application so that it installs on a bare host through a CAB archive.

For security reasons, by default, IE is set up to either prompt the user, or simply forbird downloading ActiveX controls. The solution is to add the domain or host in the list of Trusted Sites (Tools | Internet Options, Security tab), and lower the security settings for those sites by clicking on the "Custom Level..." button.

  1. Create a new ActiveX Control project, and build a new ActiveX Control with just a pushbutton called Command1 that displays a message box when clicked
  2. Make sure the version number is increased every time your recompile: Select Project | Properties, click on the Create tab, and check the "Automatic increment" option. You'll just need to update this information in the web page so that the client knows that a new version of the ActiveX component is available on the server
  3. Compile this ActiveX control
  4. Go back to the Project | Properties dialog, select the Component tab, choose "Binary compatibility", and aim at dummy.ocx. This makes sure that from now on, the control's CLSID remains identical every time you recompile the control, so you don't have to find and copy/paste the new CLSID in the web page
  5. On the web server, create a directory to store this OCX and the HTML page where it will be referenced
  6. Upload this ActiveX component in this directory
  7. In the same directory where you saved the ActiveX, create a web page eg. index.html with the following content:
    <TITLE>Testing ActiveX</TITLE>
    <OBJECT ID="UserControl11" CLASSID="CLSID:67A2A38D-3CA7-479A-9928-C4E070A691EE"   CODEBASE="dummy.ocx#version=1,0,0,3">

    <script language="vbscript">
        Call UserControl11.command1.click


    To find out what the CLSID is for the component, either search the Registry on the host where the component was compiled (look for dummy.ocx), or launch the Microsoft ActiveX Control Pad, and insert this component into the page, which will tell you its CLSID. To find out the version number of the component, either launch Windows Explorer, right-click on the .OCX file, and hit the Version tab, or look in the Project | Properties, followed by the Create tab.

    Since the CLSID remains the same every time you compile the ActiveX component (thanks to binary compatibility), the only way for the client host to know that a newer version of the component is available on the server is by checking the release number given above in the CODEBASE attribute. In other words, if you recompile and upload your ActiveX component but forget to update this information in the web page, the client host will not download the new version and continue running the previous version that is already located locally.

    Incidently, since the server does not use the Registry to locate the control, this also means that you do not have to run Windows + IIS to host a web page that links to an ActiveX control, but could just as well run Apache on a Unix server...

    Note that you could just as well write the ActiveX component to display a message box when the user clicks on the command1 button. I included this little bit of VBS just to show you how VBS and ActiveX components can work together.
  8. If you compiled this component on the same host where you wish to perform a test, make sure you uninstall the component from this host (otherwise, the new version won't be downloaded since the client host already has it installed): Open a DOS box, and run "regsrv32 c:\path\to\ocx\dummy.ocx /u" : The component is now removed from the client host. Obviously, you really should test on a bare host that never had VB-related stuff on it as this is the only way to make sure a client host downloads all the stuff it needs to run this web application
  9. Aim your browser at http://server/index.html and check that the control is correctly downloaded and run

Signing controls with Microsoft Authenticode

Starting with XPSP2, it appears that ActiveX controls will no longer be downloaded through a web page unless they've been signed with Authenticode: "You will need to apply for and receive either an Individual or Commercial Software Publisher certificate from a Certificate Authority (CA) that supports Authenticode™ technology. "Digital Certificates for Authenticode" provides more information about the certificate enrollment process for software publishers. Digital IDsSM for Software Publishers is now available from VeriSign."

Updating OCX's

Here's a way to update OCX's from your web server: 

1. Have an Setting in the registry for the control version.
2. Have a centrally located Controls.ini file.
3. Whenever the versions don't match in the controls file, copy the .OCX to the local computer.
4. Register the control.
Your Done.
Now for what you really want.
TS1 = GetSetting(App.Title, "Control Information", "IntakeManage", "-2") ' Get control info from the registry.
TS = Space(16)
RetVal = GetPrivateProfileString("Control Information", "IntakeManage", "-1", TS, 16, MPath + "Controls.ini") ' get control info from the .ini file.
TS = left(TS, RetVal)
If TS <> TS1 Then ' We need an update
If Onsite Then
On Error Resume Next
FileCopy Directory & "IntakeManage.ocx", SysDir & "IntakeManage.ocx" ' Copy the control
If Err Then
MsgBox "Unable to update Intake Control. Please exit out of any copy of Zonecare and try again."
End If
Shell "regsvr32 /s " & SysDir & "IntakeManage.ocx", vbHide ' register the control
SaveSetting App.Title, "Control Information", "IntakeManage", TS ' update the registry
End If
End If
NOTE: This code must be placed in a sub main and sub Main must be your startup. Otherwise, your main form might have one of the controls on it and the control will not get updated.

List of attributes

Here's the list of attributes that can be used in the OBJECT tag:


(CHECK) Note the INF files are read "from the bottom up" when you list the dependent files (it took me a bunch of sweat to find this out..)

Building a CAB using VB's Deployment Wizard

This is by far the easiest way to get started, as the wizard will build all the files you need to build a CAB and the HTML file where lives the OBJECT tag that points to the ActiveX control you want to distribute. Once VB5 is installed, this wizard can be found at c:\Program Files\DevStudio\VB\setupkit\kitfil32\Setupwiz.exe .

  1. Launch the wizard, and choose the control's VBP file
  2. Select "Build Internet package", and follow the instructions. Any OCX on which your control depends on that does not have a DEP file will trigger a warning, since the wizard is unable to check its own dependencies (don't ask why the wizard can't look inside the an OCX, DLL, or EXE to search for dependencies directly...)
  3. Once the wizard is done, you will be provided by an HTML file which contains an OBJECT tag referencing this ActiveX control, a CAB file which contains this OCX along with other, non-MS controls and DLLs it depends on (optional), and an INF file which lists the dependencies, and how to solve them (ie. where to download VB's run-time stuff from MS' web site, if you chose to let the user fetch those from MS' site instead of your own). You are also provided with a Support/ sub-directory, which contains a copy of each of those non-MS dependencies, the INF file, and a DDF file. Use the DDF file to build a CAB manually, using the command-line MAKECAB.EXE utility provided with VB


  1. In case your ActiveX control makes use of other ActiveX controls, those must have a .DEP file, which lists the files they themselves depend on. Use the Deployment Wizard to generate a DEP file.
  2. Those extra ActiveX controls must be registered; Copying them in the same directory where your project files live is not enough. Just like any ActiveX control, the wizard looks up a control in the Registry. Note that if another version of a given OCX is also available in WINDOWS' directory but is not the one registered, the wizard will warn you (ie., any OCX that your control depends on will be looked for in WINDOWS instead of just depending on the Registry)
  3. In case your project makes use of regular DLLs (ie. non-COM), they must be located in directories where the wizard can find them: Either in the same directory where your VBP file lives, C:\WINDOWS, \SYSTEM[32], or any directory specified in the PATH variable
  4. If your ActiveX control includes the Microsoft Calendar OCX, its DEP file is often wrong: Register=$(DLLSelfRegisterEx) and Version=
  5. Check what happens on W2K/XP when a CAB updates Windows system files while the user is not logged with administrative rights
  6. Once you have used the Wizard to build a working CAB and HTML files, you can use MAKECAB.EXE with the DDF and INF files to build a new CAB file yourself

Building a CAB file manually using MAKECAB.EXE

Once you have run VB's deployment wizard once, you are provided with an INF and DDF files, which are all you need to build a new CAB file yourself. Any time you compile a new version of your OCX, you must update its version number if the INF, and launch MAKECAB.EXE to generate a new CAB file. Editing the DDF file is only required if you wish to add a new file to the CAB; If this new file is an OCX, you are better off asking the deployment wizard to generate the INF for you, as an ActiveX control has a bunch of dependencies (in other words, edit the DDF file manually to add non-OCX stuff, eg. regular DLLs, EXEs, and non-executable files.)

  1. If you need to add a new file to the CAB, edit your DDF file
  2. Edit the INF file to update your OCX's version number (and add relevant sections if you are adding extra files)
  3. makecab /F myfile.ddf
  4. del setup.rpt

Voilà! You are now the proud owner of a new CAB :-) You are

Determining dependencies

Besides the fixed-cost components like the VB run-time or Automation, your application probably needs additional components to run. Unfortunately, I don't know of a free tool to build a list automatically, so here's how to do this:

Running EXEs out of an INF/CAB

According to a message crossposted to some microsoft.* newsgroups, it may be possible to run an EXE to install stuff through an INF/CAB. I don't know how to make sure it runs silently, and in which order (order of appearance in INF?)

Sample VB5 INF File

Here's a DUMMY.CAB which contains an bare DUMMY.OCX to trigger the download of the CAB. The CAB contains a number of components that are shared by the different web apps I wrote.

The way things I organized things on my web server:

  1. index.php contains a welcome screen, explaining to users that they need to lower security settings in IE and accept a few fixed-cost components;
  2. Next, we move to install.php which contains the DUMMY.OCX section so IE downloads the corresponding CAB file whose INF is shown below.
  3. Once those fixed-cost components are installed (and won't need to be downloaded again until you make a change to the OCX/CAB), we move on to menu.php, which contains links to the different applications, each consisting in a single OCX, since all the other components have already been installed through DUMMY.CAB.
;INF file located in DUMMY.CAB linked to DUMMY.OCX
;DestDir can be 10 for \Windows, 11 for Windows\System(32) or left blank to mean Occache
;Common Controls 5.0 SP2 - Includes advanced controls like progress bar, etc.
;Common Controls2 6.0 SP4 - Contains DatePicker etc.
;Localized (French) file
;VB5-compatible version of SQLite
;Localized (French) file
;ComponentOne's VSFlexGrid
;VB fixed-cost stuff
;Localized (French) file
;Common Controls 6.0SP4 - Includes advanced controls like progress bar, image combo, etc.
;Common Dialogs
;Localized (French) file
;Localized (French) file

Sample VB6 INF file

This is the INF file generated by VB6's add-in utility "Packaging and Deployment Utility" to distribute a single, no thrill ActiveX control (ie. not extended controls, just basics like a label and a push-button):

;DestDir can be either 10 for \Windows, 11 for Windows\System(32) or empty for Occache
[Setup Hooks]
HKLM,"SOFTWARE\Classes\CLSID\{F4C9FFFC-7C1F-4C22-9507-CD8F7A160952}\Implemented Categories\{7DD95802-9882-11CF-9FA9-00AA006C42C4}"
HKLM,"SOFTWARE\Classes\CLSID\{F4C9FFFC-7C1F-4C22-9507-CD8F7A160952}\Implemented Categories\{7DD95801-9882-11CF-9FA9-00AA006C42C4}"
HKCR,"Licenses",,,"Licensing: Copying the keys may be a violation of established copyrights."

My application requires more files than just one ActiveX control

VB5 and 6 provide a setup wizard to build a CAB file, which is the equivalent of a ZIP file with the addition of an INF file to provide extra information on each file that make up this packaged file. For VB5, this wizard is located on the CD at  VB\SETUPKIT\KITFIL32\SETUPWIZ.EXE. Once you have this wizard once, a DDF file is generated that you can modify as you wish and then use as input to the Makecab.exe utility to regenerate a CAB file manually.

Here's how to include JPG files in the CAB file, and have those files downloaded into the user's Windows directory:

;10 = Drive:\WINDOWS (or WINNT), 11 = WINDOWS\SYSTEM, empty = WINDOWS\Downloaded Program Files\

For your information, you cannot use a dummy CLSID to use a CAB to distribute non-OCX files; Doing so triggers a download of the stuff listed in the INF every time the page is viewed. A work-around is to build a small OCX that does nothing, and include it in the HTML page, the INF, and the CAB. Needless to say, should you make any change to the contents of the CAB file (ie. add some DLL), you must update the dummy OCX in order for IE to trigger a new download.

Before performing a new test, remember to remove the OCX in the Registry, and delete all the downloaded files in the Windows directory that make up this CAB file. Better yet, use a bare test host, ghost it, and build a bunch of images that you can reinstall in a couple of minutes using cloning software like Ghost, DriveImage, et al.

More infos:

INF sections for common dependencies

Here's the relevant sections that must be located in an INF so that IE knows what to do if it encounters an ActiveX control that depends on other controls which are either not available on the host, or with an older version number. Note that you need more sections if you use localized controls (eg. the Common Dialog widget requires a couple of extra sections for the language you used.)

Microsoft Windows Common Control 5.0 SP2

Progress bar, etc.


Microsoft Windows Common Control 6.0 SP4

Same as 5.0 SP2, but with a couple of additional controls like Image Combo


Microsoft Windows Common Control-2 6.0 SP4

DatePicker, etc.


Microsoft Common Dialog Control 6.0 SP3

Common dialog boxes like OpenFile, etc.


Chains of events

Here's the order in which events are triggered when loading an ActiveX control in a web page:

  1. Initialize
  2. InitProperties (which can be changed through the PARAM tag in the web page). ReadProperty is called instead if some elements were stocked using the PropertyBag object?
  3. Resize (for native controls)/Paint (for user-drawn controls)
  4. Show ?
  5. Other events...
  6. WriteProperty if the PropertyChanged property is set?
  7. Terminate

List of events


Se produit lorsque l’utilisateur du contrôle appuie sur l’une des touches d’accès rapide du contrôle ou lorsque la touche ENTRÉE est actionnée alors que le développeur a affecté à la propriété Default la valeur True, ou, lorsque la touche ÉCHAP est actionnée alors que le développeur a affecté à la propriété Cancel la valeur True. Les propriétés Default et Cancel sont activées par le concepteur du contrôle en affectant à la propriété DefaultCancel la valeur True.


Se produit lorsque la valeur d’une propriété ambiante change.


Se produit lorsque le conteneur a accompli une requête de lecture asynchrone.




Se produit à la fin d'une opération glisser-déplacer soit parce que l'utilisateur a fait glisser un contrôle sur un objet et a relâché le bouton de la souris, soit parce que la valeur 2 (Drop) a été affectée à l'argument action de la méthode Drag utilisée.


Se produit lorsqu'une opération glisser-déplacer est en cours. Vous pouvez utiliser cet événement pour contrôler le pointeur de la souris lorsqu'il aborde ou quitte une cible valide ou s'immobilise dessus. La position du pointeur de la souris détermine l'objet cible qui reçoit l'événement.


Se produit lorsque le focus atteint l’objet. Le focus peut être reçu par l’objet lui-même ou par un contrôle constitutif.


Se produit lorsque le focus quitte l’objet sur lequel il était. Le focus peut être perdu par l’objet lui-même ou par un contrôle constitutif.


Se produit dans l’objet ou dans le contrôle constitutif lorsque le focus y parvient. L’objet défini par l'espace réservé object ne peut obtenir le focus que si la propriété CanGetFocus a la valeur True et qu’aucun des contrôles constitutifs ne peut recevoir le focus. L’événement EnterFocus survient avant l’événement GotFocus.


Se produit lorsque la propriété Visible de l’objet prend la valeur False.


Se produit lorsqu'une application crée une instance d'un objet Form, MDIForm, UserControl, PropertyPage, ou d'une classe. Faites appel à cet événement pour initialiser les données utilisées par l'instance de l'objet Form ou MDIForm, ou de la classe. Dans le cas de ces objets, l'événement Initialize se produit avant l'événement Load (ATTENTION : Load() N'EXISTE PAS POUR DES CONTROLES!)


Se produit lorsqu’une nouvelle instance d’un objet est créée.

Cet événement permet au concepteur de l’objet d’initialiser une nouvelle instance de cet objet. Cet événement n’intervient qu’en cas de création d’une nouvelle instance d’un objet. Ainsi, le concepteur de l’objet peut faire la distinction entre la création d’une nouvelle instance de l’objet et le chargement d’une instance existante de ce même objet.

En plaçant un code initialisant de nouvelles instances d’un objet dans l’événement InitProperties plutôt que dans l’événement Initialize, le développeur peut éviter les situations où le fait de charger des données dans une instance existante de l’objet, par le biais d’un événement ReadProperties, annule l’initialisation de ce même objet.




Se produit dans l’objet ou dans le contrôle constitutif lorsque le focus le quitte. L’événement LostFocus survient avant l’événement ExitFocus.











L'événement Paint est invoqué lorsque la méthode Refresh est utilisée. Si la propriété AutoRedraw a la valeur True, les données sont automatiquement réaffichées, de sorte que l'événement Paint n'est pas nécessaire.

Le fait d'utiliser la méthode Refresh dans une procédure d'événement Resize provoque le réaffichage complet de l'objet chaque fois que l'utilisateur redimensionne la feuille.


Se produit lors du chargement d’une instance existante d’un objet dont l’état a été enregistré.

Lorsque cet événement intervient, le concepteur de l’objet peut charger l’état enregistré à partir de l’argument pb, en invoquant la méthode ReadProperty de l’objet PropertyBag pour chaque valeur devant être chargée. Cet événement intervient après l’événement Initialize.

Prévoyez toujours un mécanisme de détection d’erreurs lorsque vous manipulez l’événement ReadProperties. Vous protégerez ainsi le contrôle contre les valeurs de propriétés invalides susceptibles d’avoir été entrées par les utilisateurs qui auront modifié le fichier contenant les données enregistrées avec des éditeurs de texte. Veillez cependant à ne pas déclencher une erreur dans un événement, ce qui serait fatal au conteneur. Un mécanisme de détection d’erreurs intégré à la procédure de l’événement ReadProperties ne doit donc en aucun cas générer d’erreurs.


Si vous souhaitez que la taille des graphiques reste proportionnelle à celle de la feuille après son redimensionnement, invoquez l'événement Paint en utilisant la méthode Refresh dans une procédure d'événement Resize.

Lorsque la propriété AutoRedraw a la valeur False et que la feuille est redimensionnée, Visual Basic appelle également les événements connexes Resize et Paint, dans cet ordre. Lorsque vous associez des procédures à ces événements connexes, veillez à ce que leurs actions ne soient pas en conflit.

Lorsque la propriété SizeMode d'un contrôle conteneur OLE a la valeur 2 (Autosize), le contrôle est automatiquement redimensionné selon la taille d'affichage de l'objet qu'il contient. Si cette taille change, le contrôle est automatiquement ajusté à la taille de l'objet. Dans ce cas, l'événement Resize est invoqué pour l'objet avant que le contrôle conteneur OLE soit redimensionné. Les arguments height et width indiquent la taille optimale d'affichage de l'objet (déterminée par l'application qui a servi à le créer). Vous pouvez affecter une taille différente à l'objet en modifiant les valeurs des arguments height et width de l'événement Resize.


Se produit lorsque la propriété Visible de l’objet prend la valeur True. Si le contrôle est en cours d’affichage dans un explorateur Internet, un événement Show se déclenche si l’utilisateur revient à la page contenant le contrôle.


Se produit lorsque toutes les références à une instance d'un objet Form, MDIForm, UserControl, PropertyPage ou d'une classe sont supprimées de la mémoire soit parce que la valeur Nothing a été affectée à toutes les variables faisant référence à l'objet, soit parce que la dernière référence à l'objet est hors de portée.

Pour tous les objets à l'exception des classes, l'événement Terminate se produit après l'événement Unload.

L'événement Terminate n'est pas déclenché si les instances de la feuille ou de la classe ont été supprimées de la mémoire en raison d'un arrêt anormal de l'application. Par exemple, si votre application invoque l'instruction End avant d'avoir supprimé de la mémoire toutes les instances existantes de la classe ou de la feuille, l'événement Terminate n'est pas déclenché pour la feuille ou la classe considérée.


Se produit lorsqu’une instance d’un objet doit être enregistrée. Cet événement signale à l’objet que son état doit être enregistré de manière à pouvoir être restauré ultérieurement. Dans la plupart des cas, l’état de l’objet est uniquement constitué de valeurs de propriétés.

Le concepteur de l’objet défini par l'espace réservé object peut faire en sorte que celui-ci enregistre son état à l’occasion de l’événement WriteProperties, en invoquant la méthode WriteProperty de l’objet PropertyBag pour chaque valeur devant être enregistrée.

Note   Le groupe de propriétés pb peut être différent de l’objet pb communiqué au dernier événement ReadProperties survenu.

L’événement WriteProperties peut intervenir plusieurs fois au cours de l’existence d’une instance de l’objet défini par l'espace réservé object.

Using the Property Bag

<OBJECT classid='clsid:14A2B8E1-EF77-4C43-9756-BA8F3950883D' STANDBY='Loading....'>
<PARAM NAME='MyHello' VALUE="Hello">

Public Sub UserControl_Initialize()
End Sub

Public Property Get MyHello() As String
MyHello = lblHello.Caption
End Property

Public Property Let MyHello(ByVal New_MyHello As String)
lblHello.Caption() = New_MyHello
PropertyChanged "MyHello"
End Property

'Load property values from storage
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)

lblHello.Caption = PropBag.ReadProperty("MyHello", "Nothing Here")
End Sub

'Write property values to storage
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)

Call PropBag.WriteProperty("MyHello", lblHello.Caption, "Nothing Here")
End Sub


List of changes

This is the changes made to a W2K host after adding an entry in IE's list of Trusted Sites, and having the CAB archive download and install the prerequired VB stuff through the embedded dummy.inf file before running an ActiveX control (Projet1.ocx). Stuff ADDED or UPDATED. Note that Windows at least since 98 comes with MSVBVM5 (2K and XP also have MSVBVM6) and ASYCFILT, but those files will be upgraded if newer versions are available on Microsoft's site (if you write your INF to tell IE to fetch those from Microsof's site instead of your web server.)


Downloaded Program Files




Security-related sections in Internet Explorer are confusing:

Security tab:

Content tab:

This is where you manage certificates used to sign ActiveX controls.

More infos in Chapter 5 - Understanding Authentication and Security.

Digital Signing for ActiveX Components


Licensing Issues for Controls

The License Package Authoring Tool won't create an entry in the .lpk file for your ActiveX control unless you check the Required License Key checkbox in the Project Options dialog of your control project.

The license file is an .lpk file you associate with one or more HTML pages. It contains license strings for each ActiveX control on the pages to which it refers. You use the License Package Authoring Tool to create an appropriate license file for their pages. This tool, which is available in the Tools directory, presents you with a list of controls you can include in you license file.

You must embed an object called the license manager in the Web page from which a user will download your ActiveX controls. The license manager uses an OBJECT tag to reference the control's .lpk file. If you use the Package and Deployment Wizard to create your download, it produces the license manager and inserts it in the file for you.

When you add licensing support to your control component, a license key is compiled into it. This key covers all the controls in the component. Running your Setup program transfers the license key to another computer's registry, allowing your controls to be used for development. Simply copying your .ocx file to another computer and registering it does not transfer the license key, so the controls cannot be used.

When you make the .ocx file, Visual Basic will create a .vbl file containing the registry key for licensing your control component. When you use the Package and Deployment wizard to create a setup for your .ocx, the .vbl file is automatically included in the setup procedure.

The license file is an .lpk file you associate with one or more HTML pages. It contains license strings for each ActiveX control on the pages to which it refers. You use the License Package Authoring Tool to create an appropriate license file for their pages. This tool, which is available in the Tools directory, presents you with a list of controls you can include in you license file.

<!-- If any of the controls on this page require licensing, you must create a license package file. Run LPK_TOOL.EXE in the Tools directory to create the required .lpk file. -->
<OBJECT CLASSID="clsid:5220cb21-c88d-11cf-b347-00aa00a28331">
    <PARAM NAME="LPKPath" VALUE="LPKfilename.LPK">

Note: the CLASSID of the object specified for referencing the LPK file is for the Microsoft License Manager component and not the CLSID of the components that you are licensing!

Licensed controls can be used on World Wide Web pages, in conjunction with browsers that support control licensing. Both the control component and the license key must be available to be downloaded to the computer of the person accessing a Web page.

The downloaded license key is not added to the registry. Instead, browser asks the control to create a run-time instance of itself, and passes it the downloaded license key.

The owner of the Web server that uses your control must have purchased and installed your control, just as a developer would, in order to supply both control and license.

If the license is not available, control creation will fail, and the browser will receive a standard control creation error. Whether the browser passes this message along to the person accessing the Web page, or simply ignores it, depends on the person who developed the browser.

Things to watch out for

Private Sub UserControl_Initialize()
    Label1.Caption = App.Major & "." & App.Minor & "." & App.Revision
End Sub


If an OCX doesn't load, first try the usual suspects:

  1. Check that the host has the VB runtime that matches the VB compiler you used to build the OCX (ie. if you used VB5, make sure the host has the VB5 runtime instead)
  2. Check that it also has any dependencies like localized VB stuff (eg. VB5FR.DLL), components and references that your control uses (eg. MSCOMCTL.OCX, etc.), any custom dependencies (eg. if you use some database engine like Access, this will have to be installed prior to trying to load your OCX), etc.
  3. Check that the server on which the web page is located is listed in the Trusted Sites section of IE, and that IE lets the user download and run ActiveX controls from those sites
  4. Check that the OBJECT section is correct:
  5. Make sure IE refreshes the page, instead of reading it from the cache via Tools | Internet Options, or add the following tag in the header in each HTML page to force IE to fetch the page from the server every time:

    <meta http-equiv="Pragma" content="no-cache">
  6. Check that the object runs OK when embedded in an EXE, and make sure you have error handlers in all the routines: Uncaught errors when the control is embedded in a web page will remain silent, and you'll be staring at the familiar, grey, empty square :-)

If you're still stuck with a silent grey little square, here are things to try:


What is the relation between the four-digit version number as generated by VB when compiling an ActiveX control, and the two-digit number that shows up in that control's record in the Registry?

No relationship. The two-digit number reflects changes in the control's TypeLib while maintaining binary compatibility. It's COM's way to keep more than one interface in a given control. The alternative is to break binary compatibility, so that older apps use the original CLSID to access the control's older interface, while newer apps use the newer CLSID.

Some ActiveX controls from Microsoft have more than one Version entries for a given control, such as 1.0 and 1.2. Why is that?

If there's more than one version registered, it usually means that someone forgot to set, or forgot how to manage Binary Compatibility settings.

What are TypeLibs as opposed to CLSIDs?

A TypeLib defines the interface of the object. Adding a new interface increments the TypeLib version. A given OCX is listed under two different GUIDs (under HKEY_CLASSES_ROOT\CLSID\ and HKEY_CLASSES_ROOT\TypeLib\). More information on this issue in this Binary Compatibility primer.

Here are some apps that let you view informations on a COM control:

Is it possible to have two versions of a control in different directories, and have them both registered and available for applications that expect either of the two versions?

Suppose more than one version of a control is registered, how can I which versions are installed, and load a given version?

Reading the control's record in the Registry is at least one possible way, although there might be a COM method to achieve the same end. In the IDE, use the Reference > Components dialog to list the different interfaces provided by a control.

Programmatically, it seems like it's not possible to select a given version. The only way is to unregister the one that you're not interested in and register the other. The app will bomb if an older version is loaded.

Handling Err 429

By Iouri Boutchkine
When working on a large VB application that uses hundreds of COM objects, the "429 can't create object" error doesn't give you much help in determining which object could not be created.  You can get around this limitation by writing a function to wrap the VB runtime CreateObject  function:
Public Function CreateObject(sProgID as string) as object
    On Error Goto CreateErr
    ' Call the VB runtime CreateObject function
    Set CreateObject = VBA.CreateObject(sProgId)
    Exit Function
    ' return the error with the name of the object that could not be created
    Err.Raise Err.Number, _
    "CreateObject Wrapper", Err.Description & ": '" & sProgID & "'"
End Function
With this wrapper function, you get the 429 error and the name of the object that could not be
This problem happens when you use an ActiveX control in your program and the ActiveX control is not
registered in the registry. If it works on the Development PC, it must be an incompatibillity between your
control and the same control installed in the user PC. To fix this check which ActiveX controls you need in
order to run your program and make sure all of them are placed in the SYSTEM folder. You should know which  control is the one that's giving you so much trouble (it must be in the Form that crashes your program) so  you'll have to manually register it. To do it you have two basic choices: Do it through Windows Explorer or  through a DOS Prompt Window. If you are using the Explorer just drag and drop your
control into REGSVR32.EXE (Both must be in the SYSTEM folder) or  in the Dos Prompt window be sure you are in the SYSTEM folder  and tye REGSVR32 [YourControlName].OCX After doing this you  should see a message box saying that the registration process  succeeded. If it fails, then you will have to dig inside the registry and find all the keys that references the control to delete them. Then you'll have to repeat the registration process.
Error 429 means that there is a component that isn't correctly registered. The PDW is supposed to take care of this, but sometimes it forgets. I doubt its an ADO component, just because since it is referenced in different ways so the PDW couldn't overlook it. Does your project have any custom controls or DLLs that were written by you or your company. These it may not pick up and register properly. If you find that this problem is only occurring on one machine, see if you can manually register the suspected controls by going  to the start-run menu and typing "regsvr32 &lt;dll or ocx path and name&gt;". if it still doesn't work - look into  rebuilding the setup package with the PDW and examine every option and/or another setup packager  (Wise or Install Shield).
Here's another clue: You are refrencing an ado library that is the latest and geratest on your machine but when you try  to package the exe PDW by dafault picks up from its own repository of redistribution components which  normally has a version which is older. So what happens the PDW picks up the ADO library all right automatically based on the Reference setting in  your project but it picks up the older version. If this is the case then For this replace the Mdac_typ.exe in the
C:\Program Files\Microsoft Visual Studio\VB98\Wizards\PDWizard\Redist with the latest Mdac_typ.exe
'check this Goto Project|References from the vb menu. Look for a ActiveX control that has MISSING next to it.  If you see a missing that's the problem. It can't find it. Sometimes it is a activeX version problem, you may  just need to pick a different version of the missing ActiveX control. Ot you may need to somehow install  the missing control or not use it. As for where the debugger shows the problem, it's a red herring, just look  for the MISSING control in references.

To read:

How to make a web page available offline?

(CHECK) For users connecting in dial-up, once they have viewed the page that contains the ActiveX control, they can have this page saved offline, and work with it without having to connect each time. Just save the URL to Favorites, right-click on this item in the Favorites, and set it to offline. You can update it by clicking on Tools | Synchronize.

How to extract the CLSID from an OCX?

This routine uses Eduardo Morcillos' OleLib.Tlb which must be added as a reference in the project). The OleLib.Tlb itself doesn't need to be included in your setup.

Private Function GetClsID(sFile$, sClass$) As String
    Dim i&, sName$, Obj As Object, pAttr&, S As String * 64
    Dim TLI As ITypeLib, TI As ITypeInfo, TA As TYPEATTR
    On Error Resume Next
    Set TLI = LoadTypeLibEx(sFile, REGKIND_NONE)
    If TLI Is Nothing Then Err.Clear: Exit Function
    GetClsID = Space(39)
    For i = 0 To TLI.GetTypeInfoCount - 1
        If TLI.GetTypeInfoType(i) <> TKIND_COCLASS Then GoTo nxt
        Set TI = TLI.GetTypeInfo(i)
        TI.GetDocumentation DISPID_UNKNOWN, sName, "", 0, ""
        If UCase(sName) <> UCase(sClass) Then GoTo nxt
        pAttr = TI.GetTypeAttr
        MoveMemory TA, ByVal pAttr, Len(TA)
        TI.ReleaseTypeAttr pAttr
        If TA.wTypeFlags Then
            StringFromGUID2 TA.iid, S, Len(S)
            GetClsID = Left(S, InStr(S, Chr(0)) - 1)
            Exit For
        End If
    nxt: Next i
End Function

How to show an constituent form, wait for it to close, read how it went, and act on this?

Since you can include forms in an ActiveX control, here's how to show a logon form, wait for the user to select a push button OK/Cancel, read a property from the form to know whether the password was correct, and enable a group of pushbuttons on the ActiveX control itself:

Add this to the ActiveX control:

Private Sub UserControl_Show()
    frmLogin.Show vbModal
    If frmLogin.ExitOK Then
        Dim iCounter As Integer
        For iCounter = 0 To 5
            Command1(iCounter).Enabled = True
        Next iCounter
        MsgBox "Wrong password. Hit F5 and try again", vbExclamation, "Wrong password"
    End If
End Sub

Add a standard Logon form (Add form...), and add this code to the form:

Private m_ExitOK As Boolean
Private Sub cmdCancel_Click()
    m_blnSuccess = False
    Unload Me
End Sub
Private Sub cmdOK_Click()
    If txtPassword = "zorro" Then
        m_blnSuccess = True
        m_ExitOK = True
        m_blnSuccess = False
        m_ExitOK = False
    End If
    Unload Me
End Sub
Public Property Get ExitOK() As Variant
    ExitOK = m_ExitOK
End Property

After accessing my control in a browser, I can no longer compile it

If you are using AvantBrowser, by default, it remains in memory (icon in the taskbar) even if you closed its visible application window, and this includes ActiveX controls that were loaded through a web page you visited. Use DLL Demon to confirm that your control is still loaded, making it impossible for VB to compile a new version ("Access denied"). Close AvantBrowser's icon in the taskbar, and try again.

How to check that controls and other stuff is installed correctly?

Considering the number of changes made to a host to run an ActiveX control (VB run-time, etc.), how could I check that things install properly, especially on hosts connected to the Net via modem (hitting the ESC key or IE crashing during the 3-4 minutes it takes to download the whole shebbang)? Can a VBScript section do this?

Why is the CLSID different on the server and on the client host?

On the server, the web page contains CLASSID="CLSID:CE1A6A5A-4691-4977-8DCE-7192DA2CD80A". Once I hit the page and accepted the ActiveX, it is located in the registry under 8E2A8A55-F701-41EF-A529-BE296A9D8D41 :-)

Did I forget to hit F5 to refresh the Registry?

"Update ActiveX controls" in the Project Properties?

What does this option mean in General tab?

Can I add forms to a control?

Yes, but only modal forms are allowed (you'll get an error message when using the control in IE anyway): Form1.Show vbModal .

How to register/unregister a bunch of ActiveX controls on a host?

for %f in (*.ocx *.dll) do regsvr32 /s "%f"


for /r %i in (*.ocx) do regsvr32 /s "%i"

In Internet Explorer, why can't I set the default security environment to "Internet" instead of "Local Intranet"?

Those are profiles used depending on the URL. Any local web site will be handled based on the settings specified for the "Local Intranet" section; Any Internet site will be handled by the settings in the ad hoc section. To lower security settings for a given Internet web server, use the Trusted Sites section.

How do I get rid of all the dialogs?

Note: For security reasons, you should lower security requirements only for sites that you added in the Trusted Sites section.

Click on "Custom level...", set all items in the ActiveX section to "Enable"; Scroll to the Scripting section, and set Active Scripting to "Enable".)

Where is an ActiveX saved once it's been downloaded?

Unless otherwise specified in the INF file that is embedded in a CAB archive, files are downloaded into C:\WINNT\Downloaded Program Files\

An ActiveX control fails installing with error 80040154 "Class not registered"

Make sure that your INF file downloads VB binaries with the same version that you used to build your ActiveX controls, ie. if your ActiveX control was built with VB5, linking to VB6 stuff in your INF won't do you much good...

I want to know what changes are made to a host after downloading this ActiveX stuff

On a brand new host...

  1. Install a utility to take a snapshot of the system (eg. InCtrl, ConfigSafe, or Arkosoft SystemSnapshot); MS' sysdiff utility stops dead when it encounters any open file) before any change is made to it
  2. Use your browser to connect to the web server, and accept the VB fixed-cost stuff from Microsoft (ie. VB run-time, Automation, etc.) and your ActiveX control(s). Make sure the whole thing works as planned
  3. Take a new snapshot, and check what changes have been made

What tools other than VB can I use to build ActiveX controls?

Can I run ActiveX controls in other browsers?

A plug-in is available for Netscape (and hence, should work also in Opera) from NCompass (bought by Microsoft).

What is an OCA file?

An .OCA file is a binary file that functions as both an extended type library file and a cache for the custom control file (What Is an OCA file?)

You can delete oca files at will.. They'll be recreated on demand. If you delete either oca's or tlb's, you should do the RegClean.. there are utilities available for managing .tlb's.. there's at least one available at VB Accelerator, RegClean (ZDNet). Note that if you run RegClean a second time after applying its suggested changes, it will often find additional entries that are no longer needed.  I've never had to do more than two, but it should eventually come up empty-handed

Two different version numbers to an OCX?

Note that there are two versions in an OCX, the version resource and the typelib version. The version resource is what you see in windows, the typelib version is what you'll see in the TypeLib key in the registry. You don't get direct control over this second version number from the VBP.

However, you can use the 'PowerVB Binary Compatibility Editor' add-in that comes with the CD Updates to "Advanced Visual Basic 6" to accomplish this very easily. Of course, this type of edit can have side effects, so you should be aware of why typelib versions are incremented, along with being aware of all previously shipped versions of the control, before making this type of modification.

Can I do without an OCX and hit the Win API instead?

In many cases (not all by any means), OCXs are merely wrappers for the functionality that exists in Windows via the API.  This is so that VB programmers not familiar or comfortable with the API can still easily incorporate that functionality in their programs.  In the case of the Common  Dialog control, if you use the API functions GetOpenFileName, GetSaveFileName, etc., you aren't using comdlg32.ocx, which means you don't need to distribute it either.

What's a property bag?

Can I register/unregister an ActiveX through a right-click in Windows Explorer?

Tip given here:

"Content Type"="application/x-msdownload"
@="Application Extension"
@="regsvr32.exe \"%1\""
@="regsvr32.exe /u \"%1\""
@="regsvr32.exe \"%1\""
@="regsvr32.exe /u \"%1\""

Registering an OCX through code

Your DLLs and OCXs have a built in DllRegisterServer function so you can register them thru code as follows:

Declare Function RegMyDll Lib "MyDll.dll" Alias "DllRegisterServer"() as Long
Dim lResult as Long
lResult = RegMyDll()

To find if an Active X is registered you must look up the Windows registry. The easiest way is to search for a file name: MyOCX.DLL. If it is registred the reference to it will be in the registry under HKEY_CLASSES_ROOT\TypeLib\{ITS GUID} . This is non-trivial. Different types of DLLs register somewhat differently, and the exact entries created are based in part on the OS version.

Loading an OCX through code

Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal _
  lpLibFileName As String) As Long
Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As
Long) As Long
Private Sub Form_Click()
  If IsDLLAvailable("SHDOCVW.dll") Then
      MsgBox "OK"
      ' MsgBox ApiErrorText(Err.LastDllError)
      ' for ApiErrorText, see:
  End If
End Sub
Function IsDLLAvailable(ByVal DllFilename As String) As Boolean
  Dim hModule As Long
   ' attempt to load the module
  hModule = LoadLibrary(DllFilename)
  If hModule > 32 Then
      FreeLibrary hModule ' decrement the DLL usage counter
      IsDLLAvailable = True
  End If
End Function

Checking if an OCX is available

If you know the OCX's CLSID, you can use this code:

Public Type CLSID       '// This is the same as a GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type
'// Possible errors
Public Const E_INVALIDARG                     As Long = &H80070057
Public Const E_UNEXPECTED                     As Long = &H8000FFFF
Public Const CO_E_CLASSSTRING                 As Long = &H800401F3
Public Const E_OUTOFMEMORY                    As Long = &H8007000E
Public Const REGDB_E_WRITEREGDB               As Long = &H80040151
Public Declare Function CLSIDFromProgID Lib "OLE32" (ByVal lpszProgID As  String, pclsid As CLSID) As Long
Public Function IsProgIDRegistered(ByVal strProgID As String) As Boolean
    '// a ProgID goes something like this: "MSComctlLib.Slider". Passing a
    '// bogus value or a non-registered class will return FALSE
    Dim pclsid As CLSID
    Dim hResult As Long
    On Error Resume Next
    hResult = CLSIDFromProgID(StrConv(strProgID, vbUnicode), pclsid)
    IsProgIDRegistered = (hResult = 0)
End Function

Instead of peeking in the Registry, we'll try to load the OCX, and catch an error if it's not available:

Function TestReg (ByVal sProgID As String) As Boolean
  On Error GoTo Trap
  Dim oMyObject As Object
  Set oMyObject = CreateObject(sProgID)
  TestReg = True
End Function

Alternatives to ActiveX

Some alternatives for richer web clients :



<script language="vbscript">
        Dim oShell
        Set oShell = CreateObject("Wscript.shell")
        oShell.RegDelete "HKEY_CLASSES_ROOT\CLSID\{67A2A38D-3CA7-479A-9928-C4E070A691EE}"

I think you need to install the WSH tool to access this object.

Temp stuff

Apparently, it is possible to use a control's ProgID instead of its CLSID ("[...] You need to specify the ProgID in the page to instantiate a control. The ProgID or CLSID of a control may be retrieved manually from the system registry." here)

Dim obExcelApp as Object

Set obExcelApp = CreateObject("Excel.Application")

HOW TO: Use Licensed ActiveX Controls in Internet Explorer

Here's some VB code (how to access the registry in VBScript?) to check that a control was correctly installed:

Of course, you'd have to create the control, but you can use a usercontrol to check the registry:
Const HKEY_CLASSES_ROOT = &H80000000
Private Declare Function RegOpenKey Lib "advapi32.dll" Alias "RegOpenKeyA" (ByVal hKey As Long, ByVal lpSubKey As String, phkResult As Long) As Long
Private Declare Function GetSystemDirectory Lib "kernel32" Alias "GetSystemDirectoryA" (ByVal lpBuffer As String, ByVal nSize As Long) As Long
Dim MySource As String
Dim MyDest As String
Const OF_CREATE = &H1000
Const OF_READ = &H0
Private Type OFSTRUCT
        cBytes As Byte
        fFixedDisk As Byte
        nErrCode As Integer
        Reserved1 As Integer
        Reserved2 As Integer
        szPathName(OFS_MAXPATHNAME) As Byte
End Type
Private Declare Sub LZClose Lib "lz32.dll" (ByVal hfFile As Long)
Private Declare Function LZCopy Lib "lz32.dll" (ByVal hfSource As Long, ByVal hfDest As Long) As Long
Private Declare Function LZOpenFile Lib "lz32.dll" Alias "LZOpenFileA" (ByVal lpszFile As String, lpOf As OFSTRUCT, ByVal style As Long) As Long
Private Sub UserControl_Initialize()
        Dim iret as long
        Dim lHandle as Long
        iret = RegOpenKey(HKEY_CLASSES_ROOT, "MSWinsock.Winsock", lHandle)
        If lHandle = 0 Then
                UserControl.AsyncRead HTTP://www.yoursite.com/MSWinsock.ocx,vbasyncreadtypeFile
        End If
        'Sub inside a sub???    
        Private Sub Timer1_Timer()
                Static C
            C = C + 1
            If C > 1 Then
            Timer1.Interval = 0
                    Dim hsource As Long
                    Dim hdest As Long
                    Dim iret As Long
                    Dim OpenStruct As OFSTRUCT
                    Dim sBuffer as String * 255
                    Dim lbuffer as Long
                    Dim Pos as long
                    Dim iret1 as long
                    LBuffer = Len(sBuffer)
                    Iret1=GetSystemDirectory(sBuffer, lBuffer)
                    Pos = instr(sBuffer,  CHR$(0))
                    MyDest = Left$(sBuffer, pos-1)
                    MyDest =  MyDest & "\MSWinsock.ocx"
                    hsource = LZOpenFile(MySource, OpenStruct, OF_READ)
                    hdest = LZOpenFile(MyDest, OpenStruct, OF_CREATE)
                    iret = LZCopy(hsource, hdest)
                    If iret = -1 Then
                            MsgBox "File transfer failed"
                            MsgBox "Transfer successful. " & Format$(iret, "###,###,###,###") & "bytes were transfered."
                    End If
                    LZClose hdest
                    LZClose hsource
            End If
            Dim X
            X=Shell("Regsvr32 " & MyDest, vbhide)
        End Sub
        Private Sub UserControl_AsyncReadComplete(AsyncProp As AsyncProperty)
            MySource = AsyncProp.Value
            Timer1.Interval = 1000
        End Sub
End Sub

Private Sub UserControl_Show()

    If Ambient.UserMode = True Then

        MsgBox "Run-time"


        MsgBox "Design Time"

    End If

End Sub


Private Sub UserControl_Initialize()

    With UserControl

    .picBOX.Move 0, 0, .ScaleWidth, .ScaleHeight

    End With

End Sub


Private m_privateResize As Boolean



Event Click()

Event DblClick()

Event MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)

Event MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)

Event MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)

Event Resize()


Private Sub picBOX_Click()

    RaiseEvent Click

End Sub



Public Property Let BackColor(ByVal New_BackColor As OLE_COLOR)

    picBOX.BackColor() = New_BackColor

    PropertyChanged "BackColor"

End Property


Private Sub UserControl_ReadProperties(PropBag As PropertyBag)

    picBOX.Appearance = PropBag.ReadProperty("Appearance", 1)


Private Sub UserControl_WriteProperties(PropBag As PropertyBag)

    Call PropBag.WriteProperty("Appearance", picBOX.Appearance, 1)



Once again, the property bag is what remembers what you set in the property window when you design a form and place controls on it.  If you do not use the property bag, no matter what you set on the property window later will never be saved.








This'll give you the clsid of the tlb that belongs to an ocx (or dll) but keep in mind that an ocx can contain several controls that each have their own clsid. Here's a startthough. Set a reference to 'TypeLib Information' (tlbinf32), drop a command button on the form and run this.

Option Explicit
Private Sub Command1_Click()
   MsgBox GetCLSIDFromFile _
End Sub
Private Function GetCLSIDFromFile _
   (ByVal FullPathToFile As String) As String
   On Error GoTo ErrorTrap
   Dim oTLB As TLI.TypeLibInfo
   Dim iCount As Integer
   Set oTLB = TLI.TypeLibInfoFromFile(FullPathToFile)
   If oTLB Is Nothing Then
      MsgBox "No TypeLib info found."
      GetCLSIDFromFile = oTLB.Guid
   End If
   Exit Function
   If Err.Number = &H80040202 Then 'can't get tlb info
      MsgBox "Failed to get type lib info for file: [" _
      & FullPathToFile & "[" & vbCrLf & vbCrLf _
      & Err.Description, vbExclamation
      MsgBox "Error " & Err.Number _
         & vbCrLf & Err.Description, "GetCLSIDFromFile"
   End If
   Debug.Assert False
   Resume Terminate
End Function

For a complete example of how to extract just about anything from an ocx, see: ActiveX Documenter http://vbaccelerator.com/home/VB/Utilities/ActiveX_Documenter/article.asp It's a free utility (includes source) that will show all methods/properties/etc in an activex component. Very handy tool.