Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

A Custom COM Class Example

As an example of how custom COM classes are created with IDL and the Active Template Library, the CD-ROM that accompanies this book includes OsVersionInfo, a custom COM class that is a wrapper around the Win32 GetVersionEx function.

The OsVersionInfo project creates an EXE module that includes a COM class named OsVersion created using the ATL Object Wizard. Table 30.1 lists the values used in the Names property sheet for the OsVersion ATL class.

Table 30.1. Names Property Sheet Values for OsVersion

Field Name
Short Name OsVersion
Class COsVersion
H File OsVersion.h
CPP File OsVersion.cpp
CoClass OsVersion
Interface IOsVersion
Type OsVersion Class
Prog ID OsVersionInfo.OsVersion

Table 30.2 lists the values used on the Attributes property page for OsVersion.

Table 30.2. Attributes Property Sheet Values for OsVersion

Field Name
Threading Model Both
Interface Custom
Aggregation Yes
ISupportErrorInfo Unchecked
Connection Points Unchecked
Free Threaded Marshaler Unchecked

Defining the IOsVersion Interface

The IOsVersion interface has a number of methods, as shown in the IDL provided in Listing 30.1. Lines that were added to the original code generated by the wizard are shown in bold.

Example 30.1. OsVersionInfo IDL File

import "oaidl.idl";
import "ocidl.idl";

typedef enum tagOsType

   {

      OsUnknown = 0,

      OsWin32s,

      OsWin95,

      OsWin98,

      OsWinNt,

   }OsType;


   typedef struct tagOsVerNum

   {

      long Major;

      long Minor;

   }OsVerNum;

    [
        object,
        uuid(414AE921-EAFA-11D3-8E67-00C04F8DC7A5),

        helpstring("IOsVersion Interface"),
        pointer_default(unique)
    ]
    interface IOsVersion : IUnknown
    {
       HRESULT GetVersionInfoString([out,retval]BSTR* pBstrInfo);

          HRESULT GetVersion([out]OsVerNum *pVerNum);

          HRESULT GetBuildNumber([out]long* pBuild);

          HRESULT GetOsType([out]OsType* pType);
    };







[
    uuid(414AE913-EAFA-11D3-8E67-00C04F8DC7A5),
    version(1.0),
    helpstring("OsVersionInfo 1.0 Type Library")
]
library OSVERSIONINFOLib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");

   struct OsVerNum;

      enum OsType;

    [
        uuid(414AE922-EAFA-11D3-8E67-00C04F8DC7A5),
        helpstring("OsVersion Class")
    ]
    coclass OsVersion
    {
        [default] interface IOsVersion;
    };
};

Listing 30.1. contains two typedefs:

The OsType and OsVerNum types are referenced in the library section of the IDL file. This causes the MIDL compiler to include these two types in the type library when the IDL is compiled.

Modifications to the COsVersion Declaration

The definition for the COsVersion class is located in the OsVersion.h header file, portions of which are shown in Listing 30.2. Modifications to the wizard-generated source are shown in bold.

Example 30.2. Definition of the COsVersion Class

class ATL_NO_VTABLE COsVersion :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<COsVersion, &CLSID_OsVersion>,
    public IOsVersion
{
public:
    COsVersion()
    {
    }

DECLARE_REGISTRY_RESOURCEID(IDR_OSVERSION)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(COsVersion)
    COM_INTERFACE_ENTRY(IOsVersion)
END_COM_MAP()

   HRESULT FinalConstruct();

// IOsVersion
public:
   STDMETHOD(GetOsType)(OsType* pType);

      STDMETHOD(GetBuildNumber)(long* pBuild);

      STDMETHOD(GetVersion)(OsVerNum* pVerNum);

      STDMETHOD(GetVersionInfoString)(/BSTR* pBstrInfo);

   protected:

      OSVERSIONINFO m_info;
};


   

The FinalConstruct method declared in Listing 30.2 is called by the framework after the COM object is constructed. This enables you to perform initialization work, such as calling a virtual function that isn't safe in a constructor. It also allows you to bypass the constructor completely—a technique used when omitting the C runtime library from your component.

Implementing the COsVersion Class

The implementation of the COsVersion class is located in the OsVersion.cpp source file. A partial listing of this file is provided in Listing 30.3. The accompanying CD-ROM includes the complete source for this file.

Example 30.3. Implementation of the COsVersion Class

HRESULT COsVersion::FinalConstruct()
{
    ZeroMemory(&m_info, sizeof(OSVERSIONINFO));
    m_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx(&m_info);
    return S_OK;
}

STDMETHODIMP COsVersion::GetVersionInfoString(BSTR *pBstrInfo)
{
    CComBSTR bstrTemp(m_info.szCSDVersion);
    if(!bstrTemp)
        return E_OUTOFMEMORY;
    *pBstrInfo = bstrTemp.Detach();
    return S_OK;
}

STDMETHODIMP COsVersion::GetVersion(OsVerNum *pVerNum)
{
    pVerNum->Major = m_info.dwMajorVersion;
    pVerNum->Minor = m_info.dwMinorVersion;
    return S_OK;
}

STDMETHODIMP COsVersion::GetBuildNumber(long *pBuild)
{
    // If the OS is Windows 9x, the build is in the lower
    // word of dwBuildNumber. If the OS is Windows NT/2000
    // the build is the entire DWORD. For Win32s, the build
    // number is cleared.
    if(m_info.dwPlatformId == VER_PLATFORM_WIN32_NT)
        *pBuild = m_info.dwBuildNumber;
    else if(m_info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
        *pBuild = LOWORD(m_info.dwBuildNumber);
    else
        *pBuild = 0;
    return S_OK;
}







STDMETHODIMP COsVersion::GetOsType(OsType *pType)
{
    switch(m_info.dwPlatformId)
    {
        case VER_PLATFORM_WIN32s:
  *pType = OsWin32s;
  break;

        case VER_PLATFORM_WIN32_WINDOWS:
  if(m_info.dwMinorVersion == 0)
      *pType = OsWin95;
  else
      *pType = OsWin98;
  break;

        case VER_PLATFORM_WIN32_NT:
  *pType = OsWinNt;
  break;

        default:
  *pType = OsUnknown;
  break;
    }
    return S_OK;
}


   

When a COM object is constructed, the ATL framework calls the FinalConstruct method . Inside COsVersion::FinalConstruct, the Win32 GetVersionEx function is called to collect version information from the operating system and store it in the m_info member variable. Other method calls return information to the client based on the contents of m_info.

Build the OsVersionInfo project. After the OsVersionInfo module is compiled successfully, the project takes steps to register the component on your system. You won't be able to use the component from a client, however, until you build and register the proxy/stub DLL, as described in the next section.

Compiling and Registering the Standard Proxy/Stub DLL

ATL projects include a command-line makefile used to build the proxy/stub DLL for each ATL project. This makefile is the only file in the project directory with an .mk filename extension. For the OsVersionInfo project, the name of the makefile is OsVersionInfoPs.mk. To build the DLL, use the NMAKE utility, like this:

nmake OsVersionInfoPs.mk

After the proxy/stub DLL is built successfully, you can register the DLL using the RegSvr32 utility:

regsvr32 OsVersionInfoPs

You should run both of these commands in a DOS command window from the project directory. In order to compile the proxy/stub DLL from a command window, you may need to run the vcvars32.bat file located in the Visual C++ bin directory. The default path to this file is:

\Program Files\Microsoft Visual Studio\VC98\bin\vcvars32.bat

If the DLL is registered successfully, a message box appears, as shown in Figure 30.4.

30fig04.gif

Figure 30.4 The RegSevr32 utility is used to register the proxy/stub DLL.

Creating Test Clients for OsVersionInfo

To facilitate testing of the OsVersionInfo COM object, the CD-ROM that accompanies this book includes OsInfoClient, a console-mode test driver. OsInfoClient calls each of the IOsVersion interface functions and displays the results to the console window.

The OsInfoClient project was built as a Win32 console application. Unicode compatibility macros are used to enable the project to be built as either a Unicode or ANSI project. The project uses the osversioninfo_i.c and osversioninfo.h files from the OsVersionInfo project directory. The MIDL compiler generates these two files when the IDL file is compiled; they contain information about the GUIDs and interface declarations used by the OsVersionInfo COM object.

The OsInfoClient project contains the main.cpp source file, portions of which are provided in Listing 30.4. The accompanying CD-ROM includes the complete source for this project.

Example 30.4. OsInfoClient Test Driver

#define _WIN32_DCOM

#include <windows.h>
#include <tchar.h>
#ifndef  UNICODE
    #include <stdio.h>
#endif
#include <atlbase.h>
#include "osversioninfo_i.c"
#include "osversioninfo.h"

//  Display a dialog box that contains an error message
void HandleError(LPCTSTR pszTitle, HRESULT hr);

// Macro that tests an HRESULT - if an error exists, a dialog box
// with the error message is displayed, and zero is returned.
#define TESTHR(hr,str) if(FAILED(hr)){HandleError(str,hr);return 0;}

// Utility class that ensures that CoInitialize and CoUninitialize are
// called at the appropriate times.
struct CComInit
{
    HRESULT hr;
    CComInit(DWORD dwInit)
    {
        hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    }
    ~CComInit()
    {
        CoUninitialize();
    }
};

CComInit init(COINIT_MULTITHREADED);

int _tmain()
{
    // Uses the AFX string conversion macros to convert between
    // OLE/BSTR strings and T strings.
    USES_CONVERSION;
    CComQIPtr<IOsVersion> pVersion;

    HRESULT hr = pVersion.CoCreateInstance(CLSID_OsVersion);
    TESTHR(hr, _T("CoCreateInstance"));

    OsVerNum ver;
    hr = pVersion->GetVersion(&ver);
    TESTHR(hr, _T("GetVersion"));

    // Get the OS type, and convert the enumerated type into a
    // string.
    OsType ost;
    hr = pVersion->GetOsType(&ost);
    TESTHR(hr, _T("GetOsType"));

    CComBSTR bstrOsType;
    switch(ost)
    {
        case OsWin32s:
  bstrOsType = _T("Win32s");
  break;
        case OsWin95:
  bstrOsType = _T("Windows 95");
  break;
        case OsWin98:
  bstrOsType = _T("Windows 98");
  break;
        case OsWinNt:
  bstrOsType = _T("Windows NT/2000");
  break;
        default:
  bstrOsType = _T("Unknown");
    }

    long build;
    hr = pVersion->GetBuildNumber(&build);
    TESTHR(hr, _T("GetBuildNumber"));

    CComBSTR bstrVersionInfo;
    hr = pVersion->GetVersionInfoString(&bstrVersionInfo);
    TESTHR(hr, _T("GetVersionInfoString"));

    // Display results to the console - use W2T to convert from
    // wide strings to T strings.
    _tprintf(_T("Windows OS Type: %s\n")
   _T("Version        : %d.%d\n")
   _T("Build Number   : %d\n")
   _T("Version Info   : %s\n"),
   W2T(bstrOsType),
   ver.Major,
   ver.Minor,
   build,
   W2T(bstrVersionInfo));

    return 0;
}




						

Listing 30.4 includes the CComInit class, which guarantees that CoInitialize and CoUninitialize are called at the appropriate times. By declaring a global variable of CComInit, you can be sure that CoInitialize is called before any smart pointers have been initialized, and CoUninitialize is called after all of the COM smart pointer instances are destroyed.

Build the OsInfoClient project. After OsInfoClient is run from the command line, it displays information about the version of the operating system, as Figure 30.5 shows.

30fig05.gif

Figure 30.5 The OsInfoClient project uses OsVersionInfo to collect version information about the operating system.

Share ThisShare This

Informit Network