This article is another in the series of tutorials which
I did a couple of months ago (https://www.codeguru.com/atl/atlclienttut.shtml and
https://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 (https://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:
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.