Using Containment In ATL - A Complete Application

This article is another in the series of tutorials which I did a couple of months ago (http://www.codeguru.com/atl/atlclienttut.shtml and http://www.codeguru.com/atl/activex_tut.shtml). This article focuses on how to use containment for containing small COM objects inside one COM object. The application, which I have used for this project, has been previously posted in System category (http://www.codeguru.com/system/HardwareInfo.shtml). I have added some new information to that application. I would be briefly discussing the various API calls used for the new information presented.

The interfaces (projects) included in the application are as follows:

ISystemInfo - Outer COM object containing al other small COM objects
IOSInformation - Interface implementing OS information
ICPUInformation - Interface implementing information relating to CPU
IHDiskInformation - Interface implementing Hard disk (partition) information
IStorageInformation - Interface implementing information about all storage devices on system
IMemeoryInformation - Interface implementing information about memory of system
IMultiMediaInformation - Interface implementing information audio device on system
IMiscInformation - Interface implementing information not covered by other interfaces

All the inner objects and inner object is automation compliant. I will discuss only one of the inner ones and the outer object.

IHDiskInformation Interface:

This interface gathers the information about all the disk partitions on the system. The steps followed are:
1. In the work space, add new project.
2. Choose ATL COM Appwizard. Give name to the project. In this case it was given SystemHDisk. Click on Add To Current Workspace radio button. Click OK.
3. In next step, choose all the default options i.e. we want to make DLL. And finish.
4. This will add the project to the workspace.
5. Click on Insert menu option at the top.
6. Click on New ATL Object.
7. From the different options available, choose Simple Object. Since we are not making a full ActiveX control, Simple Object will serve our purpose.
8. On Names property page of ATL Object Wizard Properties, fill in a meaningful name for the interface. In this case, I used HDiskInformation. This will automatically suffix the short name with "I" to give the interface name (e.g. IHDiskInformation in this case).
9. On the Attributes page, keep all the default options and in addition check the Support ISupportErrorInfo check box. Checking this option means that the interface will implement the error-reporting interface too.
10. After filing all the information, click OK and this would add the interface to our project.

Now we are ready for implementation of the interface. Add properties and method definitions to the interface. For IHDiskInformation, the IDL file looks like this:



[
	object,
	uuid(5EEB2D5E-D720-11D2-80FD-00105AC8B8F6),
	dual,
	helpstring("IHDiskInformation Interface"),
	pointer_default(unique)
]
interface IHDiskInformation : IDispatch
{
[propget, id(1), helpstring("property NumberOfPartitions")] HRESULT NumberOfPartitions([out, retval] long *pVal);
[propget, id(2), helpstring("property Bootable")] HRESULT Bootable([out, retval] VARIANT *pVal);
[propget, id(3), helpstring("property Letter")] HRESULT Letter([out, retval] VARIANT *pVal);
[propget, id(4), helpstring("property PartitionType")] HRESULT PartitionType([out, retval] VARIANT *pVal);
[propget, id(5), helpstring("property PartitionNumber")] HRESULT PartitionNumber([out, retval] VARIANT *pVal);
[propget, id(6), helpstring("property PartitionLength")] HRESULT PartitionLength([out, retval] VARIANT *pVal);
[propget, id(7), helpstring("property HiddenSectors")] HRESULT HiddenSectors([out, retval] VARIANT *pVal);
[id(8), helpstring("method GetHDiskInformation")] HRESULT GetHDiskInformation(long *plNumberOfPartitions, VARIANT *pbstrDriveLetterArr, VARIANT *pbBootableArr, VARIANT *pbstrTypeArr, VARIANT *plPartitionNumberArr, VARIANT *plLengthArr, VARIANT *plHiddenSectorsArr);
};

As is clear from the IDL file that interface implements 7 properties (NumberOfPartitions, Bootable, Letter, PartitionType, PartitionNumber, PartitionLength, HiddenSectors) and 1 method (GetHDiskInformation). All the properties give the different types of partition information individually. And if the user wants all the information about all the partitions with one call, the method call will be very useful. I had my reasons to implement it like this. If I want to run these interfaces over DCOM then I would like to reduce the number of calls over the network. And getting all the information with one function call definitely serves that purpose. For the information types, I used VARIANT data type to make it automation compatible e.g. Bootable property, the argument VARIANT has an array of VT_BOOL type. You can look at the code what different VARIANT data types for this interface correspond to.

To extract information for each partition, first I made GetLogicalDrives API call to get all the logical drives on the system. Then I used GetDriveType API call to get the type of drive associated with the drive letter. If the drive type is DRIVE_FIXED, it means it's a hard disk partition. Then for this drive make DeviceIoControl API call with dwIoControlCode argument set to IOCTL_DISK_GET_PARTITION_INFO. This will give all the information about the disk pertition. For all the argument types, check on the help for this call. After we have obtained all the information with the appropriate API calls, fill the VARIANT arrays with corresponding information.

ISystemInfo Interface:
This is the outer interface, which contains all the inner COM objects/interfaces. We follow the same steps as we did for IHDiskInterface interface. We will not have any properties for this interface because this interface doesn't implement anything by itself. If you look in SystemInfo.idl file, it only has definition for 10 methods. And each method corresponds to their counterpart in individual inner COM objects. E.g. we have GetHDiskInformation method in IHdiskInformation interface. This interface is contained ISystemInfo interface. Therefore ISystemInfo interface has the same method with same arguments. It redirects the call to the inner object to get the method implementation.

Containment:

To implement containment, the outer object has to implement FinalConstruct of CComObjectRootEx Class. This is the place where all the contained objects will be constructed. For the application attached, the implementation looks like this.



HRESULT
CSystemInformation::FinalConstruct ()
{
	HRESULT hr;

hr = CoCreateInstance (CLSID_OSInformation, 0, CLSCTX_INPROC_SERVER, IID_IOSInformation, (void **) &m_pOSInfo);
	if (FAILED (hr)) {
		return E_NOINTERFACE;
	}

	hr = CoCreateInstance (CLSID_MouseInformation, 0, CLSCTX_INPROC_SERVER,
		IID_IMouseInformation, (void **) &m_pMouseInfo);
	if (FAILED (hr)) {
		return E_NOINTERFACE;
	}

	hr = CoCreateInstance (CLSID_MemoryInformation, 0, CLSCTX_INPROC_SERVER,
		IID_IMemoryInformation, (void **) &m_pMemoryInfo);
	if (FAILED (hr)) {
		return E_NOINTERFACE;
	}
	.
	.
	.
	m_pOSInfo->AddRef ();
	m_pMouseInfo->AddRef ();
	.
	.
}

FinalConstruct is called before the outer object completes its construction. Don't forget to call AddRef for each constructed object. Since we have constructed and AddRef'd the inner interfaces, we need some place where we can release all of these. ATL provides FinalRelease call in CcomObjectRootEx which gets called on outer object before its release and unloading. The outer object will have to provide its implementation. In the aplication the implementation looks like this.


void
CSystemInformation::FinalRelease ()
{
	if (m_pOSInfo != NULL) {
		m_pOSInfo->Release ();
	}

	if (m_pMouseInfo != NULL) {
		m_pMouseInfo->Release ();
	}

	if (m_pMemoryInfo != NULL) {
		m_pMemoryInfo->Release ();
	}
	.
	.
}

As you can see that Release being called on each inner interface to counter each AddRef for these.

Client Application:

I hace created an MFC project to for implementation of the application. The application has been created as Dialog based application with Automation support. The dialog box encapsulates a property sheet that contains 8 property pages for each inner COM object of the ISystemInfo interface. This interface pointer gets created when the application is launched. It is being done in OnInitDialog function of CsystemApplicationDlg class. It calls CreateInterfacePointer function. The implementation of this function looks like this.


void CSystemApplicationDlg::CreateInterfacePointer ()
{
	HRESULT hr = E_FAIL;

	hr = CoCreateInstance (CLSID_SystemInformation, NULL, CLSCTX_INPROC, IID_ISystemInformation,
		(void **) &m_pSystemInfo);
	if (FAILED (hr) || m_pSystemInfo == NULL) {
		AfxMessageBox (_T ("Application Failed to create SystemInformation Intrface"),
			MB_ICONSTOP);
		VERIFY (FALSE);
	}

	m_pSystemInfo->AddRef ();
}

After successful creation of interface pointer we need to call AddRef on it to maintain reference count. And before the application terminates we will have to call Release on this interface. This has been done in the dialog box destructor.


CSystemApplicationDlg::~CSystemApplicationDlg()
{
	// If there is an automation proxy for this dialog, set
	//  its back pointer to this dialog to NULL, so it knows
	//  the dialog has been deleted.
	if (m_pAutoProxy != NULL)
		m_pAutoProxy->m_pDialog = NULL;

	// Release the interface pointer.

	if (m_pSystemInfo != NULL) {
		m_pSystemInfo->Release ();
		m_pSystemInfo = NULL;
	}
}

The final outcome of the application looks like:

Demo Project

The first page is showing information about CPU of my system.

Project Compilation:

Although I have set all the depencies for the attached application code, but here is what it should like. Some of the projects need some special lib files to be included.

Project dependcies:


SystemApplication
    SystemInfo
        SystemOS
        SystemCPU
           SystemOS
        SystemMouse
        SystemHDisk
        SystemStorage
        SystemMultiMedia
        SystemMisc

Lib inclusions:

winmm.lib - SystemMultiMedia

Header Includes:
SystemHDisk


#include <winioctl.h> - 

SystemMultiMedia
#include <mmsystem.h>
#include <mmreg.h>

Project has been compiled with VC++5 (SP 3) on WinNT 4.0 (SP 3). Some of the API calls used are not available on Win95.

Download source - 158 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

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

RSS Feeds