1. Create an Interface
typedef struct InterfaceInterface ISimpleMsgBox : public IUnknown{ // IUnknown STDMETHOD_(ULONG, AddRef)() PURE; STDMETHOD_(ULONG, Release)() PURE; STDMETHOD(QueryInterface)(REFIID riid, void** ppv) PURE; // ISimpleMsgBox STDMETHOD(DoSimpleMsgBox)(HWND hwndParent, BSTR bsMessageText) PURE;};
Alternative solution:Using the IDL file definition interface, it will generate the relevant header file, but the readability is very poor
2. Declare a C ++ class to implement this interface
class CSimpleMsgBoxImpl : public ISimpleMsgBox {public: CSimpleMsgBoxImpl(); virtual ~CSimpleMsgBoxImpl(); // IUnknown STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); STDMETHOD(QueryInterface)(REFIID riid, void** ppv); // ISimpleMsgBox STDMETHOD(DoSimpleMsgBox)(HWND hwndParent, BSTR bsMessageText);protected: ULONG m_uRefCount;};
Implementation part. only focus on the QueryInterface and dosimplemsgbox sections.
STDMETHODIMP CSimpleMsgBoxImpl::QueryInterface ( REFIID riid, void** ppv ){HRESULT hrRet = S_OK; TRACE(">>> SimpleMsgBoxSvr: In CSimpleMsgBoxImpl::QueryInterface()\n" ); // Check that ppv really points to a void*. if ( IsBadWritePtr ( ppv, sizeof(void*) )) return E_POINTER; // Standard QI initialization - set *ppv to NULL. *ppv = NULL; // If the client is requesting an interface we support, set *ppv. if ( InlineIsEqualGUID ( riid, IID_IUnknown )) { *ppv = (IUnknown*) this; TRACE(">>> SimpleMsgBoxSvr: Client QIed for IUnknown\n" ); } else if ( InlineIsEqualGUID ( riid, __uuidof(ISimpleMsgBox) )) { *ppv = (ISimpleMsgBox*) this; TRACE(">>> SimpleMsgBoxSvr: Client QIed for ISimpleMsgBox\n" ); } else { hrRet = E_NOINTERFACE; TRACE(">>> SimpleMsgBoxSvr: Client QIed for unsupported interface\n" ); } // If we're returning an interface pointer, AddRef() it. if ( S_OK == hrRet ) { ((IUnknown*) *ppv)->AddRef(); } return hrRet;}//////////////////////////////////////////////////////////////////////// ISimpleMsgBox methodsSTDMETHODIMP CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText ){_bstr_t bsMsg = bsMessageText;LPCTSTR szMsg = (TCHAR*) bsMsg; // Use _bstr_t to convert the string to ANSI if necessary. TRACE(">>> SimpleMsgBoxSvr: In CSimpleMsgBoxImpl::DoSimpleMsgBox()\n" ); MessageBox ( hwndParent, szMsg, _T("Simple Message Box"), MB_OK ); return S_OK;}3. Specify a guid for each interface and class
Use VC ++ Extension Method
struct __declspec(uuid("{7D51904D-1645-4a8c-BDE0-0F4A44FC38C4}")) ISimpleMsgBox;class __declspec(uuid("{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}")) CSimpleMsgBoxImpl;In this way, you can use the _ uuidof keyword to introduce the VC ++ extension syntax.
class Demo{};class __declspec(uuid("B372C9F6-1959-4650-960D-73F20CD479BA")) Demo; int main( void ){ CLSID clsid = __uuidof(Demo); }
4. Create a category Factory (required)
Each class must be equipped with a class factory. Follow the createinstance and lockserver methods.
The cocreateinstance method depends on this interface in its internal life cycle.
//////////////////////////////////////////////////////////////////////// IClassFactory methodsSTDMETHODIMP CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter, REFIID riid, void** ppv ){HRESULT hrRet;CSimpleMsgBoxImpl* pMsgbox; TRACE(">>> SimpleMsgBoxSvr: In CSimpleMsgBoxClassFactory::CreateInstance()\n" ); // We don't support aggregation, so pUnkOuter must be NULL. if ( NULL != pUnkOuter ) return CLASS_E_NOAGGREGATION; // Check that ppv really points to a void*. if ( IsBadWritePtr ( ppv, sizeof(void*) )) return E_POINTER; *ppv = NULL; // Create a new COM object! pMsgbox = new CSimpleMsgBoxImpl; if ( NULL == pMsgbox ) return E_OUTOFMEMORY; // QI the object for the interface the client is requesting. hrRet = pMsgbox->QueryInterface ( riid, ppv ); // If the QI failed, delete the COM object since the client isn't able // to use it (the client doesn't have any interface pointers on the object). if ( FAILED(hrRet) ) delete pMsgbox; return hrRet;}STDMETHODIMP CSimpleMsgBoxClassFactory::LockServer ( BOOL fLock ){ // Increase/decrease the DLL ref count, according to the fLock param. fLock ? g_uDllLockCount++ : g_uDllLockCount--; TRACE(">>> SimpleMsgBoxSvr: In CSimpleMsgBoxClassFactory::LockServer(), new DLL ref count = %d\n", g_uDllLockCount ); return S_OK;}
5. Register COM components (manually register)
Everything is ready. Now we need to write its information into the registry,
1. Implement the dllregisterserver and dllunregisterserver methods, and declare the methods as stdapi, indicating they belong to the export function.
// DllRegisterServer() creates the registy entries that tells COM where our // server is located and its threading model.STDAPI DllRegisterServer(){HKEY hCLSIDKey = NULL, hInProcSvrKey = NULL;LONG lRet;TCHAR szModulePath [MAX_PATH];TCHAR szClassDescription[] = _T("SimpleMsgBox class");TCHAR szThreadingModel[] = _T("Apartment");__try { // Create a key under CLSID for our COM server. lRet = RegCreateKeyEx ( HKEY_CLASSES_ROOT, _T("CLSID\\{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}"), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE | KEY_CREATE_SUB_KEY, NULL, &hCLSIDKey, NULL ); if ( ERROR_SUCCESS != lRet ) return HRESULT_FROM_WIN32(lRet); // The default value of the key is a human-readable description of the coclass. lRet = RegSetValueEx ( hCLSIDKey, NULL, 0, REG_SZ, (const BYTE*) szClassDescription, sizeof(szClassDescription) ); if ( ERROR_SUCCESS != lRet ) return HRESULT_FROM_WIN32(lRet); // Create the InProcServer32 key, which holds info about our coclass. lRet = RegCreateKeyEx ( hCLSIDKey, _T("InProcServer32"), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hInProcSvrKey, NULL ); if ( ERROR_SUCCESS != lRet ) return HRESULT_FROM_WIN32(lRet); // The default value of the InProcServer32 key holds the full path to our DLL. GetModuleFileName ( g_hinstThisDll, szModulePath, MAX_PATH ); lRet = RegSetValueEx ( hInProcSvrKey, NULL, 0, REG_SZ, (const BYTE*) szModulePath, sizeof(TCHAR) * (lstrlen(szModulePath)+1) ); if ( ERROR_SUCCESS != lRet ) return HRESULT_FROM_WIN32(lRet); // The ThreadingModel value tells COM how it should handle threads in our DLL. // The concept of apartments is beyond the scope of this article, but for // simple, single-threaded DLLs, use Apartment. lRet = RegSetValueEx ( hInProcSvrKey, _T("ThreadingModel"), 0, REG_SZ, (const BYTE*) szThreadingModel, sizeof(szThreadingModel) ); if ( ERROR_SUCCESS != lRet ) return HRESULT_FROM_WIN32(lRet); } __finally { if ( NULL != hCLSIDKey ) RegCloseKey ( hCLSIDKey ); if ( NULL != hInProcSvrKey ) RegCloseKey ( hInProcSvrKey ); } return S_OK;}// DllUnregisterServer() deleted the registy entries that DllRegisterServer() created.STDAPI DllUnregisterServer(){ // Delete our registry entries. Note that you must delete from the deepest // key and work upwards, because on NT/2K, RegDeleteKey() doesn't delete // keys that have subkeys on NT/2K. RegDeleteKey ( HKEY_CLASSES_ROOT, _T("CLSID\\{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}\\InProcServer32") ); RegDeleteKey ( HKEY_CLASSES_ROOT, _T("CLSID\\{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}") ); return S_OK;}
2. Define the def File
EXPORTS DllRegisterServer PRIVATE DllUnregisterServer PRIVATE DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE
3. Use the regsvr32 command after compilation to register the DLL component.
Before calling this COM component, you must use the CMD command to register it. It is very important that the dllregisterserver will become the entry point.,
6. Write dllgetclassobject
Only the global function dllgetclassobject can be used to create a class factory. This method is called by the COM class library and a class factory is returned Based on the CLSID (a DLL may have multiple com classes)
Lifecycle (important ):
Cocreateinstance-> cogetclassobject-> dllgetclassobject-> iclassfactory-> createinstance
STDAPI DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv ){HRESULT hrRet;CSimpleMsgBoxClassFactory* pFactory; TRACE(">>> SimpleMsgBoxSvr: In DllGetClassObject()\n"); // Check that the client is asking for the CSimpleMsgBoxImpl factory. if ( !InlineIsEqualGUID ( rclsid, __uuidof(CSimpleMsgBoxImpl) )) return CLASS_E_CLASSNOTAVAILABLE; // Check that ppv really points to a void*. if ( IsBadWritePtr ( ppv, sizeof(void*) )) return E_POINTER; *ppv = NULL; // Construct a new class factory object. pFactory = new CSimpleMsgBoxClassFactory; if ( NULL == pFactory ) return E_OUTOFMEMORY; // AddRef() the factory since we're using it. pFactory->AddRef(); // QI() the factory for the interface the client wants. hrRet = pFactory->QueryInterface ( riid, ppv ); // We're done with the factory, so Release() it. pFactory->Release(); return hrRet;}7. Call the COM Component
void DoMsgBoxTest(HWND hMainWnd){ISimpleMsgBox* pIMsgBox;HRESULT hr; hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl), NULL, CLSCTX_INPROC_SERVER, __uuidof(ISimpleMsgBox), (void**) &pIMsgBox ); if ( FAILED(hr) ) return; pIMsgBox->DoSimpleMsgBox ( hMainWnd, _bstr_t("Hello COM!") ); pIMsgBox->Release();}
The preceding steps cannot be omitted. It can be seen that creating a COM component is a troublesome process and is prone to errors.
Or a common class factory
HRESULT hr;IClassFactory *pc=NULL;hr=::CoGetClassObject(__uuidof(CSimpleMsgBoxImpl), CLSCTX_INPROC_SERVER,NULL, IID_IClassFactory, (void**) &pc );