Creating an ActiveX Control with .NET for Internet Explorer

With the Visual Studio .NET IDE it is easy to create an ActiveX control, but there are some pitfalls and tricks, so I've created this step-by-step tutorial to help other programmers. The control shows a text, which is provided by a PARAM-tag within the object tag in a web page. Source code with project files: HelloWorld.zip

Step-by-step tutorial

Select File->New->Project...

Select the "ATL Project" icon and enter "HelloWorld" for the name

Hit "OK" and commit the next screen with "Finish". All default-settings are OK.

Project->Add Class...: Select "ATL Control", Click "OK"

Enter "HelloWorldCtl":

Check the "Insertable"-Checkbox in the Appearance-Screen:

Commit with "Finish"

Open the interface in the class viewer and add a property with the context menu "Add->Property" of the IHelloWorldCtl interface:

Property type: BSTR, name: text

Commit with "Finish".

Add a member variable for the text-property, show the content in the sample draw method and add the IPersistPropertyBagImpl class as a base class for using the PARAM from the HTML page to set the property:

Add the bold lines in the file HelloWorldCtl.h and replace two lines in the drawing method by the two bold lines:

BEGIN_PROP_MAP(CHelloWorldCtl)
PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
PROP_ENTRY("text", 1, CHelloWorldCtl::GetObjectCLSID())
// Example entries
// PROP_ENTRY("Property Description", dispid, clsid)
// PROP_PAGE(CLSID_StockColorPage)
END_PROP_MAP()
...
class ATL_NO_VTABLE CHelloWorldCtl :
...
public CComControl<CHelloWorldCtl>,
public IPersistPropertyBagImpl<CHelloWorldCtl>
...

SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE);
CW2A pszA(m_text);
TextOut(di.hdcDraw,
(rc.left + rc.right) / 2,
(rc.top + rc.bottom) / 2,
pszA,
lstrlen(pszA));
...
STDMETHOD(get_text)(BSTR* pVal);
STDMETHOD(put_text)(BSTR newVal);
private:
CComBSTR m_text;
};

Implement the getter and setter in HelloWorldCtl.cpp:

STDMETHODIMP CHelloWorldCtl::get_text(BSTR* pVal)
{
  *pVal = m_text.Copy();
  return S_OK;
}

STDMETHODIMP CHelloWorldCtl::put_text(BSTR newVal)
{
  m_text = newVal;
  return S_OK;
}

Using BSTR is a little bit tricky: If a parameter in COM is declared as "out", like in the get_text method, you get a pointer of a pointer of the value the caller wants and you have to create a new object, which is freed by the caller. If a parameter is declared as "in", like in the put_text method, you get a pointer of the value (think of BSTR as a "const char*") and you have to create your own object and copy the content. See http://www.michaelmoser.org/eightrules.htm for details. The CComBSTR class and the CW2A macro/class helps you a bit by encapsulating the details.

Now you can build the project. On my computer I had problems building it in debug mode, because there was a "fatal error LNK1113: invalid machine type 0xFF", but I fixed it by setting "Basic Runtime Check" in the project properties to "Default":

The control will be registered for you automaticly. Edit the automaticly generated test-page "HelloWorldCtl.htm" and add the "text"-parameter:

<HTML>
<HEAD>
<TITLE>ATL 7.0 test page for object HelloWorldCtl</TITLE>
</HEAD>
<BODY>

<OBJECT ID="HelloWorldCtl" CLASSID="CLSID:59E937ED-AC7E-407D-B40B-6545B1EECDE7">
<PARAM name="text" value="Hello World!">
</OBJECT>

</BODY>
</HTML>

Now you can view your control in the Internet Explorer and you should see the following, if you answer the warning question with "Yes":

If your control is safe, you can add the IObjectSafetyImpl class and you don't get the warning dialog (edit the file HelloWorldCtl.h):

public IPersistPropertyBagImpl<CHelloWorldCtl>,
public IObjectSafetyImpl<CHelloWorldCtl, INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA>


8. July 2002, Frank Buß