COM多线程模型、DCOM 潘爱民 http://www.icst.pku.edu.cn/compcourse 内容 ? 复习:COM聚合和COM跨进程模型 ?COM线程模型 ? 分布式COM(DCOM) –DCOM基本结构 – 对象激活 – 连接管理 – 并发管理 –DCOM安全模型 复习:COM包容模型 对象B ISomeInterface 对象A ISomeInterface 客户程序 调用 调用 IOtherInterface 对象A ISomeInterface 客户程序 QueryInterface AddRef Release SomeFunction 委托 IUnknown 非委托 IUnknown 复习:聚合,支持聚合的对象 在非聚合方式下的接口示意图 复习:聚合,支持聚合的对象 在聚合方式下的接口示意图 对象B IOtherInterface 对象A ISomeInterface 客户程序 QueryInterface AddRef Release SomeFunction QueryInterface AddRef Release OtherFunction 外部对象的 IUnknown 委托 IUnknown 非委托 IUnknown 控制 聚合模型的要点 ? 外部对象 – 创建内部对象的时候,外部对象必须把自己的 IUnknown接口指针传给内部对象 – 当外部对象接到对于聚合接口的请求时,它必须调用 非委托版本的IUnknown的QueryInterface函数,并把结 果返回给客户 ? 内部对象 – 内部对象类厂的CreateInstance必须检查pUnkOuter 参数 – 嵌 聚合:传 外 的pUnkOuter参数 – 非委托版本的IUnknown 外, 接口的 IUnknown调用必须全部委托给外部对象的 pUnkOuter 类厂建 理对象和 对 象自 程 进程 程序 客户进程 类厂 理对象 类厂 类厂对象 对象自 理 对象 客户 创建 对象 CreateInstance LPC/RPC 调用 传 ?创建 理对象 ¢返回 £连接 ?调用 自 的要点 ? 对象必须¥?IMarshal接口 ? 理对象§必须¥?IMarshal接口,并currency1 理对象'进程外对象 “?? ? 理对象必须?fifl 接口的跨进程–? ? ?型用?: – · 跨进程调用的??,?用? ”?…‰ ? – marshal-by-value `′ 的proxy和stub结构 客户进程 理对象 进程 ITF1 客户程序 ITF2 ITFn 理 管理? IRpcChannelBuffer ?ˉRPC 对象 ITF1 ITF2 ITFn 管理? ?ˉRPC IRpcProxyBuffer IRpcStubBuffer RPC ? RPC ? 进程外 ˙意¨ ? 自˙?方式的? – ??参数/RegServer和/UnregServer ? ˙?类厂 ? ˇ时— ? 调用CoInitialize和CoUninitialize ? ¥?自 接口的 理/ 多线程 ? Win32线程和COM线程 ? marshaling和 ? “线程 ? 自 线程 ? 进程内 的线程模型 进程和线程 ? 进程 – 在Linux ,时“和 “ 的结合 – 在Windows , “ ? 线程 – 在Linux , ?? – 在Windows ,§ 调a , 时“ Win32线程 ? Win32?ˉ线程本 ?, ? ?用模型o分 ? ? CreateThread,创建线程 ?UI线程(user-interface thread) – 包 ,当线程? 调用Win32 User GDI函数时 ? – 包 ?, 合GetMessage/ TranslateMessage/DispatchMessage ? ??线程(worker thread) – ? ?线?, UI, COM线程 ? COM对象的 ?? ,分 “ 线程和自 线程 ? “线程(apartment thread) – 于 STA中(Single-Threaded Apartment) – “对? 线程 ? 自 线程(free thread) – 于MTA中(Multi-Threaded Apartment) – 进程 MTA,它o以包 任意 数量的自 线程 marshaling ? 调用者'—调用者如果 于不 的线程 中,则调用 程要 线程切换,线 程切换§需要用到marshaling机制 ?COM对象的线程相依 – 的COM对象 能在 线程中运? – 内 UI的COM对象 能在创建线程 运? ? 线程 “的marshaling机制'进程“的 marshaling 程 致 ? 能— 线程访问的对象不需要 – 例如Windows的窗口 程 – 但 对于全部?量的访问,仍需要 保护 ? o能—多 线程访问的对象需要 机制 – Event、Semaphore、CriticalSection、Mutex – 这样的 —称 thread-safe Apartment( “) ? 逻辑 ,§ ¥体对? ? COM对象的 ?? ? 分 ? “ –STA –MTA –COM+引入TNA(thread-neutral apartment) * STA “ ? 每 STA “包 线程 –STA “和线程 对?关? ? 当线程—创建后,用COM库初始 就建 起 STA “ – CoInitialize(NULL); – 者CoInitializeEx(NULL,COINIT_APARTMENTTHREADED); ? 在线程结束 前,调用CoUninitialize结束 “ ? “线程包 ? ?COM在 “线程中创建 隐藏的窗口 – 用?: 、分发 STA “(续) ? 在STA中创建的COM对象都属于这 STA ?STA对象不必处理 ,因 对象的方法 能—这 STA “的线程调用 ? 但DLL程序的引出函数如DllGetClassObject 和DllCanUnloadNow…仍需 处理。 ? 类厂 否需要线程安全,取决于类厂的策略 ? 如ˇ把接口指针交给调用者 – 自动marshaling – 手工marshaling STA接收调用示意图 STA 隐藏窗口分发 stub stub STA “传 接口指针 ? COM传 的接口指针,COM都会自 动marshaling ? 手工marshaling – CoMarshalInterface和CoUnmarshalInterface – CoMarshalInterThreadInterfaceInStream和 CoGetInterfaceAndReleaseStream “线程要点 ?STA客户调用STA对象的 程 –RPC ?, 传 调用,RPC ?发出 后, 调用MsgWaitForMultipleObjects阻塞调用方,但仍然分 发 ,fl以UI仍 活的 ?STA客户跨进程调用的 程 – 理对象先得到 RPC线程,并把 后的数?包 交给它,RPC线程又?成??线程, 它?fi跨进程 调用,RPC线程§?fi分发 ,fl以UI§ 活的 ? 从另 进程进入 “线程的 程。 – 调用?先到达 RPC线程,RPC线程向 “线程隐 藏窗口发送 ,并把 数?传 去 “线程:?型情形 ? 客户创建 “线程,然后 “线程的主函数创建COM 对象, “线程再调用 CoMarshalInterThreadInterfaceInStream函数,把接口指 针 到流对象中,然后 知客户线程对象已经创建 成功,客户线程接到 知后,利用流对象指针把对象 的接口指针散 出来,以后客户线程就o以'对象 讯 。 STA Main STA 流来 传 MTA “ ? 每 进程至多 MTA “ ?MTA “中o以包 者多 线程 – 线程初始 : CoInitializeEx(NULL, COINIT_MULTITHREADED); ? Windows的版本: –NT 4.0 者Windows 95 + DCOM扩展 ?MTA “中的对象必须thread-safe – —MTA中线程创建的对象 MTA对象 –MTA中的线程都o以直接访问MTA对象 – ?用Win32多线程编程? 保 thread-safe MTA接收调用示意图 MTA 调用 stub stub MTA线程 RPC线程 MTA中线程的 点 ? ?用CoInitializeEx(NULL, COINIT_MULTITHREADED)初始 ? 客户'对象都在MTA中,则调用直接在客户 线程中 ? ? 客户程序在另 进程中,则调用 proxy/stub,直接在RPC线程 ? ? 客户在STA中,则调用 proxy/stub,直接 在RPC线程 ? ? 隐藏窗口,MTA线程创建的对象并 ? 在任ˇ 的线程中, ? 在MTA中 ?MTA对象必须 线程安全的, 入 MTA线程要点 ?MTA线程中的客户调用STA对象 进程中的对象 – 客户调用 理对象, 理对象又调用RPC ?,RPC ?阻塞客户线程,如果客户线程 中 窗口的 ,则会— ? 调用进入MTA线程中的对象 –RPC ?直接?用RPC线程,并调用 , 再调用 对象。这中“ 线程 切换。 STA和MTA “关? ? 不 进程 “,不管 线程类型,都需要 proxy/stub ? 在 进程内不 STA “,§需要 proxy/stub ? 在STA内部, 对象调用另 对象的方 法不需要proxy/stub ? 在MTA内部,对象和调用者 “调用不需要 proxy/stub ? 从STA调用MTA,需要proxy/stub ? 从MTA调用STA,需要proxy/stub 进程内的STA和MTA STA STAMTA 进程 进程内对象的线程模型 ?CLSID\{clsid} InprocServer32 ThreadingModel = “Apartment” “Free”、“Both” ? DllGetClassObject和DllCanUnloadNow ? 当COM库创建对象时 – 如果客户线程模型'对象的要求 致 则在客户线程中创建对象,返回直接指针 – 如果客户线程模型'对象的要求不 致: COM会 ? 相?的线程 对象?用 “Both”类型 ? 这?对象必须 thread-safe ? 它 于创建者的 “中 ? 本 “Free”对象的 ,但 在运? 时 o能会 ?出“Apartment”对象的 ? 例 : – 接口proxy/stub都 “Both”类型 Main STA ? STA, 进程的主线程 ? 如果 COM class的ThreadingModel ,则 Main STA ? 在引入MTA 前的对象都?用Main STA 客户线程'对象模型 合 Proxy/stub 直接 Proxy/stub “”( ) Proxy/stubProxy/stub 直接 “Free” 直接直接 Proxy/Stub “Apartment” 直接直接直接 “Both” 非Main-STAMain STAMTA线程 客户线程 对象模型 进程外对象的线程模型 ? '客户线程模型 关? ? 程序自己来控制 – 类厂对象 CoRegisterClassObject来传 – 类厂的CreateInstance函数 COM+ ? ? – 对象的 ?? ,包 ? ,如¨ 、安全 … – 在 来的模型中, MTA和STA就 ? ? – 跨? 调用§需要proxy/stub ? 量 理 – 不需要线程切换的跨? 调用 ? TNA(Thread neutral apartment) COM+? ? Y ? Z ? W ? X 对象B 的 理 对象A 的 理 对象B 对象C 的 理 对象A 对象C 示 取 ? 从进程内¢向进程外 进程£? 客户 客户 COM 运?库 安全 · ? LPC DCE RPC COM 运?库 安全 · ? LPC DCE RPC 从本机¢向¥程主机 DCOM 客户 COM 运?库 安全 · ? ??§ DCE RPC COM 运?库 安全 · ? ??§ DCE RPC DCOMcurrency1'?? “?:RPC(Remote Procedure Call) ? RPC Client and RPC Server ? o以在TCP、UDP ¥? ?RPC 分布式?用的基?, 程如下: – 客户′?: (¥程机?、server、函数) – 在RPC Server 需要运? RPC?口管理 , 每 RPC Server向它fifl˙? – RPC client'¥程机的RPC?口管理 ?,请求 RPC Server的?口– – 然后RPC client'RPC server直接 ? ? ¥程调用??,如HTTP、SOAP DCOM要点 ? 创建¥程对象 ? 把进程内对象?到¥程机? ?DCOM的连接管理 ?DCOM并发 管理 ?DCOM安全 DCOM 对象的创建 程 客户 COM库 (OLE32.DLL) SCM (RPCSS.EXE) SCM (RPCSS.EXE) RPC 调用创建函数 ¥程创建 创建进程和对象 对象激活(activation) ? 对象激活: – 创建·的 对象——类厂对象 – 建 已 对象'客户 “的连接—— ?对象 ? ¥程对象的创建: – `? ¥程对象:CLSID+RemoteServerName ? 如ˇ?取RemoteServerName –DCOM?”工 指 ¥程 ? – 客户程序在 中?式指 ¥程 ? 创建DCOM ( ) ? ”£? :客户程序不必知? 运?在本 … ¥程机? ? RemoteServerName : HKEY_CLASSES_ROOT\APPID\{appid-guid} “RemoteServerName”=“<DNS name>” HKEY_CLASSES_ROOT\CLSID\{clasid-guid} “AppID”=“<appid-guid >” ? RemoteServerName 不能—传 ? 客户创建 对象的 不必‰ ?用DCOM?”工 ?” 的 RemoteServerName 创建DCOM (?) ? 在CoGetClassObject和CoCreateInstanceEx函数 中指 ? typedef struct _COSERVERINFO { DWORD dwReserved1; LPWSTR pwszName; COAUTHINFO *pAuthInfo; DWORD dwReserved2; } COSERVERINFO; typedef struct _MULTI_QI { const IID* pIID; IUnknown * pItf; HRESULT hr; } MULTI_QI; 用CoCreateInstanceEx创建¥程 例 HRESULT hr = S_OK; MULTIQI mqi[] = {{IID_IBackupAdmin, NULL , hr1}}; COSERVERINFO srvinfo = {0, NULL, NULL, 0}; Srvinfo.pwszName = pFileServerName; HRESULT hr = CoCreateInstanceEx( CLSID_MyBackupService, NULL, CLSCTX_SERVER, &srvinfo, sizeof(mqi)/sizeof(mqi[0]), &mqi); if (SUCCEEDED(hr)) { if (SUCCEEDED(mqi[0].hr)) { IBackupAdmin* pBackupAdmin=mqi[0].pItf; hr=pBackupAdmin->StartBackup(); pBackupAdmin->Release(); } } 用分` 对象 ¥?动?? ′功能 pA->CreateObjectB 客户机 ??? ? ?1 ?2 ?3 请求创建对象A ?ˉ ? 3 并创建对象B 返回对象B 客户直接'对象B连接 ??对象A ¥程创建进程内 : 理进程 (Surrogate) ? 理进程‰点: – 进程内 程序中的˙ ¨ ??到 理 进程,不会?客户进程 ?? – 理进程o以 时 多 客户· ? – 在 理进程中运?进程内 o?DLLˇ 理进程的安全 。 进程内 ——入 理进程的? ? ?ˉ˙? 中,在COM对象的CLSID关 ?下必 须要指 AppID ,以 对?的AppID关 ?? ? 客户程序在创建对象¥例时,必须 ” CLSCTX_LOCAL_SERVER` ? ? 对象的CLSID关 ?下不指 LocalServer32、 LocalServer、LocalService ? ? 对象的CLSID关 ?包 InProcServer32 ? ? 在InProcServer32 中指 的DLL 必须 在? ? 对象对?的AppID 下指 DllSurrogate 。 控制¥程对象的? ?DCOM‰ ¥程对象的AddRef和Release调用, 客户程序不必 ‰ ? OR (OXID Resolver) – OXID(object exporter identifier) 对象 ? OXID对象¥? IRemUnknown接口 – RemQueryInterface – RemAddRef和RemRelease ? 参 :MSJ,1998 3 – Understanding the DCOM Wire Protocol by Analyzing Network Data Packets DCOM?用ORPC??¥?¥程 OR service OR service Client Server OXID OXID OR RPC stub proxy ORPC IPC IPC Pinging机制( ) ? 对于非 情 , 进程非 ,客 户o以 ?返回 ?出来 ? 客户非 , 进程 a ? 检 客户程序 否非 ,DCOM· “pinging”机制 ? 每 —¥程?用的对象都 “pingPeriod”和 “numPingsToTimeOut” 数 Pinging机制(?) ?ping? :pingPeriod*numPingsToTimeOut ? 当前DCOM版本中, pingPeriod=2(分)currency1numPingsToTimeOut=3,这 ? 不能— ? ? pinging机制的‰ :OXID?o? ?ping , currency1'? 连接传 ? 连接 o传 ,因 接口的 数?(OR 者OBJREF)包 机?相关的 ? 连接传 '创建传 不 ,DCOM不支持 创建传 ? o用连接传 “接支持创建传 ? 利用连接传 o¥?动?? ′ 并发管理 ? 分布式? 下的基本问 ? 方式 向 方式 ? 多线程模型 ? ?? ??(message filter)机制 ? STA “ 的 ?控制机制 – 处理跨线程调用 程中的阻塞?象 ? o用于客户程序,§o用于 程序 ?COM本 · 的¥?,又 ?用自 的message filter Message filter ? ? 的COM对象,它¥? IMessageFilter接口: class IMessageFilter : public IUnknown { public : DWORD HandleInComingCall ( DWORD dwCallType, HTASK threadIDCaller, DWORD dwTickCount, LPINTERFACEINFO pInterfaceInfo) = 0; DWORD RetryRejectedCall(HTASK threadIDCallee, DWORD dwTickCount, DWORD dwRejectType) = 0; DWORD MessagePending (HTASK threadIDCallee, DWORD dwTickCount, DWORD dwPendingType) = 0; }; Message Filter?用示意图 Client-side MsgFilter Server-side MsgFilter RPC ?RPC ? 分发调用 客户调用 IMessageFilter 接口 ? 返回 类型都 DWORD ? 类型HTASK用来`? 调用, 逻辑线 程ID,在 机? 在 ? HandleInComingCall用于 对象 方的 ?? ? RetryRejectedCall和MessagePending用于客户 方的 ?? ¥? ??机制 ? 客户程序 者 程序¥? ??对象, 然后用CoRegisterMessageFilter函数指 ?用自 的 ???否则COM? 用 的 ??对象。 ? ?? 用于当前STA,它不支持 marshaling,不能跨 “?用 方的 ?? ? 当COM检 到 外部调用进入当前进程时, 它?先会调用 ??的 HandleInComingCall成 函数。 ? HandleInComingCall函数的参数dwCallType指 示 调用的类型,参数pInterfaceInfo`? 对 象、接口和成 函数 ? HandleInComingCall函数的返回 : – SERVERCALL_REJECTED – SERVERCALL_RETRYLATER – SERVERCALL_ISHANDLED 客户程序方的 ?? ? 当客户的调用— ?? 后,COM在? 关的处 理后调用 ??的RetryRejectedCall成 函数, 客户 决 否?续 ? RetryRejectedCall返回-1 示??调用?否则返回 以 的时“数,经 这 时“后,COM 再 调 用 对象 ? 对 框: 客户程序的 处理 ?RPC ?§ 客户程序分发 ,fl以在¥程调 用完成 前,客户程序的 §会接收到 ,MessagePending成 函数让客户 机会控制 处理方案。 ? 返回 : – PENDINGMSG_CANCELCALL - 取 当前调用 – PENDINGMSG_WAITNOPROCESS - 客户不处理 – PENDINGMSG_WAITDEFPROCESS - 处理部分 , 丢掉 DCOM安全模型 ? Windows NT安全机制 ? 激活安全 ? 调用安全 ? 运?时 动? ”安全 ? 安全 ?” Windows NT安全模型 ? domain、user和user group ? authentication, ? security identifier,SID ? security description,SD ? Access Control List,ACL Windows NT安全模型 ? 用户和 源管理 – 用户的 份 –SID描述用户 –SD描述 源的安全 ? SSP(Security Support Providers) – SSPI 激发安全 (launching security) ? SCM来控制。决 “哪?用户能够( 者不能够)在激活时 启动 ?进程” ? 因 此安全 决 客户 否 启动进 程的 o,fl以这 安全 不能在程序 中控制 ? ˙? ¥施launching security – 先找到APPID的LaunchPermission ” – 机?范围内的 ” 访问安全 ? 决 “哪?用户o以' ?进程的对象 进?¥际的 ” ? 控制 程 – 进程调用CoInitializeSecurity –AppID 中的AccessPermission ” – 机?范围内的DefaultAccessPermission ” – 隐式调用CoInitializeSecurity, ?进程 的principal和SYSTEM账– 动?安全 控制 ? 进程调用 CoInitiallizeSecurity函数,则进程不 再?用˙? 的静? ” HRESULT CoInitializeSecurity( PSECURITY_DESCRIPTOR pVoid, //Points to security descriptor DWORD cAuthSvc, //Count of entries in asAuthSvc SOLE_AUTHENTICATION_SERVICE * , //Array of names to register void * pReserved1, //Reserved for future use DWORD dwAuthnLevel, //The default authentication level for proxies DWORD dwImpLevel, //The default impersonation level for proxies RPC_AUTH_IDENTITY_HANDLE pAuthInfo, // Reserved DWORD dwCapabilities, //Additonal client and/or serverside capabilities void * pvReserved2); //Reserved for future use ? IAccessControl接口 动?安全 控制(续) ? CoInitiallizeSecurity支持 ?类型 ”方式, pVoid和dwCapabilities参数决 : –AppID, 向˙? ” –SD,'Win32安全编程模型结合 – IAccessControl接口,? 安全编程模型 ? 级别,authentication level – 本进程的引入 引出对象至少?用 级别 ? 模仿级别,impersonation level – 客户 对象能够 ?哪?–?,客户o以隐 藏自己的 份、禁 对象模仿自己… IAccessControl接口 ? IAccessControl接口o以? 安全编 程模型 ? 多数?结构用来描述访问控制 ?COM· IAccessControl的 ¥?, ?用程序o以创建 ¥例,然后 ”内 部的安全控制 ? ?用程序§o以自己¥?IAccessControl 以便更加灵活…控制访问 o 调用安全 ,calling security ? 客户 理对象的IClientSecurity接口, 控制调用安全 , – 对某 接口 理 ”安全 – 对某 “的某 接口 理 ”安全 ? 程序调用CoGetCallContext?得 IServerSecurity接口,进 ?得安全 激活安全 ? 几 创建函数中包 COSERVERINFO 结构, 中的pAuthInfo 成 指向 typedef struct _COAUTHINFO { DWORD dwAuthnSvc; DWORD dwAuthzSvc; LPWSTR pwszServerPrincName; DWORD dwAuthnLevel; DWORD dwImpersonationLevel; COAUTHIDENTITY * pAuthIdentityData; DWORD dwCapabilities; } COAUTHINFO; ? 激活安全 ??激活 程,§就 创建 程, 当创建完成 后,客户得到的接口指针不受激 活安全 的?? ?进程的 份 ? 决 “ ?进程 运?在哪 用户的 份下”,AppID下的RunAs ? ? ”方案: – 交互用户,interactive user – 启动用户,launching user – 指 用户 ? ?方案各 利弊,适用于不 的场合 安全 ?”( ) ? 几 ˙? 称 LaunchPermission AccessPermission RunAs: Run as Activator Run as Interactive User Run as a fixed user account ?DCOMˉ ?用的 ” HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole "EnableDCOM"="Y" "DefaultAccessPermission" = <self-relative security descriptor> "DefaultLaunchPermission" = <self-relative security descriptor> "LegacyImpersonationLevel" = <dword_impLevel> "LegacyAuthenticationLevel" = <dword_authLevel> 安全 ?”(?) 安全 ?”( ) 小结:DCOM ? o伸缩 ? o?” ? 安全 ? ?? 关 ? 独 ?发DCOM ? ?先保 本…server成功 – ˙?类厂 – 自 接口编写proxy/stub ? ?”安全 – 从本…¢向¥程的 本? ?DLL , MTS和COM+ DCOM 见问 ? 安全 ” 问 ? 创建成功,但 QueryInterface 接口 失败,o能接口proxy/stub˙? 问 ? 调试 –/embedding –HRESULT – RunAs=Interactive User