ATL Client Application Tutorial

In my previous article "ActiveX Control Tutorial", I tried to explain how to write a complete ActiveX control. At the end of the article I included two examples to show how this control can be used in different application. I didn't explain how we can create a project which will act as container for out ActiveX control. In this tutorial I will try to give steps involved in writing a container application.

1. Start a new "ATL COM AppWizard" project named "AtlClientApp."

2. In step 1 of the ATL AppWizard, choose "Executable (EXE)" for the Server Type and then click Finish.

3. From the Insert menu, select "New ATL Object" to bring up the ATL Object Wizard. For the type of Object to insert, select Miscellaneous and pick Dialog to a add a Dialog Object to the project. Click Next to continue.

4. Provide a short name of "ClientDlg" for your new dialog. Accept the defaults for all other names and click OK.

5. In the project workspace, click the "Resource View" tab. Double-click "AtlClientApp Resources" to expand the resource tree. Double-click Dialog in the Resource tree and double-click to select the dialog box resource "IDD_CLIENTDLG."

6. Right click inside the dialog box. Select Insert ActiveX Control option from the pop up menu. This will give you list of all the ActiveX controls registered on your system.

Click on ShapeCtl Class control and hit OK. This will insert the control we created in ActiveX Control tutuorial. If you don't have that control on your machine, then download the source from that article. Compile it and the control will get registered on your machine. Otherwise I have included the compiled DLL in this application's source code. From the command prompt, use the following command to register the control.
regsvr32 ActiveXCtl.dll

Click on the View, Properties menu in the Visual Studio IDE. This will give you the property page to manipulate the design time properties of the control. You can choose the different options to configure the look of the control.

7. In the project workspace, click the "Class View" tab. Double-click the "Globals" folder to see the "_tWinMain" entry point, then double-click "_tWinMain" to jump to the code location.

8. Replace all the code in the function with the following:

extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, 
    HINSTANCE /*hPrevInstance*/, LPTSTR lpCmdLine, int 
    lpCmdLine = GetCommandLine(); //this line necessary for   

#if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
    HRESULT hRes = CoInitialize(NULL);
    _Module.Init(ObjectMap, hInstance, &LIBID_ATLCLIENTAPPLib);
    _Module.dwThreadID = GetCurrentThreadId();
    int nRet = 0;

    // Instantiate a new instance of the dialog box which is 
going to contain the ActiveX control.

	CClientDlg *pDlg = NULL;
	pDlg = new CClientDlg;
	if (NULL != pDlg) {
		pDlg->Create (NULL);
		pDlg->ShowWindow (SW_NORMAL);

		MSG msg;

		// Now we need to run the message loop to recieve the 
messages/events fired for our dialog
		// box.

		while (GetMessage (&msg, NULL, 0, 0)) {
			TranslateMessage (&msg);
			DispatchMessage (&msg);

		nRet = msg.wParam;
		delete pDlg;

    return nRet;

Since we are using this project to be our control container, we don't need any code that does the registration of this project's ATL object. Therefore all the code corresponding to that has been removed form the WinMain function.

9. At the top of the file, add the following include statement after the others:

      #include "ClientDlg.h"

10. Because we are building a client application, we do not need to perform COM registration when we compile the EXE. To remove this step, select "Settings" from the Project menu, go to the "Custom Build" tab, and remove the all the commands that appear in the "Build Commands" window. Do the same for the "Outputs" window, and click OK when done.

Now if you compile and run the application, the dialog box comes up with our ActiveX control placed in it. As I mentioned in the earlier article that we implemented four events for out control, ClickIn, ClickOut, DblClickIn and DblClickOut. aNd if you try to click inside or outside the control nothing happens. We need to include these events in our message map and establish a contact with our control to receive these event messages. Follow the following steps to include these event messages in our client application.

For each external object whose events you wish to handle, you must import the type library. This step defines the events that can be handled and provides information that is used when declaring the event sink map. The #import directive can be used to accomplish this. Add the necessary #import directive lines for each dispatch interface you will support to the header file (.h) of your COM class. The following example imports the type library of our COM server (ActiveXCtl):

#import"ActiveXCtl.dll" raw_interfaces_only, no_namespace, named_guids

1. In the project workspace, goto ClassView and right click on CclientDlg class. Click on Add Windows Message Handlers options. This is the same step we do for implementing regular windows controls.

2. In the Class or object to handle window, click on resource id of ActiveX control. If you did not specify any ID, it would be IDC_SHAPECTL1. You will see four events in New Windows messages/events window, namely ClickIn, ClickOut, DblClickIn, DblClickOut. Click on these events messages one by one and then click on AddHandler button. Accept the default names for the functions, offered by IDE. You can have names of your own, but to match your code with the one included with this article, accept the default names. You don't have to implement all the events. Just for the sake of using all these events, I included each one of these in my application.

3. ATL uses the template class IDispEventImpl to provide support for connection points in your ATL COM object. A connection point allows your COM object to handle events fired from external COM objects. These connection points are mapped with an event sink map, provided by your COM object.

class CClientDlg : 
	public CAxDialogImpl>CClientDlg<,
	public IDispEventImpl>IDC_SHAPECTL, CClientDlg<

The class inheritances for the container application will look like this after the four events have been added. If you want to support some more events for your control, add IDispEventImpl definition for each one of those in the control inheritance.

4. In order for the event notifications to be handled by the proper function, your COM object must route each event to its correct handler. This is achieved by declaring an event sink map. ATL provides several macros, BEGIN_SINK_MAP, END_SINK_MAP, and SINK_ENTRY, that make this mapping easier. The standard format is as follows:

   SINK_ENTRY(id, dispid, func)
. . . //additional external event entries

5. When you add these four events to your container, wizard puts four sink entries in the ClientDlg.h file. Make sure the following four lines are there in the file.

	//Make sure the Event Handlers have __stdcall calling convention
	SINK_ENTRY(IDC_SHAPECTL, 0x1, OnClickInShapectl)
	SINK_ENTRY(IDC_SHAPECTL, 0x2, OnClickOutShapectl)
	SINK_ENTRY(IDC_SHAPECTL, 0x3, OnDblClickInShapectl)
	SINK_ENTRY(IDC_SHAPECTL, 0x4, OnDblClickOutShapectl)

In order for the event notifications to be handled by the proper function, your COM object must route each event to its correct handler. This is achieved by declaring an event sink map.

6. The final step is to implement a method that will advise (or unadvise) all connection points at the proper times. This advising must be done before communication between the external clients and your object can take place. Before your object becomes visible, each external dispatch interface supported by your object is queried for outgoing interfaces. A connection is established and a reference to the outgoing interface is used to handle events from the control. This procedure is referred to as "advising."

After your object is finished with the external interfaces, the outgoing interfaces should be notified that your COM object no longer uses them. This process is referred to as "unadvising". Because of the unique nature of COM objects, this procedure varies. ATL has made this procedure by providing some macros to do the work of advising and unadvising for us.

In Initialization of dialog box, establish the advise connection with control by using AtlAdviseSinkMap macro. In this function we will obtain the IUnknown pointer of our ShapeCtl and cache it so that we can use it to call other methods on the control. To accomplish make use of the AtlAxGetControl macro which takes HWND handle of the control window. Use GetDlgItem API call to get his window handle. The function should look like this

CClientDlg::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	// Cache the pointer to shape control.
	m_ShapeCtlWnd = GetDlgItem (IDC_SHAPECTL);
	// Get the unknown pointer.
	AtlAxGetControl (m_ShapeCtlWnd, (IUnknown **) &m_pShapeCtl);
	// Make the connecton to control's IControlContainer interface.
	AtlAdviseSinkMap (this, TRUE);
	return 1;  // Let the system set the focus

7. Honoring the COM specification we will implement unadvising of the IDispatch interface before the container is destroyed or released. ATL has provided macros to that job. We will use the same AtlAdviseSinkMap macro to do that in OnCancel function.

CClientDlg::OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
	AtlAdviseSinkMap (this, FALSE);
	DestroyWindow ();
	PostQuitMessage (0);
	return 0;

Now you can compile and run your container application. I have put some code inside two of the four events. When you click inside the control, it redraws itself with a radius increased by 5 units. And if you click outside the control, radius is decreased by 5 units and control is redrawn. To obtain the current value of control radius, we have made use of the IUnknown pointer of the control which we saved at the initialization of dialog box.

hr = m_pShapeCtl->get_Radius (&radius);
if (FAILED (hr)) {
MessageBox (_T ("Failed to get radius of control"), _T ("Information"), MB_OK);

This makes use of the vtable of control server. Containers implemented in VB or scripting languages like VBScript, JScript, makes use if IDispatch::Invoke to implement this control properties manipulation or event handling. In the next article (very soon) I will try to explain this technique too.

The included code has been compiled and tested with VC++ 6.0 (SP-1) on NT-4.0 (SP-4).

Download Project - 50 KB


  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • For many organizations, moving enterprise applications to the public cloud can be a very attractive proposition, but planning the best way to move your applications is mission–critical. As an alternative to the costly option of re–architecting the application for a cloud environment, you can follow a "lift and shift" model that's significantly cheaper and almost always a lot quicker. In order to have a successful "lift and shift" migration, read this white paper to learn a few rules you should …

  • Entire organizations suffer when their networks can't keep up and new opportunities are put on hold. Waiting on service providers isn't good business. In these examples, learn how to simplify network management so that your organization can better manage costs, adapt quickly to business demands, and seize market opportunities when they arise.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date