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
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);
Implement the getter and setter in HelloWorldCtl.cpp:
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;
};
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>