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