610 likes | 946 Views
DCOM. 5 、 DCOM. COM 特性 语言无关性 二进制 复用性 包含方式 聚合方式 进程透明性 进程内服务程序 : DLL 本地服务程序 : EXE 远地服务程序 : DLL 或 EXE =》DCOM. DCOM. 为什么需要跨越进程、跨越计算机? 软件规模持续增加 不同 EXE 程序之间需要交互 不同 EXE 程序可以分布在 不同的计算机上 适用于分布式计算环境 关键问题:进程空间不同. DCOM. 内 容 跨越进程 IDL/MIDL 本地服务器的实现
E N D
DCOM 5、DCOM • COM特性 • 语言无关性 • 二进制 • 复用性 • 包含方式 • 聚合方式 • 进程透明性 • 进程内服务程序: DLL • 本地服务程序: EXE • 远地服务程序: DLL或EXE =》DCOM
DCOM 为什么需要跨越进程、跨越计算机? 软件规模持续增加 不同EXE程序之间需要交互 不同EXE程序可以分布在 不同的计算机上 适用于分布式计算环境 关键问题:进程空间不同
DCOM 内 容 跨越进程 IDL/MIDL 本地服务器的实现 远程访问(DCOM)的实现 DCOM的特性
DCOM (1)跨越进程 每一个EXE文件都将在不同的进程中运行 每一个进程都有自己的进程空间 DLL将被映射到连接它们的EXE文件的进程空间 DLL被称为进程中服务器(Inproc server) EXE被称为进程外服务器(Outproc server) DLL提供的构件将接口传给客户 一个接口实际上是一个函数指针数组 客户必须能够访问同接口相关联的内存
DCOM 对于跨越进程边界的接口 需满足如下要求: 一个进程需要能够调用 另外一个进程中的函数 一个进程需要能够将数据传递给 另外一个进程 客户无须关心它所访问的服务器 是DLL还是EXE
本地过程调用 DCOM 不同进程间通信的方法: DDE 管道 共享内存等 LPC COM:LPC 基于RPC的单机进程间通信技术 DCOM:RPC
DCOM LPC由操作系统实现 进程边界 EXE EXE 客户 构件 LPC
编排(Marshal) DCOM 将函数调用的参数从一个进程的地址空间中 传到 另外一个进程的地址空间中 LPC:将参数数据从一个进程的地址空间 复制到 另外一个进程的地址空间中 RPC:需要考虑不同机器在数据表示方面的不同 支持编排的接口为: IMarshal
代理/残根DLL(surrogate/stub) DCOM 客户同一个模仿构件的DLL通信 这个DLL可以为客户完成 参数的编排 与LPC调用 这个DLL被称为 代理 构件需要另一个DLL 这个DLL为构件完成 参数的反编排 与LPC调用
DCOM EXE EXE 客户 构件 进程边界 DLL DLL 代理 残根 LPC
(2)IDL/MIDL DCOM 先利用IDL语言编写接口的描述 在利用MIDL编译器生成代理和残根DLL 与UUID、RPC类似 IDL是从开发软件基金会OSF的 分布式计算环境(DCE)借用过来的 IDL语法与 C、C++相同 COM只利用了IDL的一个子集 并进行了扩展 MIDL编译器接收接口的IDL描述 并生成相应的代理和残根DLL
DCOM IDL 开发人员可以不知道LPC的细节 但必须知道如何利用IDL描述自己的接口 但是 IDL不规范: 存在不一致 文档资料差 缺乏典型的好例子 IDL可用于: 生成代理和残根DLL 建立类型库(Type Library)
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) ; } ;
DCOM 接口头(属性列表): (1)object: 所定义的接口为一个COM接口 (2)uuid(...):接口的IID (3)helpstring(...):将帮助串放类型库中 (4)pointer_default(unique):如何处理指针 使用IDL的目的在于提供足够的信息 以便函数参数可以被调整 因此IDL需要知道如何处理指针 pointer_default的作用在于告诉MIDL 在没有为指针指定其它属性时如何处理指针
DCOM pointer_default具有三个不同的选项: ref: 将指针当成引用对待 该类指针 总是指向一个合法的地址 不能为空 在调用前后指向相同的内存地址 在函数内部不能为其指定别名 unique: 该类指针 可以为空 在函数内可以修改其值 不能为其指定别名 ptr: 将指针指定为C指针 该类指针 可以有别名 可以为空 可以修改其值 MIDL 将使用这些值对它生成的代理及残根代码优化
IDL中的输入/输出参数 DCOM 利用 in , out这两个参数属性 MIDL可以对代理及残根代码进行进一步的优化 对于被标记为 in 的参数 MIDL将知道 仅仅需要将此参数值从客户传递给构件 残根代码不需要回送任何值 对于被标记为 out 的参数 MIDL将知道 该参数仅被用类从构件向客户回送数据 代理不需对输出参数值进行调整 也不需将该值传送给构件 可以同时使用两个关键字标记某一参数 输出参数必须为指针
IDL中的字符串 DCOM 对数据块进行编排时 必须知道数据块的大小 C++串的长度易于决定 COM对字符串的标准约定是使用Unicode字符:wchar_t 也可以使用LOLECHAR 或者 LPOLESTR IDL中的import 与C++中的 #include 类似 可以使用任意次 而不会引起重复定义 所有COM及OLE(ActiveX)的标准接口 皆定义于相应的IDL中 一般位于C++编译器的 \INCLUDE文件中
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[]) ; } ;
DCOM MIDL编译器 foo.idl foo.def makefile Midl.exe foo.ii foo_p.c foo_i.c ... dlldata.c C编译器 与连接器 foo.dll
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)
DCOM (3)本地服务器的实现 进程内服务器模式中 DllGetClassObject 创建类厂 因为EXE不输出函数 必须给CoGetClassObject提供 获取IClassFactory的方法 COM的解决方案是: 维护一个关于被登记类厂的内部表格
DCOM 当客户调用CoGetClassObject时 COM首先检查关于类厂的私有表格 以得到与客户请求的CLSID相应的类厂 若相应的类厂不在表格中 COM在注册表中查找 并启动相应的EXE 此EXE将完成相应类厂的登记 EXE调用CoRegisterClassObject 完成类厂的登记
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 ;
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 ; }
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
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) ; 是等价的
(4)远程访问的实现 DCOM 前述的本地服务器可以是远地服务器 即 不需要对CLIENT.EXE或SERVER.EXE 进行任何修改 需要系统的支持: Windows NT4.0 或者 DCOM for Windows95 它们需要DCOM配置工具 DCOMCNFG.EXE
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
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
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;
(5)DCOM的特性 DCOM 平台独立性 应用系统可以跨越不同的操作系统 原因:建立在DCE的RPC基础之上 协议无关性 应用系统是协议无关的 目前支持:TCP/IP、UDP 、 IPX/SPX、NetBIOS 可伸缩性 应用系统能适应规模的变化 支持多CPU
DCOM 可配置性 对 服务器的变化 客户程序的自动安装 等 可以灵活配置 安全性 使用了 Windows NT 提供的 可扩展安全性框架 实现的安全性包括: 访问安全性 激发安全性 等
自动化 6、自动化 一种 客户与构件之间通信的方法 COM接口 自动化(OLE自动化) Word、Excel 及VB、Java都使用了自动化技术 特点: 使得用解释性语言和宏语言 访问COM构件更为容易 编写COM构件也将更为容易 关注运行时的类型检查 以牺牲速度与编译时类型检查为代价
自动化 自动化不独立于COM 建立在COM基础上 一个自动化服务器 是一个实现了 IDispatch 接口的COM构件 一个自动化控制器 是一个通过 IDispatch 接口 同自动化服务器进行通信的COM客户 自动化控制器不直接调用自动化服务器实现的函数 而是通过 IDispatch 接口中的成员函数 实现对服务器中函数的间接调用
自动化 内 容 IDispatch 接口 IDispatch 的使用 类型库 IDispatch 的实现
自动化 (1)IDispatch 接口 IDispatch接口是作为 VB 的一部分开发出来的 COM构件通过一个标准的接口( IDispatch ) 提供构件支持的服务 而不必提供多个特定于服务的接口 IDispatch 将接收一个函数的名称并执行它
自动化 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 );
自动化 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 );
自动化 执行函数时 自动化控制程序将调度标识 传给 Invoke 成员函数 自动化服务器根据调度标识 使用 case 语句执行不同的代码 Invoke 的工作方式与 vtbl 类似: 都实现一组按索引访问的函数 都可以定义接口 IDispatch :: Invoke 的一个实现所包含的函数集 被称作一个调度接口 (不是COM接口)
自动化 调度接口 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
自动化 调度接口 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
自动化 调度接口 FooBar继承Idispatch接口 IDispatch * pIDispatch DISPID 名称 pVtbl QueryInterface AddRef 1 “Foo” Release GetIIDsOfNames 函数 2 “Bar” GetTypeInfoCount 3 “FooBar” GetTypeInfo GetIIDsOfNames Invoke Invoke 函数 &Foo &Bar &FooBar 双重接口
自动化 // 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() ; } ;
(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
自动化 Word wFlags: 指定所调用函数的类型: DISPATCH_METHOD 常规函数 DISPATCH_PROPERTYGET 获取属性函数 DISPATCH_PROPERTYPUT 设置属性函数 DISPATCH_PROPERTYPUTREF 通过引用设置属性的函数
自动化 DISPPARAMS * pDispParams 传递给被调用函数的参数 typedef struct tagDISPPARAMS{ VARIANTARG* rgvarg; DISPID* rgdispidNamedArgs; unsigned int cArgs; unsigned int cNamedArgs; } DISPPARAMS; 其中,每一个参数类型都是 VARIANTARG。 VARIANTARG 是许多不同类型值的联合 这导致了:只有那些能够被放到VARIANTARG结构 中的类型才可以通过调度接口进行传递。
自动化 VARIANT * pVarResult 指向VARIANT结构的指针 用于Invoke所执行的函数的结果。 EXCEPINFO * pExcepInfo 指向一个 EXCEPINFO的指针 用于表示执行过程中遇到的例外情况 UINT * puArgErr 保存与执行错误相应的参数的索引 Invoke的返回值为: DISP_E_PARAMNOTFOUND 或DISP_E_TYPEMISMATCH
自动化 • 例子 • 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
自动化 // 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, ¶m, NULL, NULL, NULL) ; // Clean up ::SysFreeString(bstrIn) ;
自动化 • VARIANT类型 对于VARIANT结构的初始化 是借助于函数VariantInit完成的。 在VARIANT结构中标识所保存数据的类型。 VARIANT 使得 VB 程序在调用一个调度接口方法之前 不需要知道此方法需要哪些参数 VB将接受用户输入的任何参数并将其填充 到VARIANT结构中 这与利用头文件描述函数接口的强类型检查方式大为不同。 将类型检查推迟到运行时完成 需要 调度接口的方法和函数对它们接受到 的参数的类型进行检查