EnduraSoft MFC ActiveX Frequently Asked Questions (FAQ)

Probably the best use of the Web is information sharing. At EnduraSoft, we believe in information freely shared, and we hope our experiences with ActiveX programming are helpful to you and others. To develop our products, we've had to overcome many "information deficiencies"…this isn't too surprising given the nature of this technology (rather new).

This FAQ will no doubt continue to grow as we add to our own knowledge. If you see something that is incorrect or not quite correct, please drop us a line! Similarly, if you have a tidbit you'd like to add, we'll add it here for everyone to use (and, of course, credit you for your kind contribution). Give a little, take a little.

Disclaimer: We believe the information contained in this FAQ is correct and accurate, but we can't make any guarantees as to its suitability for any particular use. In other words, don't sue us if something doesn't work. Rather, drop us a note and we'll work together to figure it out.

Also note this FAQ currently refers to MFC-based controls, not (at this time) ATL. Our experience with ATL is growing, and we're really excited about the power of ATL, but we don't have enough professional experience to put it here for you. Given time… If you have ATL-based FAQ fodder, send it in! We'll include it pronto.

Like the famous Stingray MFC FAQ, please allow us this shameless plug:

Try ClassX! Or maybe the free demo!

Try ClassWerx!

(Please take a moment and check 'em out...maybe they can help!)

We've added the knowledge contained herein to these products, and we hope you'll try and love working with them as much as we do. Now, onto the FAQ.

The FAQ is broken down into sections. At this time, we've populated some of the sections, but only time will allow us to fill each as they deserve. The request for you to donate your experience is always welcome!

Sections:

1. ControlWizard/Generated Code/Code

1.1 How do I get the nifty text values to show up in container parameter lists, versus simple integer values?

1.2 What does the term 'double-buffered' mean and why should my controls always be double-buffered?

2. Parameters/Methods/Events

2.1 With Internet Explorer 4.0, my events aren't firing properly. Why?

3. Bypassing ClassWizard

3.1 I added parameters/methods/events (your pick) to my code without using the Class Wizard, and now I control is acting strange. Why?

3.2 I can't get Visual Basic to recognize a control method which needs to accept more than one input parameter. How can I set up my control method calls to allow VB to call functions using more than one parameter?

4. Control Naming/Registration

4.1 When I created the control, I called it (whatever). But when it loads into a container, I want it to have a default name of (whatever, not the same as what you originally named it). How can I do this?

5. Object Safety/Component Categories

5.1 What is a component category? What is 'safe for scripting/initialization'?

5.2 How do I add Component Category support to my control?

5.3 What is IObjectSafety? Why do I get 'unsafe to initialize/script' when my control loads in Internet Explorer 4.0/4.01?

6. Control Packaging

6.1 How do I package a control for download over the Internet?

7. Control Signing

7.1 I get this weird error '0x80004005' when applying a digital signature to my cabinet file. What is it and why do I get it?

7.2 How do I digitally sign an ActiveX control for download over the Internet?

1. ControlWizard/Generated Code/Code

1.1 "How do I get the nifty text values to show up in container parameter lists, versus simple integer values?"

Place an enumeration in your control's ODL file, then replace the parameter's type (usually SHORT) with the enumerated type. For example, say you had a parameter called "Speed" which accepted a short value from 0 to 3, with 0 being "Slow" and 3 being "Fastest". ClassWizard would insert the following code into your ODL file (other parameters shown for example purposes):

//{{AFX_ODL_PROP(CMyControlCtrl)
	[id(1)] short Speed;
	[id(DISPID_BACKCOLOR),bindable, requestedit] OLE_COLOR BackColor;
	[id(DISPID_FORECOLOR), bindable, requestedit] OLE_COLOR ForeColor;
	[id(DISPID_HWND)] OLE_HANDLE hWnd;
	[id(DISPID_ENABLED), bindable, requestedit] boolean Enabled;
	[id(DISPID_READYSTATE), readonly] long ReadyState;
//}}AFX_ODL_PROP

Add the following text just after the last "importlib" statement, like so:


importlib(STDTYPE_TLB);
typedef enum
{
	[helpstring("Slow")] Slow = 0,
	[helpstring("Medium Slow")] MediumSlow = 1,
	[helpstring("Fast")] Fast = 2,
	[helpstring("Fastest")] Fastest = 3
} enumSpeed;

    
And change the parameter's type from "short" to enumSpeed, like so:
[id(1)] enumSpeed Speed;

Now, when you use a container which displays the enumerated help text, you'll see the words "Slow", MediumSlow", "Fast" and "Fastest" instead of a blank parameter input line.

1.2 "What does the term 'double-buffered' mean and why should my controls always be double-buffered?"

Double-buffering is a graphics programming term meaning you modify your image in off-screen memory, then copy that memory to the screen all at once (or at least the portion of the image that needs to be updated, which is called a 'dirty rectangle'). The off-screen memory is the 'buffer' we are referring to, and the fact it exists in parallel with screen memory means it is a second copy, or a 'double'.

The benefit of this is you do not see any 'screen flash' associated with your controls graphics update. For example, imagine drawing a blue circle. Then when the user clicks the mouse inside the circle, you erase the blue circle and draw a red rectangle. Without a double-buffer, you may actually see the background as it erases (to remove your circle) and redraws a rectangle. Even on fast systems this is annoying, as you see a brief 'flash' as the screen erases and redraws.

However, if you do all of this in memory, then copy the resulting image to the screen, you never see the 'flash'. Any graphical manipulation is handled off-screen with the final image simply pasted to the screen all at once (no need to even erase the original image as you're replacing it, or the 'dirty rectangle', in its entirety).

To see sample code to do this, check out "OLE Controls: Top Tips" at Microsoft (note you need to be a MSDN online member to enter the library, though this membership is currently free). This article is an excellent reference tool for the remaining 9 tips, one of which describes the 'dirty rectangle' update I mentioned here.

2. Parameters/Methods/Events

2.1 "With Internet Explorer 4.0, my events aren't firing properly. Why?"

IE 4.0 introduced a new twist, one of many, requiring you to properly handle the controls "ready state". If you control declares DISPID_READYSTATE, it must fire FireReadyStateChanged(READYSTATE_COMPLETE) before IE 4.0 will allow the control's events to fire. See the online article "Building ActiveX Controls for Internet Explorer 4.0" at Microsoft.

3. Bypassing ClassWizard

3.1 "I added parameters/methods/events (your pick) to my code without using the Class Wizard, and now I control is acting strange. Why?"

Depending upon what you mean by strange, it could be the order of the declarations in your control's source file is out of order. The order of the entities in both the dispatch and event maps is critical. That is, if you go to your control's header file (MyControlCtl.h) and examine the last few lines, you'll see something like this:

// Dispatch and event IDs
public:
enum {
//{{AFX_DISP_ID(CMyControlCtrl)
dispidParameter1 = 1L,
dispidParameter2 = 2L,
dispidParameter3 = 3L,
dispidParameter4 = 4L,
eventidEvent1 = 1L,
eventidEvent2 = 2L,
//}}AFX_DISP_ID
};

The order of the IDs here must match the order of the IDs in both the BEGIN_DISPATCH_MAP and BEGIN_EVENT_MAP sections of your control's source file (MyControlCtl.cpp):

/////////////////////////////////////////////////////////////////////////////
// Dispatch map
BEGIN_DISPATCH_MAP(CMyControlCtrl, COleControl)
//{{AFX_DISPATCH_MAP(CMyControlCtrl)
DISP_PROPERTY_EX(CMyControlCtrl, "Parameter1", GetParameter1, SetParameter1, VT_I2)
DISP_PROPERTY_EX(CMyControlCtrl, " Parameter2", GetParameter2, SetParameter2, VT_BOOL)
DISP_PROPERTY_EX(CMyControlCtrl, " Parameter3", GetParameter3, SetParameter3, VT_I2)
DISP_PROPERTY_EX(CMyControlCtrl, " Parameter4", GetParameter4, SetParameter4, VT_I2)
DISP_FUNCTION(CMyControlCtrl, "Method1", Method1, VT_I4, VTS_I4 VTS_I4 VTS_BOOL)
DISP_FUNCTION(CMyControlCtrl, " Method2", Method2, VT_BOOL, VTS_NONE)
DISP_STOCKPROP_BACKCOLOR()
DISP_STOCKPROP_FORECOLOR()
DISP_STOCKPROP_HWND()
DISP_STOCKPROP_ENABLED()
DISP_STOCKPROP_READYSTATE()
DISP_PROPERTY_EX_ID(CMyControlCtrl, "Font", DISPID_FONT, GetFont, SetFont, VT_FONT)
//}}AFX_DISPATCH_MAP
DISP_FUNCTION_ID(CMyControlCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()

/////////////////////////////////////////////////////////////////////////////
// Event map
BEGIN_EVENT_MAP(CMyControlCtrl, COleControl)
//{{AFX_EVENT_MAP(CMyControlCtrl)
EVENT_CUSTOM("Event1", FireEvent1, VTS_NONE)
EVENT_CUSTOM("Event2", FireEvent2, VTS_NONE)
EVENT_STOCK_READYSTATECHANGE()
//}}AFX_EVENT_MAP
END_EVENT_MAP()

3.2 "I can't get Visual Basic to recognize a control method which needs to accept more than one input parameter. How can I set up my control method calls to allow VB to call functions using more than one parameter?"

It is likely you are using a SHORT value as one of your parameters. Change this to a LONG value. VB accepts BSTR, BOOL, and LONG as parameters (as well as other values, but these are the most common). Also, in any such function, VB will expect a return value, so don't declare your method as VOID. Return a LONG value, even if you merely always return zero. In your VB code, you then must set up a dummy return variable to accept the LONG return value, even if you don’t use it.

4. Control Naming/Registration

4.1 "When I created the control, I called it (whatever). But when it loads into a container, I want it to have a default name of (whatever, not the same as what you originally named it). How can I do this?"

Easy! Change the "coclass" identifier in your control's ODL file to the name you wish to use. For example, say you called your control MyControl when the ControlWizard created it. The control would default to the name "MyControl" in most containers, like Visual Basic. But if you wanted to have the control have a default name of "ZippyControl", you would change the coclass identifier from "MyControl" to "ZippyControl":

coclass MyControl
changes to:
coclass ZippyControl

Now, Visual Basic (and most containers) will provide you with the default control name ZippyControl1 (or ZippyControl2, etc…). You'll find the coclass identifier near the bottom of the ODL file.

5. Object Safety/Component Categories

5.1 "What is a component category?" "What is 'safe for scripting/initialization'?"

Microsoft, in their 1996 ActiveX control specification, provided developers with a mechanism to determine not only that a control exists in a given user's system, but also that it supports a variety of capabilities. You can query a control for its methods and parameters, but you can't otherwise ask it what it does. Component categories were meant to assist developers (or rather, containers) in determining more precisely what a control is capable of.

For example, the first category is that of "control", and it is meant to tell the container this object is, indeed, an automation control (ActiveX). Here are the current categories:

It is completely up to you, the developer, to determine your control is safe given any of these categories. There are some guidelines specifically for initialization and scripting, however. See the article " Building ActiveX Controls for Internet Explorer 4.0" at Microsoft.

Internet security is the primary reason you need to support the safe for initialization/scripting categories. If you do not, your control will not be loaded and shown by the default Internet Explorer security settings. (Note Netscape is not relevant in this particular discussion, as Netscape does not support ActiveX controls using the <OBJECT> tag.)

5.2 "How do I add Component Category support to my control?"

Inserting specific keys into the Registry settings for your control supports all of the component categories, and you'll typically add these keys when your control self-registers. The specific values are defined by inserting the following header files into your control code (typically in stdafx.h):

#include <comcat.h> // component category includes
#include <objsafe.h> // IObjectSafety and component category includes

A brief discussion of this topic may be found in "Signing and Marking ActiveX Controls with ATL " at the Microsoft site (URL below). The code to actually insert the values into the Registry can be found in "Safe Initialization and Scripting for ActiveX Controls " at Microsoft.

5.3 "What is IObjectSafety?" "Why do I get 'unsafe to initialize/script' when my control loads in Internet Explorer 4.0/4.01?"

IObjectSafety is a Component Object Model (COM) interface Internet Explorer and other containers may use to query the particular safety levels your control provides. While it helps to understand COM when programming COM interfaces, you'll find a fairly good description and the code required to support IObjectSafety in Frank Crocket's book "MFC Developer Studio". You could cut and paste his code to your control and support IObjectSafety rather easily.

IObjectSafety was optional for Internet Explorer 3, but appears not to be so for version 4. You must support IObjectSafety to keep from getting the 'unsafe' dialogs when browsing your control(s).

6. Control Packaging

6.1 "How do I package a control for download over the Internet?"

Oh boy, the big can of worms. <g> Well, first, support component categories, especially the 'safe for' initialization and scripting categories. Then build into your code IObjectSafety. Assuming you've done that, then you need to apply for a digital signature. For this, you need to apply for a "certificate" from a "trusted" third party, such as Verisign. Once you pay for your certificate, then you need to build a "cabinet" file.

Microsoft provides you with the means to build cabinet files when you purchase Developer Studio/Visual C++. If you "explore" your Visual C++ CD, you'll see a subdirectory called "Cab&Sign". Here are the tools you need…you have the compression/creation program " CabArc " and the digital signature application " SignCode ", and other support programs you may require. We'll address digital signatures in the next section. In this answer, our goal is to actually generate a correct cabinet file.

Let's assume you are happy with your control's operation and are ready to put it on the 'Net. First, be sure you compile a Release build--release builds are smaller, and to be honest, you probably don't want debugging information "out there" regarding your control. Once you have your release build version, you need to create a INF file. This ASCII text file contains a scripting language used by Windows to install the control. This is the INF file we use at EnduraSoft (this example is for the ClassX Rocker Switch):

; Version signature (same for both NT and Win95) -- do not remove
[version]
signature="$CHICAGO$"
AdvancedINF=2.0

[Add.Code]
; This control
rcswitch.ocx=rcswitch.ocx
; Dependent DLLs
msvcrt.dll=msvcrt.dll
mfc42.dll=mfc42.dll
olepro32.dll=olepro32.dll

[rcswitch.ocx]
file-win32-x86=thiscab
clsid={8D44BF9C-C166-11D0-8CD1-00A0C924B8E4}
FileVersion=1,1,0,1
RegisterServer=yes
; dependent DLLs
[msvcrt.dll]

; This is an example of conditional hook. The hook only gets processed
; if msvcrt.dll of the specified version is absent on client machine.
; Note these files are valid for Visual C++, version 5.0, SP1.
FileVersion=5,0,0,7128
hook=mfc42installer

[mfc42.dll]
FileVersion=4,21,0,7160
hook=mfc42installer

[olepro32.dll]
FileVersion=5,0,4055,1
hook=mfc42installer

[mfc42installer]
file-win32-x86=http://activex.microsoft.com/controls/vc/mfc42.cab
; If dependent DLLs are packaged directly into the above cabinet file
; along with an .inf file, specify that .inf file to run as follows:
;InfFile=mfc42.inf
; The mfc42.cab file actually contains a self extracting executable.
; In this case we specify a "run=" command.
run=%EXTRACT_DIR%\mfc42.exe

The [version] section tells the installation routines you're installing this control in Windows 95 or NT 4.0/5.0 and you're using the 2.0 version of the installation language.

The [Add.Code] section tells the installation routines what will be installed. In this case, we have our control file, rcswitch.ocx, and the MFC DLLs we require to run the control. Note if the user's system does not have these files, or if they are outdated, we'll upgrade them here. We have to, since we require them for our own control to run correctly. The format of these lines always appear in this form: file=section.

Assuming you've copied the CabArc utility to a location in your path, use this command line (substituting your control filename, of course):

> cabarc -s 6144 n mycab.cab mycontrol.ocx mycontrol.inf

This command tells CabArc to insert room for a digital signature ("-s 6144") and create a new cabinet file ("n mycab.cab"). It will insert the files that follow, so we’ll insert the control and its associated INF file.(personally, I forget this command line all of the time, so I use a batch file).

If the program executed correctly, you’ll now have a cabinet file in the current directory named "mycab.cab". If you intend to apply a digital signature to the cabinet file, follow onto the next section. If you had no intention of providing a digital signature, you won’t need the "-s 6144" on the CabArc command line and may eliminate that to recoup some space.

7. Control Signing

7.1 "I get this weird error '0x80004005' when applying a digital signature to my cabinet file. What is it and why do I get it?"

The error is an "unspecified COM error", whatever that may mean (from winerror.h), and you are most likely receiving it using SignCode with a cabinet file not created by cabarc (you used MakeCab from the ActiveX SDK, for example). If you use the SignCode utility from the "Cab&Sign" directory of your Visual C++ CD, you must also use the CabArc program to create the cabinet file. MakeCab evidently generates a cabinet file with a different format.

7.2 "How do I digitally sign an ActiveX control for download over the Internet?"

Assuming you’ve completed the necessary steps from Section 6, and you have obtained your digital certificate from Verisign, you now apply the digital certificate to your cabinet file. You’ll use the "SignCode" utility found in the "Cab&Sign" directory of your Visual C++ CD-ROM. Simply run the utility:

> signcode

This will execute a console-based Windows program which will guide you through the steps required to actually apply the signature. You will need to have the cabinet file, your digital certificate, and your private key handy. (Note the private key will be generated when you apply for the digital certificate.) SignCode is a digital certificate application wizard, so it’s easy to use and not too scary. <g>