一:建立一個介面
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;};
替代方案:使用idl檔案定義介面,其會產生相關標頭檔,但可讀性很差
二.聲明一個C++類實現該介面
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;};
實現部分,只關注QueryInterface 和DoSimpleMsgBox 部分
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;}三.為介面和類指定各自指定一個GUID
使用VC++擴充方法
struct __declspec(uuid("{7D51904D-1645-4a8c-BDE0-0F4A44FC38C4}")) ISimpleMsgBox;class __declspec(uuid("{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}")) CSimpleMsgBoxImpl;這樣便可以使用__uuidof關鍵字了__uuidof使用方法介紹屬於VC++擴充文法
class Demo{};class __declspec(uuid("B372C9F6-1959-4650-960D-73F20CD479BA")) Demo; int main( void ){ CLSID clsid = __uuidof(Demo); }
四.建立類廠(必須建立)
每個類必須配有一個類廠,關注CreateInstance 和LockServer 方法.
CoCreateInstance方法內部會生命週期依賴這個介面
//////////////////////////////////////////////////////////////////////// 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;}
五.註冊COM組件(手動註冊)
萬事俱備,現在要將其資訊寫入註冊表,
1.實現DllRegisterServer和DllUnregisterServer方法,並將方法聲明為STDAPI,表明屬於匯出函數
// 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.定義def檔案
EXPORTS DllRegisterServer PRIVATE DllUnregisterServer PRIVATE DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE
3.編譯通過後使用regsvr32命令,註冊此dll組件
在調用此com組件之前,必須通過cmd命令註冊,很重要,DllRegisterServer將會成為進入點,
六.編寫DllGetClassObject
只有通過全域函數DllGetClassObject,才可以建立類廠,這個方法com類庫會去調用,其會根據CLSID返回一個類工廠(一個dll可能會有多個com類)
生命週期(非常重要):
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;}七.調用com組件
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();}
以上步驟不可以省略,可以看到建立一個com組件是比較麻煩的一個流程,很容易出錯
或者先常見一個類工廠
HRESULT hr;IClassFactory *pc=NULL;hr=::CoGetClassObject(__uuidof(CSimpleMsgBoxImpl), CLSCTX_INPROC_SERVER,NULL, IID_IClassFactory, (void**) &pc );