COM实现 潘爱民 http://www.icst.pku.edu.cn/compcourse 内容 !复习:COM接口与COM对象 !注册表 !类厂 !COM库 !总结和例子 组件接口 !第一个里程碑 –用vtable作为接口 –解决了名字冲突和二进制结构兼容问题 !第二个里程碑 –接口转换:Dynamic_cast !第三个里程碑 –管理对象生命周期:引用计数 COM接口 !接口标识:IID ! IUnknown接口: class IUnknown { public: virtual HRESULT__stdcall QueryInterface( const IID& iid, void **ppv) = 0 ; virtual ULONG __stdcall AddRef() = 0; virtual ULONG __stdcall Release() = 0; }; COM接口结构 接口指针 指针 指针函数1 指针函数2 指针函数3 。。。。。。 对象实现 vtablepVtable COM接口引用计数 !引用计数的含义 – Outstanding reference !对象实现引用计数 – AddRef、Release !客户显式地操纵引用计数 –客户有责任维护好引用计数 !引用计数规则 COM接口QueryInterface HRESULT __stdcall QueryInterface( const IID& iid, void **ppv) !一个COM对象可以实现多个接口 – QueryInterface是技术保 !QueryInterface实现 –多 : 用static_cast 转换 –对象 :IUnknown 一 !客户 QueryInterface 用对象的接口 –注 :QueryInterface内含AddRef IDL !类C的 是 程 – 的 ! :MIDL.exe 生C++ 件 义 – 生C++ 件 义 ?C++ 义 !MIDL.exe 生TLB类¢库 –£多 程 ?用COM对TLB的¥? !§有的标currency1接口'可以“SDK ??IDL?fi –¥?import/importlib fl IDL xxx.IDL 件 MIDL.exe xxx.h C++ 件 xxx_i.c GUID xxx_p.c P/S dlldata.c xxx.tlb 用?客户/ – proxy/stub 用??? 程 ·Java、VB COM对象 !对象标识:CLSID ! ?:??和?fl ”?…‰和操作 !对象与接口的 ? –· C++对象与`′函数? 的 ? !COM对象的 –对象 一??ˉcurrency1则 实现?式 !进程内组件 – in process component !进程˙组件 – out of process component 进程内组件 !组件:¨`DLL——引 函数 !客户:用?的API函数 LoadLibrary、 GetProcAddress、FreeLibrary !??: – 1. 可以引 ??ˇ— – 2. DumpBin 组件的引 函数和ˇ— 进程˙组件 !实现?式:EXE !IPC:DDE、 制、 内 、 RPC/LPC !例: 用 用? – 用A 进程A DLL 进程B –f 用A 用? API函数 ? DLL LPC 用 –f – f LPCa 结 DLL a ?结 进程˙组件(?) 客户程? 客户进程 o理DLL o理对象 组件程? 组件进程 DLL 客户 用接口` ′函数 ?o理对象 LPC 用组件 DLL LPC a 结 o理对象a ?结 组件对象 用组件 对象接口` ′函数 – ` a ? :对象与客户? 的?接 !客户 vtable与对象进 !客户· ? 第一个接口指针 – CreateString引 函数 !· ??(create) ??(activate) !?? 作一 是 组件 的一个函数 `:??函数CreateObject !客户· 问这个函数 ??函数 !?案1 –直接引 ??函数 –优点:对?DLL非常?便 !?案2 –把??函数 ??一个对象 vtable 用 –优点:灵? 客户以一致的?式 用??函数 ??函数(?) 客户 组件 ??函数指针 客户 组件 ??函数指针 ??函数§“的对象 !该对象被称为类对象 称为类厂 !现“问题是:· ??类厂对象 –对?DLL 引 函数 –对?EXE EXE的引 函数 !客户-〉引 函数-〉类厂对象-〉用户对象 !引 函数的名字固 :DllGetClassObject !增加了一层 接? 带 灵?? ??对象结构示 图 客户 组件 ??实例对象 DllGetClassObject { } ??类厂对象 类厂(Class Factory) !类厂:用???COM对象的COM对象 !目标: `COM对象的?? 程 更好 地把客户与对象隔离开 。 !特殊?: –实现一个”多个??接口 缺省的接口 为IClassFactory –类厂本 没有CLSID 类厂(?) !类厂与COM对象有一一对 关? 字典组件 IClassFactory 类厂 字典对象 IDictionary ISpellCheck 多对象、多类厂组件 类厂1 对象1 类厂 2 对象 2 ??类厂对象 !DllGetClassObject??类厂对象 –??类厂对象需要哪些 !DllGetClassObject原¢: HRESULT DllGetClassObject( const CLSID& clsid, const IID& iid, (void **)ppv ); ??函数需要哪些 !clsid –与类厂绑“一起 !iid –客户提供 !结 接口指针 –类¢取决?iid IClassFactory接口 class IClassFactory : public IUnknown { virtual HRESULT __stdcall CreateInstance( IUnknown *pUnknownOuter, const IID& iid, void **ppv) = 0; virtual HRESULT __stdcall LockServer( BOOL bLock) = 0; }; 小结:客户??对象 程 !客户提供 –组件位置、clsid、iid、结 接口指针地址ppv ! 程: – 据组件位置 LoadLibrary – GetProcAddress ?取DllGetClassObject –用clsid和IID_IClassFactory? 类厂对象接口 指针pFactory –用iid、ppv 用pFactory->CreateInstance ?? 程的位置透?? !位置透??可以极大地?便客户程? !· ¨?位置透?? –“ 前环境下 每个clsid 与某个组件 ? –· 从clsid映射?组件位置 !解决?案: –维护clsid与组件位置的映射关? –“客户与组件? 插入 介 COM?案 !“Windows平台 用? 注册表保 映射关? §以 从clsid可以??对 组件的位置 !“客户与组件? 插入COM库 COM 库 `??的细节 作 Windows? 注册表 !树…结构 – 是“My Computer” –预 义的5个子节点 – HKEY_CLASSES_ROOT !为HKEY_LOCAL_MACHINE的一个子节点 – HKEY_CURRENT_USER !为HKEY_USERS的一个子节点 – HKEY_LOCAL_MACHINE – HKEY_USERS – HKEY_CURRENT_CONFIG 注册表管理COM对象 !HKEY_CLASSES_ROOT\CLSID TreeView组件的注册 Microsoft TreeView Control C:\WINDOWS\SYSTEM\COMCTL32.OCX COMCTL.TreeCtrl.1 {6B7E6392-850A-101B-AFC0-4210102A8DA7} C:\WINDOWS\SYSTEM\COMCTL32.OCX :COM对象的标识 !CLSID 两种?式 – 128位整数 随 数 需要运算功能 是 需要比较和 ?功能 –字符串?式 例·: {72d3edc2-a4c4-11d0-8533-00c04fd8d503} !ProgID:友好名 字符串?式 –有可能重名 用一种约 避免重名 –例·:Word.Document –包含版本:Word.Document.8 TreeControl的ProgID {0713E8A2-850A-101B-AFC0-4210102A8DA7} {0713E8A2-850A-101B-AFC0-4210102A8DA7} COMCTL.TreeCtrl.1 注册表??事项 !? ??的注册 、公 仓库 ! 具RegEdit.exe、Regedt32、OLEView !程? 问 :Win32 API !Component Categories 组件类 组件类 COM组件的注册 !进程内组件 – 两个引 函数DllRegisterServer和DllUnregisterServer 注册 具:RegSvr32.exe 例·:RegSvr32 c:\DictComp\DictComp.dll RegSvr32 /u c:\DictComp\DictComp.dll !进程˙组件 – 命 数/RegServer和/UnregServer COM库 !?? 程 – COM库 ?COM组件和客户 ! 用 程 –对?进程内组件 COM库 与 理 COM对象?? 程 客户 组件 COM??函数 COM库 DllGetClassObject 类厂对象接口指针 COM??函数 !COM库 三个用???组件的函数: CoGetClassObject CoCreateInstance CoCreateInstanceEx CoGetClassObject !??一个类厂 HRESULT CoGetClassObject( const CLSID& clsid, DWORD dwClsContext, COSERVERINFO *pServerInfo, const IID& iid, (void **)ppv ); CoCreateInstance HRESULT CoCreateInstance( const CLSID& clsid, IUnknown *pUnknownOuter, DWORD dwClsContext, const IID& iid, (void **)ppv ); CoCreateInstance实现 HRESULT CoCreateInstance(const CLSID& clsid, IUnknown *pUnknownOuter, DWORD dwClsContext, const IID& iid, void *ppv) { IClassFactory *pCF; HRESULT hr; hr = CoGetClassObject(clsid, dwClsContext, NULL, IID_IClassFactory, (void *)pCF); if (FAILED(hr)) return hr; hr = pCF->CreateInstance(pUnkOuter, iid, (void *)ppv); pCF->Release(); return hr; } CoCreateInstanceEx HRESULT CoCreateInstanceEx( const CLSID& clsid, IUnknown *pUnknownOuter, DWORD dwClsContext, COSERVERINFO *pServerInfo, DWORD dwCount, MULTI_QI *rgMultiQI ); 三个??函数 用原则 !· 客户?? 程对象”? 一 ? 取对象的多个接口指针 则 用 CoCreateInstanceEx函数 !· 客户 ?取类厂对象”?要 用 类厂的某些`′函数 则 用 CoGetClassObject函数 !“?? 下 用CoCreateInstance函 数??对象 这是 常用的?fl。 ?? 程示 图 客户程? 用CoCreateInstance 用CoGetClassObject ? 类厂接口指针 ??对象 用对象 COM 库 CoGetClassObject: 据注册表??DLL ?入DLL程? 用DllGetClassObject函数 a 类厂 组件程? DllGetClassObject ??类厂 a 类厂接口指针 IClassFactory 类厂 COM对象 ? 类 厂 的 实 现 class CDictionaryFactory : public IClassFactory { protected: ULONG m_Ref; public: CDictionaryFactory (void); ~ CDictionaryFactory (void); //IUnknown members HRESULT QueryInterface(const IID& iid, void **ppv); ULONG AddRef(); ULONG Release(); //IClassFactory members HRESULT CreateInstance(IUnknown *, const IID& iid, void **ppv) HRESULT LockServer(BOOL); }; CreateInstance函数的实现 HRESULT CDictionaryFactory::CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void **ppv) { CDictionary * pObj; HRESULT hr; *ppv=NULL; hr=E_OUTOFMEMORY; if (pUnknownOuter != NULL) return CLASS_E_NOAGGREGATION; pObj pObj =new CDictionary(); if (pObj== NULL) return hr; // ? CreateInstance函数的实现(?) //? //Obtain the first interface pointer (which does an AddRef) hr=pObj->QueryInterface(iid, ppv); if (hr != S_OK) { g_DictionaryNumber --; delete pObj; } return hr; } DllGetClassObject的实现 extern "C" HRESULT __stdcall DllGetClassObject(const CLSID& clsid, const IID& iid, void **ppv) { if (clsid == CLSID_Dictionary ) { CDictionaryFactory *pFactory = new CDictionaryFactory; if (pFactory == NULL) { return E_OUTOFMEMORY ; } HRESULT result = pFactory->QueryInterface(iid, ppv); return result; } else { return CLASS_E_CLASSNOTAVAILABLE; } } 类厂对组件生 期的 制 !组件引用计数 计类厂 !IClassFactory::LockServer函数 COM库 !COM库的 !COM库的内 管理 !组件程?的? 和 !常用函数和HRESULT COM库的组` !用??? 程的SCM(Service Control Manager) – rpcss.exe – ole32.dll !?? –提供COM环境 –管理server、组件 – …… COM库的组`(?) COM 用 (COM client) Service Control Manager RPCSS.EXE COM 用 (COM server) OLE32.DLL OLE32.DLL COM库的 ! 本的 函数: – HRESULT CoInitialize(void *pReserved); ! ?前 一可以 用的函数: – DWORD CoBuildVersion(); ! 一个 函数: – CoInitializeEx !COM库的? 函数: – void CoUninitialize(void); 有关CLSID和ProgID的COM函数 !IsEqualGUID、IsEqualIID、IsEqualCLSID !CLSIDFromProgID、ProgIDFromCLSID !StringFromCLSID、CLSIDFromString !StringFromIID、 IIDFromString !StringFromGUID2 –内 用? !注 :COM库函数的字符串 用OLECHAR 类¢ COM库的内 管理 !COM库提供了内 管理 以 内 管理 的标currency1 HRESULT CoGetMalloc(DWORD dwMemContext, IMalloc **ppMalloc); class IMalloc : public IUnknown { void * Alloc(ULONG cb) = 0; void * Realloc( void * pv, ULONG cb) = 0; void Free(void* pv) = 0; ULONG GetSize( void * pv) = 0; int DidAlloc(void * pv) = 0; void HeapMinimize()= 0; }; COM库内 管理用fl 一 DWORD length = MAX_LENGTH; IMalloc * pIMalloc; HRESULT hr; hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc); if (hr != S_OK) // return failure psz=pIMalloc->Alloc(length); pIMalloc->Release(); if (NULL==psz) // return failure ...... pszText = psz; COM库内 管理用fl 二 !三个 ?函数: void * CoTaskMemAlloc(ULONG cb); void CoTaskMemFree(void *pv); void CoTaskMemRealloc(void *pv, ULONG cb); COM库内 管理用fl 三 DWORD length = MAX_LENGTH; IMalloc * pIMalloc; HRESULT hr; psz=CoTaskMemAlloc (length); if (NULL==psz) // return failure ...... pszText = psz; COM库内 管理用fl ? WCHAR *pwProgID; char pszProgID[128]; hResult = ::ProgIDFromCLSID(CLSID_Dictionary, &pwProgID); if (hResult != S_OK) { …… } wcstombs(pszProgID, pwProgID, 128) ; CoTaskMemFree(pwProgID); 组件程?的? 和 !进程内组件的? – DllGetClassObject !进程˙组件的? – “/Embedding”命 数 !进程内组件的 – CoFreeUnusedLibraries !进程˙组件的 – main”?WinMain函数¢ 进程内组件的 !组件 能£? !客户 用COM库函数CoFreeUnusedLibraries !COM库 用DLL组件的引 函数 – HRESULT DllCanUnloadNow(); –¥DllCanUnloadNowa S_OK 则 –¥DllCanUnloadNowa S_FALSE 则 !DllCanUnloadNow实现:对象计数+?计数 COM库 一些常用函数 类 函数 功能 CoBuildVersion ?取COM库的版本§ CoUnitialize COM库的 CoUninitialize COM库功能 –? 函数 CoFreeUnusedLibraries currency1'进程 §有 用的组件程? IsEqualGUID ?ˉ两个GUID 是“ IsEqualIID ?ˉ两个IID是“ IsEqualCLSID ?ˉ两个CLSID是“ CLSIDFromProgID 把字符串?式的对象标识转 为CLSID结构?式 StringFromCLSID 把CLSID结构?式转 为字符串?式 IIDFromString 把字符串?式的IID转 为IID结构?式 StringFromIID 把IID结构?式转 为字符串?式 GUID有关的函数 StringFromGUID2 把GUID 结构?式转 为字符串?式 CoGetClassObject ?取对象的类厂 CoCreateInstance ??COM对象 CoCreateInstanceEx ??COM对象 可指 多个接口” 程对象 CoRegisterClassObject ??一个对象 以便?? 用可以?接?该对象 CoRevokeClassObject 取 对象的??操作 对象??函数 CoDisconnectObject ˉ开?? 用与对象的?接 CoTaskMemAlloc 内 函数 CoTaskMemRealloc 内 重fi 函数 CoTaskMemFree 内 currency1'函数 内 管理函数 CoGetMalloc ?取COM库的内 管理 接口 COM库 一些常用函数 ! 函数 !GUID有关的函 数 !对象??函数 !内 管理函数 !表fl?fl的操作结 32位整数 !类 : 映了函数 用结 的 本 !操作 :标识了结 操作 – HRESULT数据结构 0 操作结 操作 16 1531 30 29 28 ?重程? £ 义标· !操作 #define FACILITY_WINDOWS 8 #define FACILITY_STORAGE 3 #define FACILITY_RPC 1 #define FACILITY_SSPI 9 #define FACILITY_WIN32 7 #define FACILITY_CONTROL 10 #define FACILITY_NULL 0 #define FACILITY_INTERNET 12 #define FACILITY_ITF 4 #define FACILITY_DISPATCH 2 #define FACILITY_CERT 11 !Win32 SDK的 件WinError.h HRESULT !类 00 - 表示函数 用`功 01 - 包含了一些 10 - ? 11 - ?? 常用的HRESULT? 义 a ” ?? S_OK 函数? `功 ?”为0。* S_FALSE 函数? `功 a …‰FALSE ?”为1。* E_FAIL 函数? ? ?原 ` 。 E_OUTOFMEMORY 函数? ? ?原 为内 `功。 E_NOTIMPL 函数? ? `′函数没有被实现。 E_NOTINTERFACE 函数? ? 组件没有实现指 的接口 用? QueryInterface`′函数的a ”。 HRESULT(?) !FormatMessage函数 !SUCCEEDED和FAILED? !常用 义 COM实现 程 !进程内组件与客户的′作 程 !进程˙组件与客户的′作 程 实现一个进程内COM组件的?? ! 义 要的CLSID和IID !实现COM对象 – QueryInterfaceˉ??接口 –管理引用计数 注 对??引用计数的维护 !实现类厂对象 –对象的引用计数 ?“??对象引用计数内 –维护?计数 !实现DllGetClassObject、DllCanUnloadNow !(可 )实现两个注册函数 客户程? COM 库组件程?(DLL) CLSID clsid IClassFactory *pClf; IUnknown *pUnknown; CoInitialize(NULL); CLSIDFromProgID( "Dictionary.Object", &clsid); COM “注册表 ?字典 CLSID CoGetClassObject(clsid, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pClf); COM 库“内 ? clsid 组件 if (DictComp.dll˙没被?入内 ) { 从注册表 ?取组件程? ?¨ 名 ...\DictComp.dll CoLoadLibrary(...) } 用DLL的 DllGetClassObject ??类厂对象 CDictionaryFactory a IClassFactory接口 COM 库a IClassFactory 接口? 客户 进程内组件与客户的′作 程 pClf->CreateInstance(NULL, IID_IUnknown, (void **)&pUnknown); 类厂对象的 CreateInstance 函数被 用( 组件的 vtable 被客 户直接 用) 用 new 操作符构?字典组件对象 new CDictionary; a IUnknown 接口指针 客户 用字典组件 ?接口 进 种操作...... pClf->Release(); pUnknown->Release(); 组件对象的 Release 函数被 用 ( 是 组件的 vtable) if (m_Ref == 0) { delete this; return 0; } CoFreeUnusedLibraries() COM 库 用字典组件的引 函数 DllCanUnloadNow DllCanUnloadNow 函数 : if ( “字典对象 && ?计数 为 0) return TRUE; else return FALSE; if (字典组件 DllCanUnloadNow 函 数a TRUE) CoFreeLibrary(...); CoUninitialize() COM 库currency1'?– 客户程?¢ 客户程? COM库 组件程?(EXE) CLSID clsid IClassFactory *pClf; IUnknown *pUnknown; CoInitialize(NULL); CLSIDFromProgID( "...", &clsid); COM “注册表 组件对象CLSID CoGetClassObject(clsid, CLSCTX_LOCAL_SERVER, NULL, IID_IClassFactory, (void **)&pClf); COM库“内 ?clsid组件 if (组件EXE˙没有?ˇ || COM需要 一个实例) { 从注册表 ?取EXE组件名 ??组件进程 } 进程˙组件与客户的′作 程 用 CoInitialize ??组件¥?的 种类厂对象 用 COM 函数注册§有类厂对 象:CoRegisterClassObject 组件注册 ` COM 库把 IClassFactory接口a ?客户 pClf->CreateInstance(NULL, IID_IUnknown, (void **)&pUnknown); 类厂对象的 CreateInstance 函数被 用( 进程 o理对象被 接 用) 构?组件对象 a IUnknown 接口指针 客户 用字典组件 ?接口 ( 接)进 种操作...... pClf->Release(); pUnknown->Release(); 组件对象的Release函数被 用 if (m_Ref == 0) { delete this; return 0; } · — ¢ 件则 常¢ CoUninitialize () COM 库对§有该客户没有`功currency1 '的对象 用Release函数 CoUninitialize () 组件程?¢ COM 库currency1'?– 客户程?¢ £ 作 ! 程? – COM原理与 用 !第二 的 字典组件和字典客户 !两个 程:DictComp和DictCtrl –”? Inside COM !第 的例子程? –目标: 了解COM组件的?? 程 进一?理解COM组件的位置透??