1 / 61

5 、 DCOM

DCOM. 5 、 DCOM. COM 特性 语言无关性 二进制 复用性 包含方式 聚合方式 进程透明性 进程内服务程序 : DLL 本地服务程序 : EXE 远地服务程序 : DLL 或 EXE =》DCOM. DCOM. 为什么需要跨越进程、跨越计算机? 软件规模持续增加 不同 EXE 程序之间需要交互 不同 EXE 程序可以分布在 不同的计算机上 适用于分布式计算环境 关键问题:进程空间不同. DCOM. 内 容 跨越进程 IDL/MIDL 本地服务器的实现

koren
Télécharger la présentation

5 、 DCOM

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. DCOM 5、DCOM • COM特性 • 语言无关性 • 二进制 • 复用性 • 包含方式 • 聚合方式 • 进程透明性 • 进程内服务程序: DLL • 本地服务程序: EXE • 远地服务程序: DLL或EXE =》DCOM

  2. DCOM 为什么需要跨越进程、跨越计算机? 软件规模持续增加 不同EXE程序之间需要交互 不同EXE程序可以分布在 不同的计算机上 适用于分布式计算环境 关键问题:进程空间不同

  3. DCOM 内 容 跨越进程 IDL/MIDL 本地服务器的实现 远程访问(DCOM)的实现 DCOM的特性

  4. DCOM (1)跨越进程 每一个EXE文件都将在不同的进程中运行 每一个进程都有自己的进程空间 DLL将被映射到连接它们的EXE文件的进程空间 DLL被称为进程中服务器(Inproc server) EXE被称为进程外服务器(Outproc server) DLL提供的构件将接口传给客户 一个接口实际上是一个函数指针数组 客户必须能够访问同接口相关联的内存

  5. DCOM 对于跨越进程边界的接口 需满足如下要求: 一个进程需要能够调用 另外一个进程中的函数 一个进程需要能够将数据传递给 另外一个进程 客户无须关心它所访问的服务器 是DLL还是EXE

  6. 本地过程调用 DCOM 不同进程间通信的方法: DDE 管道 共享内存等 LPC COM:LPC 基于RPC的单机进程间通信技术 DCOM:RPC

  7. DCOM LPC由操作系统实现 进程边界 EXE EXE 客户 构件 LPC

  8. 编排(Marshal) DCOM 将函数调用的参数从一个进程的地址空间中 传到 另外一个进程的地址空间中 LPC:将参数数据从一个进程的地址空间 复制到 另外一个进程的地址空间中 RPC:需要考虑不同机器在数据表示方面的不同 支持编排的接口为: IMarshal

  9. 代理/残根DLL(surrogate/stub) DCOM 客户同一个模仿构件的DLL通信 这个DLL可以为客户完成 参数的编排 与LPC调用 这个DLL被称为 代理 构件需要另一个DLL 这个DLL为构件完成 参数的反编排 与LPC调用

  10. DCOM EXE EXE 客户 构件 进程边界 DLL DLL 代理 残根 LPC

  11. (2)IDL/MIDL DCOM 先利用IDL语言编写接口的描述 在利用MIDL编译器生成代理和残根DLL 与UUID、RPC类似 IDL是从开发软件基金会OSF的 分布式计算环境(DCE)借用过来的 IDL语法与 C、C++相同 COM只利用了IDL的一个子集 并进行了扩展 MIDL编译器接收接口的IDL描述 并生成相应的代理和残根DLL

  12. DCOM IDL 开发人员可以不知道LPC的细节 但必须知道如何利用IDL描述自己的接口 但是 IDL不规范: 存在不一致 文档资料差 缺乏典型的好例子 IDL可用于: 生成代理和残根DLL 建立类型库(Type Library)

  13. DCOM IDL例子: import "unknwn.idl" ; // Interface IX [ object, uuid(32bb8323-b41b-11cf-a6bb-0080c7b2d682), helpstring("IX Interface"), pointer_default(unique) ] interface IX : IUnknown { HRESULT FxStringIn([in, string] wchar_t* szIn) ; HRESULT FxStringOut([out, string] wchar_t** szOut) ; } ;

  14. DCOM 接口头(属性列表): (1)object: 所定义的接口为一个COM接口 (2)uuid(...):接口的IID (3)helpstring(...):将帮助串放类型库中 (4)pointer_default(unique):如何处理指针 使用IDL的目的在于提供足够的信息 以便函数参数可以被调整 因此IDL需要知道如何处理指针 pointer_default的作用在于告诉MIDL 在没有为指针指定其它属性时如何处理指针

  15. DCOM pointer_default具有三个不同的选项: ref: 将指针当成引用对待 该类指针 总是指向一个合法的地址 不能为空 在调用前后指向相同的内存地址 在函数内部不能为其指定别名 unique: 该类指针 可以为空 在函数内可以修改其值 不能为其指定别名 ptr: 将指针指定为C指针 该类指针 可以有别名 可以为空 可以修改其值 MIDL 将使用这些值对它生成的代理及残根代码优化

  16. IDL中的输入/输出参数 DCOM 利用 in , out这两个参数属性 MIDL可以对代理及残根代码进行进一步的优化 对于被标记为 in 的参数 MIDL将知道 仅仅需要将此参数值从客户传递给构件 残根代码不需要回送任何值 对于被标记为 out 的参数 MIDL将知道 该参数仅被用类从构件向客户回送数据 代理不需对输出参数值进行调整 也不需将该值传送给构件 可以同时使用两个关键字标记某一参数 输出参数必须为指针

  17. IDL中的字符串 DCOM 对数据块进行编排时 必须知道数据块的大小 C++串的长度易于决定 COM对字符串的标准约定是使用Unicode字符:wchar_t 也可以使用LOLECHAR 或者 LPOLESTR IDL中的import 与C++中的 #include 类似 可以使用任意次 而不会引起重复定义 所有COM及OLE(ActiveX)的标准接口 皆定义于相应的IDL中 一般位于C++编译器的 \INCLUDE文件中

  18. DCOM IDL中的 size_is [ object, uuid(32bb8324-b41b-11cf-a6bb-0080c7b2d682), helpstring("IY Interface"), pointer_default(unique) ] interface IY : IUnknown { HRESULT FyCount([out] long* sizeArray) ; HRESULT FyArrayIn([in] long sizeIn, [in, size_is(sizeIn)] long arrayIn[]) ; HRESULT FyArrayOut([out, in] long* psizeInOut, [out, size_is(*psizeInOut)] long arrayOut[]) ; } ;

  19. DCOM MIDL编译器 foo.idl foo.def makefile Midl.exe foo.ii foo_p.c foo_i.c ... dlldata.c C编译器 与连接器 foo.dll

  20. DCOM Makefile 中的主要语句: midl /h Iface.h /iid Guids.c /proxy Proxy.c Server.idl Server.dll: $(SERVER_OBJS) Server.def link $(DLL_LINK_FLAGS) $(SERVER_OBJS) libcmtd.lib \ libcimtd.lib $(LIBS) /DEF:Server.def regsvr32 -s Server.dll Server.exe: $(SERVER_OBJS) $(DIR_SERVER)\OutProc.obj Server.res link $(EXE_LINK_FLAGS) $(SERVER_OBJS) \ $(DIR_SERVER)\OutProc.obj Server.res libcmtd.lib \ libcimtd.lib $(LIBS) user32.lib gdi32.lib Server /RegServer Client.exe : Client.obj Guids.obj Util.obj link $(EXE_LINK_FLAGS) Client.obj Guids.obj Util.obj\ libcmtd.lib libcimtd.lib $(LIBS)

  21. DCOM (3)本地服务器的实现 进程内服务器模式中 DllGetClassObject 创建类厂 因为EXE不输出函数 必须给CoGetClassObject提供 获取IClassFactory的方法 COM的解决方案是: 维护一个关于被登记类厂的内部表格

  22. DCOM 当客户调用CoGetClassObject时 COM首先检查关于类厂的私有表格 以得到与客户请求的CLSID相应的类厂 若相应的类厂不在表格中 COM在注册表中查找 并启动相应的EXE 此EXE将完成相应类厂的登记 EXE调用CoRegisterClassObject 完成类厂的登记

  23. DCOM BOOL CFactory::StartFactories() { CFactoryData* pStart = &g_FactoryDataArray[0] ; const CFactoryData* pEnd = &g_FactoryDataArray[g_cFactoryDataEntries - 1] ; for(CFactoryData* pData = pStart ; pData <= pEnd ; pData++) { // Initialize the class factory pointer and cookie. pData->m_pIClassFactory = NULL ; pData->m_dwRegister = NULL ; // Create the class factory for this component. IClassFactory* pIFactory = new CFactory(pData) ; // Register the class factory. DWORD dwRegister ;

  24. DCOM HRESULT hr = ::CoRegisterClassObject( *pData->m_pCLSID, static_cast<IUnknown*>(pIFactory), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dwRegister) ; if (FAILED(hr)){ pIFactory->Release() ; return FALSE ; } // Set the data. pData->m_pIClassFactory = pIFactory ; pData->m_dwRegister = dwRegister ; } return TRUE ; }

  25. DCOM 构件注册: CoRegisterClassObject( *pData->m_pCLSID, static_cast<IUnknown*>(pIFactory), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dwRegister) ; m_pCLSID:类标识 pIFactory: 类厂指针 第四个参数:EXE的单个实例 能否支持一个构件的多个实例 REGCLS_SINGLEUSER: 单个实例 则第三个参数为CLSCTX_LOCAL_SERVER REGCLS_MULTI_SEPARATE:多个实例 第三个参数可以为CLSCTX_LOCAL_SERVER 或者 CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER

  26. DCOM CoRegisterClassObject( *pData->m_pCLSID, static_cast<IUnknown*>(pIFactory), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dwRegister) ; 与 CoRegisterClassObject( *pData->m_pCLSID, static_cast<IUnknown*>(pIFactory), CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER, REGCLS_MULTI_SEPARATE, &dwRegister) ; 是等价的

  27. (4)远程访问的实现 DCOM 前述的本地服务器可以是远地服务器 即 不需要对CLIENT.EXE或SERVER.EXE 进行任何修改 需要系统的支持: Windows NT4.0 或者 DCOM for Windows95 它们需要DCOM配置工具 DCOMCNFG.EXE

  28. DCOM 从远地服务器运行SERVER.EXE的步骤 : 动作 本地系统 远程系统 建立CLIENT.EXE、SERVER.EXE 以及 PROXY.DLL  将CLIENT.EXE、SERVER.EXE 以及 PROXY.DLL复制到远地  使用命令 server/RegServer登记本地服务器   使用命令 regsvr32 Proxy.dll登记代理   运行CLIENT.EXE,并选择本地服务器   运行DCOMCNFG.EXE, 输入将要运行 SERVER.EXE的远程机器名  单击Identity选项,选择Interactive User  根据自己的权限设置Security选项   运行SERVER.EXE  运行CLIENT.EXE 

  29. DCOM 创建构件函数: CoCreateInstanceEx( CLSID_COMPONENT1, NULL, CLSCTX_REMOTE_SERVER, &ServerInfo, 3,//Number of interfaces &mqi ): Typedef struct _COSERVERINFO{ DWORD dwReserved1; LPWSTR pwszName; //远地机器名 COAUTHINFO *pAuthInfo; //创建对象的激活安全信息 DWORD dwReserved2; } COSERVERINFO

  30. DCOM mqi: multi QueryInterface, 用于同时查询多个接口 MULTI_QI mqi[3]; mqi[0].pIID = IID_IX; mqi[0].pItf = NULL; mqi[0].hr = S_OK; mqi[1].pIID = IID_IY; mqi[1].pItf = NULL; mqi[1].hr = S_OK; mqi[2].pIID = IID_IZ; mqi[2].pItf = NULL; mqi[2].hr = S_OK;

  31. (5)DCOM的特性 DCOM 平台独立性 应用系统可以跨越不同的操作系统 原因:建立在DCE的RPC基础之上 协议无关性 应用系统是协议无关的 目前支持:TCP/IP、UDP 、 IPX/SPX、NetBIOS 可伸缩性 应用系统能适应规模的变化 支持多CPU

  32. DCOM 可配置性 对 服务器的变化 客户程序的自动安装 等 可以灵活配置 安全性 使用了 Windows NT 提供的 可扩展安全性框架 实现的安全性包括: 访问安全性 激发安全性 等

  33. 自动化 6、自动化 一种 客户与构件之间通信的方法 COM接口 自动化(OLE自动化) Word、Excel 及VB、Java都使用了自动化技术 特点: 使得用解释性语言和宏语言 访问COM构件更为容易 编写COM构件也将更为容易 关注运行时的类型检查 以牺牲速度与编译时类型检查为代价

  34. 自动化 自动化不独立于COM 建立在COM基础上 一个自动化服务器 是一个实现了 IDispatch 接口的COM构件 一个自动化控制器 是一个通过 IDispatch 接口 同自动化服务器进行通信的COM客户 自动化控制器不直接调用自动化服务器实现的函数 而是通过 IDispatch 接口中的成员函数 实现对服务器中函数的间接调用

  35. 自动化 内 容 IDispatch 接口 IDispatch 的使用 类型库 IDispatch 的实现

  36. 自动化 (1)IDispatch 接口 IDispatch接口是作为 VB 的一部分开发出来的 COM构件通过一个标准的接口( IDispatch ) 提供构件支持的服务 而不必提供多个特定于服务的接口 IDispatch 将接收一个函数的名称并执行它

  37. 自动化 interface IDispatch : IUnknown{ typedef [unique] IDispatch * LPDISPATCH; HRESULT GetTypeInfoCount( [out] UINT * pctinfo ); HRESULT GetTypeInfo( [in] UINT iTInfo, [in] LCID lcid, [out] ITypeInfo ** ppTInfo ); HRESULT GetIDsOfNames( [in] REFIID riid, [in, size_is(cNames)] LPOLESTR * rgszNames, [in] UINT cNames, [in] LCID lcid, [out, size_is(cNames)] DISPID * rgDispId );

  38. 自动化 HRESULT Invoke( [in] DISPID dispIdMember, [in] REFIID riid, [in] LCID lcid, [in] WORD wFlags, [in, out] DISPPARAMS * pDispParams, [out] VARIANT * pVarResult, [out] EXCEPINFO * pExcepInfo, [out] UINT * puArgErr );

  39. 自动化 执行函数时 自动化控制程序将调度标识 传给 Invoke 成员函数 自动化服务器根据调度标识 使用 case 语句执行不同的代码 Invoke 的工作方式与 vtbl 类似: 都实现一组按索引访问的函数 都可以定义接口 IDispatch :: Invoke 的一个实现所包含的函数集 被称作一个调度接口 (不是COM接口)

  40. 自动化 调度接口 Idispatch接口 DISPID 名称 Idispatch * pIDispatch 1 “Foo” pVtbl QueryInterface GetIIDsOfNames 函数 2 “Bar” AddRef 3 “FooBar” Release GetTypeInfoCount DISPID 函数指针 GetTypeInfo Invoke 函数 GetIIDsOfNames 1 &Foo Invoke 2 &Bar 3 &FooBar 使用 Idispatch 接口实现Idisppatch::Invoke

  41. 自动化 调度接口 Idispatch接口 DISPID 名称 Idispatch * pIDispatch 1 “Foo” pVtbl QueryInterface GetIIDsOfNames 函数 2 “Bar” AddRef 3 “FooBar” Release GetTypeInfoCount FooBar 接口 GetTypeInfo Invoke 函数 GetIIDsOfNames pVtbl &Foo Invoke &Bar &FooBar 使用一个COM接口实现Idisppatch::Invoke

  42. 自动化 调度接口 FooBar继承Idispatch接口 IDispatch * pIDispatch DISPID 名称 pVtbl QueryInterface AddRef 1 “Foo” Release GetIIDsOfNames 函数 2 “Bar” GetTypeInfoCount 3 “FooBar” GetTypeInfo GetIIDsOfNames Invoke Invoke 函数 &Foo &Bar &FooBar 双重接口

  43. 自动化 // Interface IX [ object, uuid(32BB8326-B41B-11CF-A6BB-0080C7B2D682), helpstring("IX Interface"), pointer_default(unique), dual, oleautomation ] interface IX : IDispatch{ import "oaidl.idl" ; HRESULT Fx() ; HRESULT FxStringIn([in] BSTR bstrIn) ; HRESULT FxStringOut([out, retval] BSTR* pbstrOut) ; HRESULT FxFakeError() ; } ;

  44. (2)IDispatch 的使用 自动化 • Invoke函数的参数 [in] DISPID dispIdMember, 控制程序待调用函数的DISPID [in] REFIID riid, 保留(IID_NULL) [in] LCID lcid, 位置信息 [in] WORD wFlags, [in, out] DISPPARAMS * pDispParams, [out] VARIANT * pVarResult, [out] EXCEPINFO * pExcepInfo, [out] UINT * puArgErr

  45. 自动化 Word wFlags: 指定所调用函数的类型: DISPATCH_METHOD 常规函数 DISPATCH_PROPERTYGET 获取属性函数 DISPATCH_PROPERTYPUT 设置属性函数 DISPATCH_PROPERTYPUTREF 通过引用设置属性的函数

  46. 自动化 DISPPARAMS * pDispParams 传递给被调用函数的参数 typedef struct tagDISPPARAMS{ VARIANTARG* rgvarg; DISPID* rgdispidNamedArgs; unsigned int cArgs; unsigned int cNamedArgs; } DISPPARAMS; 其中,每一个参数类型都是 VARIANTARG。 VARIANTARG 是许多不同类型值的联合 这导致了:只有那些能够被放到VARIANTARG结构 中的类型才可以通过调度接口进行传递。

  47. 自动化 VARIANT * pVarResult 指向VARIANT结构的指针 用于Invoke所执行的函数的结果。 EXCEPINFO * pExcepInfo 指向一个 EXCEPINFO的指针 用于表示执行过程中遇到的例外情况 UINT * puArgErr 保存与执行错误相应的参数的索引 Invoke的返回值为: DISP_E_PARAMNOTFOUND 或DISP_E_TYPEMISMATCH

  48. 自动化 • 例子 • hr = pIDispatch->GetIDsOfNames(IID_NULL, • &name, • 1, • GetUserDefaultLCID(), • &dispid) ; • wchar_t wszIn[] = L"This is the test." ; • // Convert the wide-character string to a BSTR. • BSTR bstrIn ; • bstrIn = ::SysAllocString(wszIn) ; • // Allocate and initialize a VARIANT argument. • VARIANTARG varg ; • ::VariantInit(&varg) ; // Initialize the VARIANT. • varg.vt = VT_BSTR ; // Type of VARIANT data • varg.bstrVal = bstrIn ; // Data for the VARIANT

  49. 自动化 // Fill in the DISPPARAMS structure. DISPPARAMS param ; param.cArgs = 1 ; // Number of arguments param.rgvarg = &varg ; // Arguments param.cNamedArgs = 0 ; // Number of named args param.rgdispidNamedArgs = NULL ; // Named arguments hr = pIDispatch->Invoke(dispid, IID_NULL, GetUserDefaultLCID(), DISPATCH_METHOD, &param, NULL, NULL, NULL) ; // Clean up ::SysFreeString(bstrIn) ;

  50. 自动化 • VARIANT类型 对于VARIANT结构的初始化 是借助于函数VariantInit完成的。 在VARIANT结构中标识所保存数据的类型。 VARIANT 使得 VB 程序在调用一个调度接口方法之前 不需要知道此方法需要哪些参数 VB将接受用户输入的任何参数并将其填充 到VARIANT结构中 这与利用头文件描述函数接口的强类型检查方式大为不同。 将类型检查推迟到运行时完成 需要 调度接口的方法和函数对它们接受到 的参数的类型进行检查

More Related