[ Home | Syllabus | Course Notes | Assignments | Search]
COM Basics
HRESULT QueryInterface(REFIID iid, void** ppvObject); ULONG AddRef(void); ULONG Release(void);
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)
// 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); };
/* 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
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
// 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(); }
Implementing the Component SimpleStudent/IStudent
Could use any language - Let's use C++ first
In visualC++, create a win32 DLL project called
SimpleStudent
(source code is here).
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.
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 };
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; }
QueryInterface Rules
QueryInterface on any interface of a component, must produce
the same results.
You can query IStudent for IUnknown for instance.
Symmetric: Calls for the same interface must
return the same pointer value.
If a call fails one time, it must fail everytime.
Reflexive: If queries on one interface for a for a second succeed, then queries on the second for the first must also succeed.
Transitive: If a query on interface one gives interface two and a query on interface two gives interface three, then a query for interface three on interface one must succeed.
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
Used by CoCreateInstance, calls CreateInstance.
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; };
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; }
//// 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; }
Define a file called "component.reg" containing the following instructions
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"
register the component using the command
regedit component.reg
Or double click the file name in the file manager
Define a file called "component.def" containing
; component.def LIBRARY component.dll DESCRIPTION '(c) 2001 Chris Wild' EXPORTS DllGetClassObject @2 PRIVATE DllCanUnloadNow @3 PRIVATE