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 
/*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



Comments

  • 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

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds