Windows Systems Programming: Spring 2002

[ Home | Syllabus | Course Notes | Assignments | Search]


COM Basics


IUnknown

HRESULT QueryInterface(REFIID iid, void** ppvObject);
ULONG AddRef(void);
ULONG Release(void);

Identifiers


Using COM


COM Servers

 


Some Definitions

interface:
a set of semantically related methods (function calls). Can be used to provide access to the public member functions of an object. A COM interface is implemented as a C++ V-table (in C++ programs) However the COM interface is language independent.
virtual function:
a function that can be redefined in a derived class and which can be dynamically bound to any object in the inheritance hierarchy. Thus a pointer to the base class could point to any object of a derived class and any function referenced by that pointer is dynamically bound to the function of the correct derived class. This ability of a function to be dynamically bound permits polymorphism.
pure virtual function:
A virtual function is undefined in the base class and must be defined in any derived class. This allows a abstract base class to place requirements on any inherited object class.
polymorphism:
The ability of a function in a program to work with any object in an inheritance chain without recompilation. This implies that the run-time system must be able to find the address of the function based on the type of the object at the time the function is invoked (dynamic or late binding).
v-table:
a table containing the address of virtual functions. A v-table is associated with each class.
component:
set of (possibly) related COM classes. Housed either in a DLL (Dynamically Linked Library) or an executable file.
idl (interface definition language):
langauge independent/ platform independent means of specifiying a function's prototype. Adapted from Open Software Foundation (OSF) RPC standard.
 

Let's develop a simple COM class, exploring the problems and solutions to them.  

Project: write a simple COM object which contains a simple student record (just grade)

We want this object to be language independent and location independent (distributed).

PROBLEM: how to specify a language independent header file.
SOLUTION: Use IDL. The IDL file is compiled into language dependent code by MIDL (Microsoft's IDL compiler)

rainbow.gif (2243 bytes)

// component.idl
import "unknwn.idl";

[ object, uuid(477CF9EC-8A08-4a8e-B304-67076C64FA54) ]
interface IStudent : IUnknown
{
	HRESULT SetGrade([in] int grade);
	HRESULT GetGrade([out,retval] int* grade);
};
rainbow.gif (2243 bytes)

 


rainbow.gif (2243 bytes)

/* this ALWAYS GENERATED file contains the definitions for the interfaces */


/* File created by MIDL compiler version 5.01.0164 */
/* at Thu Feb 15 09:23:59 2001
 */
/* Compiler settings for student.idl:
    Os (OptLev=s), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: allocation ref bounds_check enum stub_data 
*/
//@@MIDL_FILE_HEADING(  )


/* verify that the <rpcndr.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCNDR_H_VERSION__
#define __REQUIRED_RPCNDR_H_VERSION__ 440
#endif

#include "rpc.h"
#include "rpcndr.h"

#ifndef __RPCNDR_H_VERSION__
#error this stub requires an updated version of <rpcndr.h>
#endif // __RPCNDR_H_VERSION__

#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/

#ifndef __student_h__
#define __student_h__

#ifdef __cplusplus
extern "C"{
#endif 

/* Forward Declarations */ 

#ifndef __IStudent_FWD_DEFINED__
#define __IStudent_FWD_DEFINED__
typedef interface IStudent IStudent;
#endif 	/* __IStudent_FWD_DEFINED__ */


/* header files for imported files */
#include "unknwn.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * ); 

#ifndef __IStudent_INTERFACE_DEFINED__
#define __IStudent_INTERFACE_DEFINED__

/* interface IStudent */
/* [uuid][object] */ 


EXTERN_C const IID IID_IStudent;

#if defined(__cplusplus) && !defined(CINTERFACE)
    
    MIDL_INTERFACE("477CF9EC-8A08-4a8e-B304-67076C64FA54")
    IStudent : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE SetGrade( 
            /* [in] */ int grade) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetGrade( 
            /* [retval][out] */ int __RPC_FAR *grade) = 0;
        
    };
    
#else 	/* C style interface */

    typedef struct IStudentVtbl
    {
        BEGIN_INTERFACE
        
        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface)( 
            IStudent __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);
        
        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef)( 
            IStudent __RPC_FAR * This);
        
        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release)( 
            IStudent __RPC_FAR * This);
        
        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *SetGrade )( 
            IStudent __RPC_FAR * This,
            /* [in] */ int grade);
        
        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetGrade )( 
            IStudent __RPC_FAR * This,
            /* [retval][out] */ int __RPC_FAR *grade);
        
        END_INTERFACE
    } IStudentVtbl;

    interface IStudent
    {
        CONST_VTBL struct IStudentVtbl __RPC_FAR *lpVtbl;
    };

    

#ifdef COBJMACROS


#define IStudent_QueryInterface(This,riid,ppvObject)	\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IStudent_AddRef(This)	\
    (This)->lpVtbl -> AddRef(This)

#define IStudent_Release(This)	\
    (This)->lpVtbl -> Release(This)


#define IStudent_SetGrade(This,grade)	\
    (This)->lpVtbl -> SetGrade(This,grade)

#define IStudent_GetGrade(This,grade)	\
    (This)->lpVtbl -> GetGrade(This,grade)

#endif /* COBJMACROS */


#endif 	/* C style interface */



HRESULT STDMETHODCALLTYPE IStudent_SetGrade_Proxy( 
    IStudent __RPC_FAR * This,
    /* [in] */ int grade);


void __RPC_STUB IStudent_SetGrade_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);


HRESULT STDMETHODCALLTYPE IStudent_GetGrade_Proxy( 
    IStudent __RPC_FAR * This,
    /* [retval][out] */ int __RPC_FAR *grade);


void __RPC_STUB IStudent_GetGrade_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);



#endif 	/* __IStudent_INTERFACE_DEFINED__ */


/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif
rainbow.gif (2243 bytes)

Now let's see how a client can access this  COM object:

PROBLEM: Creating an object in a language independent manner
SOLUTION: CoCreateInstance

PROBLEM: Returning an interface to the COM Object (like attaching a v-table)
SOLUTION: QueryInterface Method

PROBLEM: managing a shared interface's lifetime
SOLUTION: AddRef and Release Methods

rainbow.gif (2243 bytes)

// client.cpp
#include <iostream.h>
#include "student.h" // Generated by MIDL(see above)
// {A873BF78-A5DA-4a7b-B4F7-3CB51C5D210B} // generated by guidgen
static const CLSID CLSID_SimpleStudent = 
{ 0xa873bf78, 0xa5da, 0x4a7b, { 0xb4, 0xf7, 0x3c, 0xb5, 0x1c, 0x5d, 0x21, 0xb };
void main()
{
	cout << "Client: Calling CoInitialize()" << endl;
	HRESULT hr = CoInitialize(NULL);
	if(FAILED(hr))
		cout << "CoInitialize failed" << endl;
	
	IUnknown* pUnknown;
	IStudent* pStudent;

	cout << "Client: Calling CoCreateInstance()" << endl;
	hr = CoCreateInstance(CLSID_SimpleStudent , NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pUnknown);
	if(FAILED(hr))
		cout << "CoCreateInstance failed" << endl;

	cout << "Client: Calling QueryInterface() for ISum on " << pUnknown << endl;
	hr = pUnknown->QueryInterface(IID_IStudent, (void**)&pStudent); 
           // missing reference operator could crash the appliation
	if(FAILED(hr))
		cout << "IID_IStudent not supported" << endl;

	hr = pUnknown->Release();
	cout << "Client: Calling pUnknown->Release() reference count = " << hr << endl;

	int grade;
	cout << "type grade to set:";
	cin >> grade;
	hr = pStudent->SetGrade(grade);
	if(SUCCEEDED(hr))
		cout << "Client: SetGrade to " << grade << endl;
	int gradeOut;
	hr = pStudent->GetGrade(gradeOut);
	if(SUCCEEDED(hr))
		cout << "Client: GetGrade is " << gradeOut << endl;

	hr = pStudent->Release();
	cout << "Client: Calling pStudent->Release() reference count = " << hr << endl;

	cout << "Client: Calling CoUninitialize()" << endl;
	CoUninitialize();
}

 

rainbow.gif (2243 bytes)

Implementing the Component SimpleStudent/IStudent

Could use any language - Let's use C++ first

Remember that the IStudent interface object is an abstract class because its four functions are pure virtual.

(What are the four functions?)

Therefore we need to derive a implementation class from it.

rainbow.gif (2243 bytes)

class CSimpleStudent : public IStudent // remember this is derived from IUnknown
{
public:
   // IUnknown
   ULONG __stdcall AddRef(); // COM functions NOT returning HRESULT
   ULONG __stdcall Release();
   HRESULT __stdcall QueryInterface(REFIID iid, void** ppv);

   // IStudent
   HRESULT __stdcall SetGrade(int grade);
   HRESULT __stdcall GetGrade(int* grade);


   CSimpleStudent () : m_cRef(1) { g_cComponents++; }
   ~CSimpleStudent () { cout << "Component: CSimpleStudent ::CSimpleStudent ()" << endl, g_cComponents--; }

private:
   ULONG m_cRef;
   int m_grade; // remember this student's grade
};

 

rainbow.gif (2243 bytes)


Implementing IUnknown Functions

ULONG CSimpleStudent::AddRef()
{
   return ++m_cRef;
}

ULONG CSimpleStudent::Release()
{
   if(--m_cRef != 0) // test if last reference
      return m_cRef;
   delete this; // "calls" destructor
   return 0;
}

HRESULT CSimpleStudent::QueryInterface(REFIID iid, void** ppv)
{
   if(iid == IID_IUnknown) {
      *ppv = (IUnknown*)this;
   }
   else if(iid == IID_IStudent) {
      *ppv = (IStudent*)this;
   }
   else {
      *ppv = NULL;
      return E_NOINTERFACE;
   }
   AddRef();
   return S_OK;
}
rainbow.gif (2243 bytes)

QueryInterface Rules

QueryInterface on any interface of a component, must produce the same results.
You can query IStudent for IUnknown for instance.


SetGrade/GetGrade implementation

HRESULT CSimpleStudent::SetGrade(int grade)
{
   m_grade = grade;
   return S_OK;
}
HRESULT CSimpleStudent::GetGrade(int* grade)
{
   *grade = m_grade;
   return S_OK;
}
 

Creating a  "Location Transparent"  to IUnknown

IClassFactory:
A COM object for creating other COM objects of a given type.

Used by CoCreateInstance, calls CreateInstance.

rainbow.gif (2243 bytes)

class CFactory : public IClassFactory
{
public:
	// IUnknown
	ULONG __stdcall AddRef();
	ULONG __stdcall Release();
	HRESULT __stdcall QueryInterface(REFIID iid, void** ppv);

	// IClassFactory
	HRESULT __stdcall CreateInstance(IUnknown *pUnknownOuter, REFIID iid, void** ppv);
	HRESULT __stdcall LockServer(BOOL bLock);

	CFactory() : m_cRef(1) { }
	~CFactory() { }

private:
	ULONG m_cRef;
};

rainbow.gif (2243 bytes)

HRESULT CFactory::CreateInstance(IUnknown *pUnknownOuter, REFIID iid, void** ppv)
{
	if(pUnknownOuter != NULL)
		return CLASS_E_NOAGGREGATION;

	CSimpleStudent *pSimpleStudent = new CSimpleStudent;
	cout << "Component: CFactory::CreateInstance() " << pSimpleStudent << endl;

	if(pSimpleStudent== NULL)
		return E_OUTOFMEMORY;

	// QueryInterface probably for IID_IUNKNOWN
	HRESULT hr = pSimpleStudent->QueryInterface(iid, ppv);
	pSimpleStudent->Release();
	return hr;
}

rainbow.gif (2243 bytes)


//// code for dealing with DLL management

HRESULT CFactory::LockServer(BOOL bLock)
{
	if(bLock)
		g_cServerLocks++;
	else
		g_cServerLocks--;
	return S_OK;
}

HRESULT __stdcall DllCanUnloadNow()
{
	cout << "Component: DllCanUnloadNow() " << (g_cServerLocks == 0 && g_cComponents == 0 ? "Yes" : "No") << endl;
	if(g_cServerLocks == 0 && g_cComponents == 0)
		return S_OK;
	else
		return S_FALSE;
}

HRESULT __stdcall DllGetClassObject(REFCLSID clsid, REFIID iid, void** ppv)
{
	cout << "Component: DllGetClassObject" << endl;
	
	if(clsid != CLSID_SimpleStudent)
		return CLASS_E_CLASSNOTAVAILABLE;

	CFactory* pFactory = new CFactory;
	if(pFactory == NULL)
		return E_OUTOFMEMORY;

	// QueryInterface probably for IClassFactory
	HRESULT hr = pFactory->QueryInterface(iid, ppv);
	pFactory->Release();
	return hr;
}

rainbow.gif (2243 bytes)


Registering the Component

REGEDIT4

[HKEY_CLASSES_ROOT\CLSID\{A873BF78-A5DA-4a7b-B4F7-3CB51C5D210B}]
@="Simple Student"

[HKEY_CLASSES_ROOT\CLSID\{A873BF78-A5DA-4a7b-B4F7-3CB51C5D210B}\InprocServer32]
@="D:\\Courses\\CS495NT\\Spring01\\Examples\\Student1\\component\\Debug\\component.dll"
rainbow.gif (2243 bytes)

; component.def
LIBRARY         component.dll
DESCRIPTION     '(c) 2001 Chris Wild'
EXPORTS
                DllGetClassObject   @2	PRIVATE
                DllCanUnloadNow     @3	PRIVATE

 


dll2.gif (20140 bytes)

 


Copyright chris wild 1999-2002.
For problems or questions regarding this web contact [Dr. Wild].
Last updated: February 17, 2002.