ATL Client Application Tutorial

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

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
/*nShowCmd*/)
{
lpCmdLine = GetCommandLine(); //this line necessary for
_ATL_MIN_CRT

#if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
#else
HRESULT hRes = CoInitialize(NULL);
#endif
_ASSERTE(SUCCEEDED(hRes));
_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;
}

CoUninitialize();
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:


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

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.


BEGIN_SINK_MAP(CClientDlg)
//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)
END_SINK_MAP()

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


LRESULT
CClientDlg::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
HRESULT hr = E_FAIL;
// 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.


LRESULT
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);
return;
}

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

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read