# Dynamic web pages with ActiveX and VBScript

UNDER CONSTRUCTION

## Introduction

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...

• ... client-side binaries: They are too big a risk. This is definitely a risk, but you can lower security only for your site by adding the domain in the Trusted Sites section

• ... ActiveX components as opposed to Java applets: Compared to Java applets, ActiveX present more security risk right from the start since it is allowed to perform any operation that the logged on user can do. Java applets, on the other hand, run in the so-called "sandbox" mode by default, and only signed applets, if accepted by the user, have full access to the computer.

But then, by using Trusted Sites, they don't take more risk than running a dedicated application gone berserk... If they trust you with dedicated applications (they do trust you, right?), then downloading the same kind of code from your site as an ActiveX component makes no difference.

Besides, starting with XP, Microsoft no longer ships a JVM: Not having to install this yourself on all the client hosts and set their Internet Explorer to run Java applets is a big plus.

• ...VBScript over JavaScript: VBS is only supported by Internet Explorer, but considering its market share...

• ... ActiveX and VBScript as opposed to non-proprietary solutions: Fact is, using ActiveX and VBS locks you (to develop your web application as an ActiveX component) and your customers (since as of Nov 2003, only IE can work with ActiveX and VBS, although people are working to allow Mozilla to support ActiveX) into using Microsoft products.

On the other hand, and unlike the .Net architecture (at least unless the DotGNU and Mono projects fail), you are free to use any OS and web server to host your ActiveX/VBS-filled web pages.

And, again, considering Windows' market share, is it really a sound decision to dump this solution just to satisfy the 10% of users not using Windows as their OS and/or Internet Explorer as their browser, especially when the alternatives, whether they are Java applets or server-side scripting, are such a pain?

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:

• No menus (merged with IE's)
• Show() method doesn't exist (always shown when called)
• non-modal forms N.A.
• You cannot use End, as a control is unloaded only once its container is closed

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.

Notes:

• UserControl_Initialize() is called every time the control is loaded, which explains why this event is triggered when you hit F5 to launch its container AND when you stop the project, and go back to design mode when the control is reloaded in the form
• Set the control's background color through AMBIENT:

Private Sub UserControl_InitProperties()
UserControl.BackColor = UserControl.Ambient.BackColor
End Sub

• If a control doesn't show in the form, make sure its size is big enough in the form, or it is clipped when the form is running
• If the control looks smaller when shown in IE than it is when shown running in a form in the IDE, open the HTML page, and check that the control's WIDTH and HEIGHT attributes in its OBJECT tag are big enough
• An OCX can contain more than one control
• The project's name is the name given to the OCX file when compiled; A control's name is the right hand-side part in the ProgID, ie. myproject.myctrl; The description (Project | Properties | Description) is the string that shows up when listing the registered components on a host. So, choose those three infos carefully

## 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:
<HTML>
<TITLE>Testing ActiveX</TITLE>
<BODY>
<OBJECT ID="UserControl11" CLASSID="CLSID:67A2A38D-3CA7-479A-9928-C4E070A691EE"   CODEBASE="dummy.ocx#version=1,0,0,3">
</OBJECT>

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

</BODY>
</HTML>

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

## 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.

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
Err.Clear
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
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:

• border: width of the border for an object used as hyperlink
• classid: unique ID to an ActiveX control. This is the minimum attribute that you must include, along with the codebase attribute so your browser knows from where it can download this control if it is not installed on the client host. Once a control has been registered on a host, its CLSID can be found in the Registry at HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID .

This attribute can also include the VIEWASTEXT switch.

• codebase: location where the control can be found on the server. Required if the control has never been installed on the client host.

This attribute can/should be followed by a "version" attribute, so the browser can tells whether a newer version of the control is available on the server. The whole syntax looks like CODEBASE="dummy.ocx#version=1,0,0,3". You can force update every time a user browses the page by using "version=-1,-1,-1,-1". Note that any object that has a version number (and possibly a CLSID) can be mentionned here (ie. OCX, DLL, EXE, CAB).

Ideally, the version number should be updated in both the web page and the INF file contained in the CAB archive (in case you are using CAB archives instead of direct links to OCX files to distribute your controls), but if you're lazy, just updating the version number in the CODEBASE attribute is enough to trigger a new download
• codetype: specifies a MIME type
• declare: tells the browser to load a control without activating it
• height: height of a control, in pixels
• hspace: horizontal space between the control and surrounding text
• id: gives a name to the control, which you can use to access it from VBScript code. Optional.
• name: gives a name to a control, which can be used to refer to it in an HTML form
• shapes: contains geometric forms to be used as hyperlink
• type: specifies the type of data specified by the "data" attribute, eg. "application/x-oleobject"
• usemap: connects an object to an array of links
• vspace: vertical space betwwen the control and surrounding text
• width: width of a control, in pixels

## Deployment

(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

Notes:

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=8.0.0.5007 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: • Use VB's PDW to generated a DEP file for each of your OCX. This DEP file contains all the dependencies for a given ActiveX control • Read the project's VBP • Any DLL referenced by a VB project should be added to the list of dependencies, so look for DECLARE statements ### 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 [version] signature="$CHICAGO$" AdvancedINF=2.0 [Add.Code] DUMMY.OCX=DUMMY.OCX ;Common Controls 5.0 SP2 - Includes advanced controls like progress bar, etc. COMCTL32.OCX=COMCTL32.OCX ;Common Controls2 6.0 SP4 - Contains DatePicker etc. MSCOMCT2.OCX=MSCOMCT2.OCX ;Localized (French) file MSCC2FR.DLL=MSCC2FR.DLL ;VB5-compatible version of SQLite PSVBUTLS32.DLL=PSVBUTLS32.DLL ;Localized (French) file CMCTLFR.DLL=CMCTLFR.DLL ;ComponentOne's VSFlexGrid VSFLEX7L.OCX=VSFLEX7L.OCX ;VB fixed-cost stuff ASYCFILT.DLL=ASYCFILT.DLL MSVBVM50.DLL=MSVBVM50.DLL ;Localized (French) file VB5FR.DLL=VB5FR.DLL ;Common Controls 6.0SP4 - Includes advanced controls like progress bar, image combo, etc. MSCOMCTL.OCX=MSCOMCTL.OCX ;Common Dialogs COMDLG32.OCX=COMDLG32.OCX ;Localized (French) file CMDLGFR.DLL=CMDLGFR.DLL ;Localized (French) file MSCMCFR.DLL=MSCMCFR.DLL [DUMMY.OCX] file-win32-x86=thiscab RegisterServer=yes clsid={BAF08E86-0ED3-473B-B8F7-6118840E738B} DestDir=10 FileVersion=1,0,0,0 [COMCTL32.OCX] hook=ComCtl32.cab_Installer clsid={9ED94440-E5E8-101B-B9B5-444553540000} FileVersion=6,0,81,5 [ComCtl32.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb5/ComCtl32.cab InfFile=ComCtl32.inf [MSCOMCT2.OCX] hook=MSComCt2.cab_Installer clsid={B09DE715-87C1-11D1-8BE3-0000F8754DA1} FileVersion=6,0,88,4 [MSComCt2.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/MSComCt2.cab InfFile=MSComCt2.inf [MSCC2FR.DLL] hook=MSCc2FR.cab_Installer FileVersion=6,0,81,63 [MSCc2FR.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/MSCc2FR.cab InfFile=MSCc2FR.inf [PSVBUTLS32.DLL] file-win32-x86=thiscab RegisterServer=no DestDir=10 [CMCTLFR.DLL] hook=CmCtlFR.cab_Installer FileVersion=6,0,80,22 [CmCtlFR.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb5/CmCtlFR.cab InfFile=CmCtlFR.inf [VSFLEX7L.OCX] file-win32-x86=thiscab RegisterServer=yes clsid={C0A63B86-4B21-11d3-BD95-D426EF2C7949} DestDir=10 FileVersion=7,0,0,62 [ASYCFILT.DLL] hook=AsycFilt.cab_Installer FileVersion=2,20,4118,1 [AsycFilt.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb5/AsycFilt.cab InfFile=AsycFilt.inf [MSVBVM50.DLL] hook=MSVBVM50.cab_Installer FileVersion=5,2,82,44 [MSVBVM50.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb5/MSVBVM50.cab InfFile=MSVBVM50.inf [VB5FR.DLL] hook=VB5FR.cab_Installer FileVersion=5,0,43,19 [VB5FR.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb5/VB5FR.cab InfFile=VB5FR.inf [MSCOMCTL.OCX] hook=MSComCtl.cab_Installer clsid={1EFB6596-857C-11D1-B16A-00C0F0283628} FileVersion=6,0,88,62 [MSComCtl.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/MSComCtl.cab InfFile=MSComCtl.inf [COMDLG32.OCX] hook=ComDlg32.cab_Installer clsid={F9043C85-F6F2-101A-A3C9-08002B2F49FB} FileVersion=6,0,84,18 [ComDlg32.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/ComDlg32.cab InfFile=ComDlg32.inf [CMDLGFR.DLL] hook=CmDlgFR.cab_Installer FileVersion=6,0,81,63 [CmDlgFR.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/CmDlgFR.cab InfFile=CmDlgFR.inf [MSCMCFR.DLL] hook=MSCmCFR.cab_Installer FileVersion=6,0,81,63 [MSCmCFR.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/MSCmCFR.cab InfFile=MSCmCFR.inf ### 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 [version] signature="$CHICAGO$" AdvancedINF=2.0 [DefaultInstall] CopyFiles=install.files RegisterOCXs=RegisterFiles AddReg=AddToRegistry [RInstallApplicationFiles] CopyFiles=install.files RegisterOCXs=RegisterFiles AddReg=AddToRegistry [DestinationDirs] install.files=11 [SourceDisksNames] 1=%DiskName%,dummy.CAB,1 [Add.Code] dummy.ocx=dummy.ocx MSSTKPRP.DLL=MSSTKPRP.DLL MSPRPFR.DLL=MSPRPFR.DLL msvbvm60.dll=msvbvm60.dll OLEAUT32.DLL=OLEAUT32.DLL OLEPRO32.DLL=OLEPRO32.DLL ASYCFILT.DLL=ASYCFILT.DLL STDOLE2.TLB=STDOLE2.TLB COMCAT.DLL=COMCAT.DLL VB6FR.DLL=VB6FR.DLL [install.files] dummy.ocx=dummy.ocx [SourceDisksFiles] dummy.ocx=1 [dummy.ocx] file-win32-x86=thiscab RegisterServer=yes clsid={F4C9FFFC-7C1F-4C22-9507-CD8F7A160952} DestDir= FileVersion=1,0,0,3 [MSSTKPRP.DLL] hook=MSSTKPRP.cab_Installer FileVersion=6,0,81,69 [MSSTKPRP.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/MsStkPrp.cab InfFile=MsStkPrp.inf [MSPRPFR.DLL] hook=MSPRPFR.cab_Installer FileVersion=6,0,81,63 [MSPRPFR.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/MsPrpFR.cab InfFile=MsPrpFR.inf [msvbvm60.dll] hook=msvbvm60.cab_Installer FileVersion=6,0,89,64 [msvbvm60.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/VBRun60.cab run=%EXTRACT_DIR%\VBRun60.exe [OLEAUT32.DLL] hook=OLEAUT32.cab_Installer FileVersion=2,40,4275,1 [OLEAUT32.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/VBRun60.cab run=%EXTRACT_DIR%\VBRun60.exe [OLEPRO32.DLL] hook=OLEPRO32.cab_Installer FileVersion=5,0,4275,1 [OLEPRO32.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/VBRun60.cab run=%EXTRACT_DIR%\VBRun60.exe [ASYCFILT.DLL] hook=ASYCFILT.cab_Installer FileVersion=2,40,4275,1 [ASYCFILT.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/VBRun60.cab run=%EXTRACT_DIR%\VBRun60.exe [STDOLE2.TLB] hook=STDOLE2.cab_Installer FileVersion=2,40,4275,1 [STDOLE2.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/VBRun60.cab run=%EXTRACT_DIR%\VBRun60.exe [COMCAT.DLL] hook=COMCAT.cab_Installer FileVersion=4,71,1460,1 [COMCAT.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/VBRun60.cab run=%EXTRACT_DIR%\VBRun60.exe [VB6FR.DLL] hook=VB6FR.cab_Installer FileVersion=6,0,89,88 [VB6FR.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/VB6FR.cab InfFile=VB6FR.inf [Setup Hooks] AddToRegHook=AddToRegHook [AddToRegHook] InfSection=DefaultInstall2 [DefaultInstall2] AddReg=AddToRegistry [AddToRegistry] 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." [RegisterFiles] %11%\dummy.ocx ### 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: [Add.Code] DUMMY.OCX=DUMMY.OCX DUMMY.JPG=DUMMY.JPG [DUMMY.JPG] file-win32-x86=thiscab RegisterServer=no ;10 = Drive:\WINDOWS (or WINNT), 11 = WINDOWS\SYSTEM, empty = WINDOWS\Downloaded Program Files\ DestDir=10 FileVersion= 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. [COMCTL32.OCX] hook=ComCtl32.cab_Installer clsid={9ED94440-E5E8-101B-B9B5-444553540000} FileVersion=6,0,81,5 [ComCtl32.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb5/ComCtl32.cab InfFile=ComCtl32.inf #### Microsoft Windows Common Control 6.0 SP4 Same as 5.0 SP2, but with a couple of additional controls like Image Combo [MSCOMCTL.OCX] hook=MSComCtl.cab_Installer clsid={1EFB6596-857C-11D1-B16A-00C0F0283628} FileVersion=6,0,88,62 [MSComCtl.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/MSComCtl.cab InfFile=MSComCtl.inf #### Microsoft Windows Common Control-2 6.0 SP4 DatePicker, etc. [MSCOMCT2.OCX] hook=MSComCt2.cab_Installer clsid={B09DE715-87C1-11D1-8BE3-0000F8754DA1} FileVersion=6,0,88,4 [MSComCt2.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/MSComCt2.cab InfFile=MSComCt2.inf #### Microsoft Common Dialog Control 6.0 SP3 Common dialog boxes like OpenFile, etc. [COMDLG32.OCX] hook=ComDlg32.cab_Installer clsid={F9043C85-F6F2-101A-A3C9-08002B2F49FB} FileVersion=6,0,84,18 [ComDlg32.cab_Installer] file-win32-x86=http://activex.microsoft.com/controls/vb6/ComDlg32.cab InfFile=ComDlg32.inf ## 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 ### AccessKeyPress 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. ### AmbientChanged Se produit lorsque la valeur dâ€™une propriété ambiante change. ### AsyncReadComplete Se produit lorsque le conteneur a accompli une requête de lecture asynchrone. ### Click ### DblClick ### DragDrop 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. ### DragOver 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. ### EnterFocus 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. ### ExitFocus 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. ### GotFocus 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. ### Hide Se produit lorsque la propriété Visible de lâ€™objet prend la valeur False. ### Initialize 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!) ### InitProperties 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. ### KeyDown ### KeyUp ### LostFocus 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. ### MouseDown ### MouseMove ### MouseUp ### OLECompleteDrag ### OLEDragDrop ### OLEDragOver ### OLEGiveFeedBack ### OLESetData ### OLEStartDrag ### Paint 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. ### ReadProperties 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. ### Resize 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. ### Show 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. ### Terminate 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. ### WriteProperties 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 <HTML><BODY> <OBJECT classid='clsid:14A2B8E1-EF77-4C43-9756-BA8F3950883D' STANDBY='Loading....'> <PARAM NAME='MyHello' VALUE="Hello"> </OBJECT> 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.) ### C:\WINNT #### Downloaded Program Files • dummy.inf • Projet1.ocx #### system32 • MSPRPfr.DLL • MSSTKPRP.DLL • PSVBUTLS32.DLL (VB-compatible version of SQLite) • VB6fr.DLL • msvbvm60.dll • setupapi.log #### system32\config • SECURITY • software • system • SYSTEM.ALT ## Security Security-related sections in Internet Explorer are confusing: Security tab: • Looks like the Internet context is used when the user accesses a URL that is not mentioned in the other sections (Local Intranet, Trusted Sites, Restricted Sites.) Note that the Local Intranet section lets you add servers in your organization (foreign servers should be set to Trusted Sites, instead). • For each context, you can either use default security settings or customize them • The ActiveX-related security settings are • ActiveX controls and plug-ins: • Download signed ActiveX controls • Download unsigned ActiveX controls • Initialize and script ActiveX controls not marked as safe • Run ActiveX controls and plug-ins • Script ActiveX controls marked safe for scripting 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 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"> </OBJECT> 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! • Each HTML page using one or more licensed controls requires a license file (.lpk) that contains the license strings for each control. • Each HTML page can use only a single .lpk file, but a single .lpk file can be used for more than one page. • The .lpk file must be on the same server as the HTML page it covers. • The .lpk file must contain a plain text copyright notice to dissuade casual copying of .lpk files. 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 • With the exceptions of a control's intrisic properties like width or height, the properties of a control are private (while methods are public, by default.) If you need to change a control's properties from the outside, eg. VBScript code embedded in a web page, either create a method that gives indirect access ot its private properties, or add get/let procedures • Add the following in the HEAD section in each page that contains an OBJECT tag, to forbid the browser from caching the page, thus, making it difficult to updated a control: <meta http-equiv="Pragma" content="no-cache"> • If you need to initialize data before a control pops up, use the UserControl_Initialize() even instead of _Show() • No reference to the Ambient object in Initiliaze(): At this point, your ActiveX control hasn't been inserted into a container. Referering to Ambient triggers error 398 "Client site not available". On the other hand, you can use the following code in InitProperties() to check if the control is currently in Design mode (ie. in the VB IDE) or in Running mode (ie. running in the IDE, or in IE): 'UserMode True = Execution mode If Ambient.UserMode = False Then MsgBox "UserMode" End If • You can create more than one control in a VB project • When recompiling an ActiveX control, make sure you update its version number in the INF and the OBJECT tag in the web page that references it. Otherwise, Internet Explorer won't dowload the newer version • Make sure the fixed-cost stuff (eg. VB run-time DLL) that the user needs to download from Microsoft's site matches the version of VB that you used to build your ActiveX controls... Otherwise, your ActiveX control won't start, and you'll be staring at a grey dot surrounded by a black square, with the log file simply saying "Class not registered" :-) • For testing purposes, make sure you use a brand new version of the OS, ie. a host that never had the VB IDE installed. It's the only way to make sure IE prompts to download the VB stuff • In CABs, ideally, only include an INF: Since the CAB file is downloaded every time the OCX it refers to needs to be downloaded (either because the control is not available on the client host, or you updated its version #), it'd be stupid to require the user to download megabytes of stuff it already has (eg. VB's run-time stuff, which won't change for months) • Make sure a user is always using the latest and greatest: It's a good idea to display the version number of an OCX: Private Sub UserControl_Initialize() Label1.Caption = App.Major & "." & App.Minor & "." & App.Revision End Sub ## Troubleshooting 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: • Make sure you included an "codebase=" attribute so that IE knows where to download the OCX from • If you're not using the WIDTH/HEIGHT attributes, try adding the following parameters, as some users have reported that it solves the issue : <PARAM NAME="_ExtentX" VALUE="13203"> <PARAM NAME="_ExtentY" VALUE="9260"> • Make sure the OBJECT tag is correctly closed. In this example below, a > is missing after the version info, so the whole object will be ignored: <object id="myctl" CLASSID="CLSID:8FAB6C2E-8293-4A39-A9E9-29585D75AD04" CODEBASE="dummy.ocx#version=1,0,0,3" </object> • Make sure the CLSID in the web page (and the INF file if you use a CAB) actually matches the CLSID of the ActiveX control mentionned in the OBJECT tag. A symptom of an error is that you will be prompted by IE to download the same dependencies over and over again, and also, a CLSID shown for a file in \WINNT\Downloaded Files instead of a ProgID 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: • If you use a CAB file and set it up to prompt the user to download the prerequisite files from Microsoft's site, make sure the client host's Internet connectivy (ie. router, and DNS) works... • Check that the contents of the CAB file is correct using the CABARC utility, eg. cabarc | mycab.cab displays the list of files contained in the archive (FYI, Windows Commander knows how to open CAB files, but not write into them at least using release 4.51) • Some users have reported that the PDW utilitysometimes miss dll's. Check all your 'dll dependencies. Also look on you "Compile" options • Unless specified differently in the INF file in a CAB file, by default, ActiveX .ocx and .inf files are installed in the Windows\Downloaded Program Files folder by default. If you wish, this location can be changed by tweaking the Registry: Use Registry Editor to change the "ActiveXCache" value to the location you want in the following registry key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings Use Registry Editor to change the "0" value to the location you want in the following registry key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ActiveX Cache NOTE: The values you enter in steps 1 and 2 must match. The new value can be a folder on a local hard disk, a Universal Naming Convention (UNC) name, or a mapped network drive. Note that storing ActiveX files on a network server may reduce Internet Explorer performance because the ActiveX files must be run over the network. • Check that customers don't have a firewall that strips ActiveX controls on their way in... • Ask your customers to launch Windows Explorer, go to C:\WINNT\Downloaded Program Files\, where your CAB archives should be found, right-click on a given object (eg. MYCTL.OBJECT1), and select Update. This doesn't always force Windows to download the latest CAB via the CODEBASE attribute, but it works... sometimes :-) • Not sure about this, but maybe order in an INF makes a difference, ie. maybe the VB fixed-cost stuff must be first or last, etc. Experiment with a basic ActiveX and PDW to see how it generates entries • If all else fails, two solutions to investigate: Either use the Code Download Log Viewer utility, or follow the instructions on how to view an error log file generated by Internet Explorer. Another solution that I haven't tried myself (From Simon MacArthur (simon.macarthur@union2.co.uk)): "You could implement IObjectSafety in your controls. This basically means that your control, if it is already registered on a machine, will be seen as safe. This way you can run a standard setup (which I have found much more reliable than P&D internet cab setups), and IE will not go to the cab. If you have MSDN check that out for references for IObjectSafety. Lot's of code there on how to do this, it is a bit tricky because you have to create a typelib and use some fairly nasty looking API calls, but the code is there to cut and paste. I'm starting to use this method a great deal, because it allows me to have one control that on start up will download others, and register them for me. This saves a huge amount of running around creating cabs, signing them etc. My first page checks the installed versions against an xml manifest, then downloads just the updated dlls ocx etc and registers them for me." ## Q&A ### 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: • ActiveX Property Sheet Shell Extension (Lets you inspect the TypeLib's on any ActiveX component) • ActiveX Documenter (A bit tricky to setup, and there's a bug or two; Contains all the code you'd need to inspect TypeLib versions, where a component's located on the drive (based on info found in the registry), CLSID's, TypeLib ID's, etc, etc, etc... uses the same component as VB's Object Browser does so....) • Com Explorer (Very cool, although not free; "COM Explorer is a unique tool specifically designed to enable developers and system administrators to explore, manage and fix ActiveX Controls, EXE Servers (Out-Proc) and DLL Servers (In-Proc).") ### 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 ACTIVEX CAN'T CREATE WHICH OBJECT? 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 CreateErr: ' 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 created. 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

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
Err.Clear
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()
Dim iCounter As Integer
For iCounter = 0 To 5
Command1(iCounter).Enabled = True
Next iCounter
Else
End If
End Sub

Private m_ExitOK As Boolean

Private Sub cmdCancel_Click()
m_blnSuccess = False
End Sub

Private Sub cmdOK_Click()
m_blnSuccess = True
m_ExitOK = True
Else
m_blnSuccess = False
m_ExitOK = False
End If
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"

OR

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".)

### 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...

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?

• PowerBasic with JazzAgeSoft's JA COM/PB
• VC++
• Borland C++
• Delphi?

### 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.

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

Tip given here:

REGEDIT4

[HKEY_CLASSES_ROOT\.dll]
@="dllfile"

[HKEY_CLASSES_ROOT\dllfile]
@="Application Extension"

[HKEY_CLASSES_ROOT\dllfile\Shell\Register\command]
@="regsvr32.exe \"%1\""

[HKEY_CLASSES_ROOT\dllfile\Shell\UnRegister\command]
@="regsvr32.exe /u \"%1\""

[HKEY_CLASSES_ROOT\.ocx]
@="ocxfile"

[HKEY_CLASSES_ROOT\ocxfile]
@="OCX"

[HKEY_CLASSES_ROOT\ocxfile\Shell\Register\command]
@="regsvr32.exe \"%1\""

[HKEY_CLASSES_ROOT\ocxfile\Shell\UnRegister\command]
@="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.

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"
Else
Beep
' MsgBox ApiErrorText(Err.LastDllError)
' for ApiErrorText, see:
http://www.mvps.org/vb/index2.html?tips/formatmessage.htm
End If
End Sub

Function IsDLLAvailable(ByVal DllFilename As String) As Boolean
Dim hModule As Long
' attempt to load the module
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
Trap:
End Function

## Alternatives to ActiveX

Some alternatives for richer web clients :

## TO-DO

• Download all VB stuff on our site, with MS sites as primary, and our site as secondary d'load server
• Find what features in VB are not available when running the app as an ActiveX control in a web page (no menu bar, only one form, etc.)

## Issues

• How to name the ActiveX with a better ProgID than Project1.UserControl1? Change both the project name and the control name. Actually, you really must do this because using default names probably erases references to a previous version (CHECK if VB doesn't just increase the number following the control's name)
• IE 6 crashes every so often when loading an OCX (either via a CAB or directly): Need to upgrade the VBScript engine?
• How does versioning work? The OBJECT and the INF use 4 digits, VB uses 3, and the Registry only two
• When packaging OCX's via a CAB, can I just increment the CAB's version number to force update? Yes. Make sure you update the version number in both the INF and the HTML page
• Versioning does not work reliably: Sometimes, even through the control has changed on the server, the control is not updated on the client host
• In the OBJECT section, possible to use the control's ProgID instead of its CLSID?
• In case versioning doesn't work reliably, is there a way to unregister a control through VBS to force updating?

Although IE doesn't display any error, I can't delete a key in the Registry:
<script language="vbscript">
Dim oShell
Set oShell = CreateObject("Wscript.shell")
oShell.RegDelete "HKEY_CLASSES_ROOT\CLSID\{67A2A38D-3CA7-479A-9928-C4E070A691EE}"
</script>

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 OFS_MAXPATHNAME = 128

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
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"
hdest = LZOpenFile(MyDest, OpenStruct, OF_CREATE)
iret = LZCopy(hsource, hdest)
If iret = -1 Then
MsgBox "File transfer failed"
Else
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

MySource = AsyncProp.Value
Timer1.Interval = 1000
End Sub

End Sub

Private Sub UserControl_Show()

If Ambient.UserMode = True Then

MsgBox "Run-time"

Else

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_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.

## TEMP

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 _
("C:\WINNT\system32\MSCOMCTL.OCX")
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."
Else
GetCLSIDFromFile = oTLB.Guid
End If

Terminate:
Exit Function

ErrorTrap:
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
Else
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.