Presentation is loading. Please wait.

Presentation is loading. Please wait.

十四. 名字对象 概念 IMoniker接口 名字对象的创建 根据显示名创建名字对象 类名字对象的创建 其他名字对象的创建

Similar presentations


Presentation on theme: "十四. 名字对象 概念 IMoniker接口 名字对象的创建 根据显示名创建名字对象 类名字对象的创建 其他名字对象的创建"— Presentation transcript:

1 十四. 名字对象 概念 IMoniker接口 名字对象的创建 根据显示名创建名字对象 类名字对象的创建 其他名字对象的创建
十四. 名字对象 概念 IMoniker接口 名字对象的创建 根据显示名创建名字对象 类名字对象的创建 其他名字对象的创建 简单名字对象的绑定过程。 ROT表 文件名字对象的绑定 CoGetInstanceFromFile 函数 复合名字对象概念及其绑定过程 单项和复合名字对象的创建 单项名字对象的绑定过程 复合名字对象的绑定过程

2 1 概念 我们已知创建COM对象的两种方法: 通过CoGetClassObject 得到类厂,通过类厂接口调用CreateInstance.
直接使用CoCreateInstance 或CoCreateInstanceEx. 客户使用以上两种方法在创建COM对象时,必须提供对象的CLSID或ProgID。COM提供了提供了第三种方法,即利用名字对象(moniker 绰号,名字)创建COM对象的方法。名字对象本身也是一个COM对象。名字对象为另一个COM对象提供了的符号化的表示方法,同时也对组件对象的创建过程进行封装。客户程序只需创建相应的名字对象,并使用名字对象的绑定功能得到组件对象。 在这个意义上,名字对象类似于类厂对象,但是它比类厂对象提供了更多的功能. 其关系对比图如下:

3 名字对象与类厂对象的功能对比示意图 IClassFactory (1) 类厂 客户 COM对象 IMoniker (2) moniker

4 之所以在类厂对象之外衍生出名字对象,是因为:
名字对象可以以名字的方式来创建COM对象,有时候比使用CLSID的方式更方便. 在一些复杂的应用中,COM对象形成了逻辑上的上下级关系或者是包容的关系.在每一个级别上都是一个COM对象,下级的对象只有在上级的对象范围内才有意义,而且在确定了上级对象以后,下级对象可以更加简单(且更加明确和直观)地使用名字的方式来描述. 在这种情形下,使用从上到下的名字比使用一串的CLSID更加方便合理地创建COM对象.比如Excel中的文档对象以Excel Application,WorkBook,WorkSheet、Range等层次的COM对象的方式给应用程序员提供访问接口。这些对象的状态存储在复合文档的不同级别的存储对象和流对象中. 比如“c:\My Documents\MyTable.xls! Sheet2 ! R1C1:R2C2”表示存储在文件My Documents\MyTable.xls 的工作表Sheet2的Range R1C1:R2C2对象。 这些对象往往都是永久对象。它们不仅有方法,而且有状态.它们的状态数据以复合文档的形式存储在磁盘上。如果只使用CLSID,根本不能准确地表明是标识为CLSID的COM对象的此实例,而非彼实例.所以, 不仅是更直观,而且是必须以“复合名字”的方式才能准确地创建或还原这个COM对象.

5 2. IMoniker接口 IMoniker接口 派生自IPersistStream接口(p242) 接口成员函数功能上分为四组:
名字对象是COM对象的封装和创建者.它实现了IMoniker接口. interface IMoniker : IPersistStream { HRESULT BindToObject ([in] IBindCtx *pbc, [in, unique] IMoniker *pmkToLeft, [in] REFIID riid, [out, iid_is(riid)] void **ppv); ……// 其他成员未列出 } IMoniker接口 派生自IPersistStream接口(p242) 接口成员函数功能上分为四组: 1。名字管理 2。实现绑定 3。管理复合名字对象。4。名字解析。其中最为重要的是绑定操作函数BindToObject。 所有的名字对象都实现了IMoniker接口,名字对象封装了组件对象的所有状态处理过程,客户使用IMoniker接口成员。按照统一的方法处理名字对象。

6 3 名字对象的创建 3.1 根据显示名创建名字对象 名字对象的名字称为显示名(display name)是一种用户可读的名字.显示名可以是文件路径名,这时返回文件名字对象,也可以是组件对象的CLSID,这时返回类名字对象。也可以是经过扩充的以“!”等间隔开的复合名字,(这时返回复合名字对象)。 IMoniker接口有一个方法GetDisplayName可以返回对象的显示名.然而更重要的是根据显示名来创建名字对象. 1。 WINOLEAPI MkParseDisplayName( LPBC pbc, //绑定环境 LPCOLESTR szUserName, //显示名 ULONG *pchEaten, //绑定过程中解析的字符个数 LPMONIKER FAR *ppmk //结果名字对象的指针 ); MkParseDisplayName根据显示名进行解析的结果生成名字对象,并以IMoniker指针的方式返回给客户。

7 3.2 类名字对象的创建 名字对象有很多种,也有很多别的方法来创建名字对象。 创建一个类名字对象
3.2 类名字对象的创建 名字对象有很多种,也有很多别的方法来创建名字对象。 创建一个类名字对象 WINOLEAPI CreateClassMoniker( REFCLSID rclsid, //该名字对象所命名的对象的CLSID IMoniker **ppmk //结果名字对象的指针 ); 该类名字对象将指向所命名对象的类厂对象.

8 下面是一个客户从类名字对象得到组件对象的例子:
HRESULT GetMyInterface(IMyInterface* *rgpc){ IClassFactory *pcf=0; //声明目标组件对象的CLSID为一个显示名 const OLECHAR pwsz[]= OLESTR(“clsid:E2F41FB8-BE92-4d00-A3DD-D9C285B959C1”); //为绑定和解析名字对象创建一个新的绑定环境 IBindCtx *pbc=0; ULONG cchEaten; IMoniker *pmk=0; HRESULT hr=CreatBindCtx(0,&pbc); //请求COM将显示名转换为名字对象 hr=MkParseDisplayName(pbc,pwsz,&cchEaten,&pmk); if(SUCCEEDED(hr)){//请求名字对象寻找或创建它指向的对象 hr=pmk->BindToObject(pbc,0,IID_IClassFactory,(void**)&pcf); pcf->CreateInstance(0, IID_IMyInterface, &rgpc); //现在我们有了指向所需对象的指针,因此释放名字对象和绑定环境 pmk->Release(); } pbc->Release(); return hr; }

9 3.3 其他名字对象的创建: 创建一个文件名字对象。 WINOLEAPI CreateFileMoniker(
3.3 其他名字对象的创建: 创建一个文件名字对象。 WINOLEAPI CreateFileMoniker( LPCOLESTR lpszPathName, //文件路径 LPMONIKER FAR *ppmk //结果名字对象的指针 ); 创建一个单项名字对象。 WINOLEAPI CreateItemMoniker( LPCOLESTR lpszDelim, //分割符 LPCOLESTR lpszItem, //显示名 LPMONIKER FAR *ppmk//结果名字对象的指针 创建一个复合名字对象。 WINOLEAPI CreateGenericComposite( LPMONIKER pmkFirst, //第一个名字对象 LPMONIKER pmkRest, //第二个名字对象 LPMONIKER FAR *ppmkComposite//结果名字对象的指针

10 4 简单名字对象的绑定过程。 4.1 ROT表 COM对象的激活是由COM服务的SCM(服务控制管理器)来完成的.每台支持COM的机器上都有本地的SCM。它响应客户的加载COM对象的请求。一旦对象被激活,SCM就不再介入到客户和组件对象的方法调用过程。客户以名字对象或底层API的形式访问SCM提供的服务。SCM使用ROT(Running Object Table)来管理正在运行的,已经被注册的名字对象。如果一个名字对象所指的组件对象已经在运行了,在名字对象的绑定过程中,可以直接连接到此对象上,而不必再启动新的对象。 有两种方式可以得到ROT表:

11 使用GetRunningObjectTable来得到ROT表的IRunningObjectTable指针
WINOLEAPI GetRunningObjectTable(DWORD reserved, //保留 LPRUNNINGOBJECTTABLE *pprot ); //IRunningObjectTable 接口指针 名字对象在绑定过程中要使用到绑定环境。绑定环境是COM实现的系统对象。客户可以使用CreateBindCtx函数来创建一个绑定环境对象。 WINOLEAPI CreateBindCtx(DWORD reserved, //保留 LPBC FAR* ppbc ); //IBindCtx 接口指针 客户可以从绑定环境中使用接口函数GetRunningObjectTable得到ROT,同时可以控制绑定过程中的一些行为。 以下以文件名字对象为例研究名字对象的绑定过程.

12 4.2 文件名字对象的绑定 比如客户程序通过文件名字对象来访问一个电子表格文档对象。电子表格文件名为:“c:\My Documents\MyTable.xls”. Excel中的文档对象以Excel Application,WorkBook,WorkSheet、Range等层次的COM对象的方式给应用程序员提供访问接口。这几个对象都是永久对象。它们的状态数据以复合文档的形式存储在磁盘上。 在VBA环境下或VisualBasic环境下,我们可以方便地使用VisualBasic语言访问电子表格文档对象。在C++环境下,可以对这些对象进行更精细的控制。 客户程序调用CreateFileMoniker或MkParseDisplayName函数来创建一个文件名字对象。得到文件名字对象的IMoniker指针。然后调用名字对象的IMoniker::BindToObject函数,一旦函数成功返回,就得到了电子表格文档对象。 BindToObject内部调用了CoGetInstanceFromFile,见下:

13 STDMETHODIMP FileMoniker::BindToObject(IBindCtx. pbc, IMoniker
STDMETHODIMP FileMoniker::BindToObject(IBindCtx *pbc,    IMoniker *pmkToLeft,     REFIID riid, void **ppv) { *ppv = 0; HRESULT hr = E_FAIL; if (pmkToLeft == 0) { // 左边没有名字对象的情形 MULTI_QI mqi = { &riid, 0, 0 };   //用户指定的接口IID COSERVERINFO *pcsi;    DWORD grfMode;    DWORD dwClsCtx; // 这三个参数是 BindCtx的属性    this->MyGetFromBindCtx(pbc, &pcsi, &grfMode, &dwClsCtx);   //得到绑定环境,比如使用COM的CreateBindCtx函数,然后可以得到调用IBindCtx的成员函数GetRunningObjectTable得到ROT。   hr = CoGetInstanceFromFile(pcsi, 0, 0, dwClsCtx, grfMode, this->m_pszFileName,   1, &mqi);     if (SUCCEEDED(hr))      *ppv = mqi.pItf; } //通过MULTI_QI 指针的分量指针返回客户对象的接口指针 else { // 左边有名字对象的情形,复合名字对象。见下文. } return hr; } CoGetInstanceFromFile函数完成了实际的创建COM对象的过程. 如下:

14 4.3 CoGetInstanceFromFile 函数
HRESULT CoGetInstanceFromFile( COSERVERINFO * pServerInfo, //指向远程主机 CLSID* pclsid, //要创建的对象的CLSID IUnknown * punkOuter,//用于被聚合的情形,指向外部的 IUnknown DWORD dwClsCtx, //环境变量 DWORD grfMode, //打开模式 OLECHAR* szName, //对象文件名 ULONG cmq, //MULTI_QI 数组的大小,即接口的个数. MULTI_QI * rgmqResults //MULTI_QI 数组 ); 其中: typedef struct _MULTI_QI { const IID* pIID; //客户指定的要返回的目标对象的接口IID IUnknown * pItf; // 用来保存接口的指针 HRESULT hr; } MULTI_QI; 此结构可以保存多个接口指针。

15 CoGetInstanceFromFile函数的过程:
1。首先要获得对象的CLSID,客户有可能直接在参数中提供,如果没有提供, CoGetInstanceFromFile调用GetClassFile函数以获得与文件相关的CLSID HRESULT GetClassFile([in, string] OLECHAR *pwszFileName,                    [out] CLSID *pclsid); 如果文件是复合文档,那么该复合文档的根存储对象的IPersist接口的GetClassID成员函数将返回CLSID,如果不是复合文档那么通过注册表查找文件的扩展名与ProgID的Association关系,然后再查找ProgID与CLSID的对应关系取得文件相关的CLSID 2。 得到了对象的CLSID后,由于客户在调用CoGetInstanceFromFile之前已经得到了绑定环境。 CoGetInstanceFromFile可以利用此绑定环境得到ROT(Running Object Table)。ROT表中包含这样的入口项(pmkObjectName, pUnkObject)其中pmkObjectName指向关联目标对象的名字对象的指针, pUnkObject指向运行的对象的指针。那些允许名字对象关联的对象在运行时会在ROT表中注册,即加入入口项。名字对象如果检测到此入口项时,就可以使用ROT表的GetObject函数直接返回目标对象的接口指针(IUnkown指针)。如果没有检测到,那么:

16 3. 如果在ROT表中没有找到对应的入口项,那么CoGetInstanceFromFile使用CoCreateInstance或CoGetClassObject再由类厂接口的CreateInstance创建新对象,并返回对象的IUnkown接口指针。 4。从IUnkown接口查询对象的IPersistFile接口。(既然复合文档中把它的CLSID记下来了,说明此对象肯定是永久对象,并实现了IPersistFile接口)。从IPersistFile接口调用Load方法,并把文件名传入作为参数,Load从文件读取数据对对象的状态进行初始化,并且在ROT表中进行注册。 Load方法如下:

17 STDMETHODIMP MyWorkBook::Load(const OLECHAR
STDMETHODIMP MyWorkBook::Load(const OLECHAR *pszFileName,  DWORD grfMode) { //从文件中读入对象状态 hr = this->MyReadStateFromFile(pszFile, grfMode); if (FAILED(hr)) return hr; IRunningObjectTable *prot = 0; hr = GetRunningObjectTable(0, &prot); // 从SCM中获取ROT表 if (SUCCEEDED(hr)) { // 创建一个文件名字对象并在ROT中注册     IMoniker *pmk = 0;    hr = CreateFileMoniker(pszFileName, &pmk);   //创建文件名字对象 if (SUCCEEDED(hr)) { // register self in ROT      hr = prot->Register(0, this, pmk, &m_dwReg);    //注册   pmk->Release();     }    prot->Release(); } return hr; } 以后再用同样的文件名调用CoGetInstanceFromFile函数将不会创建新的对象,而是返回指向这个对象的引用。使用文件名字对象达到了两个目的: 允许对象把自身注册到ROT中,使得以后的CoGetInstanceFromFile能找到它。 把CoGetInstanceFromFile隐藏到IMoniker接口后面了。客户只是调用名字对象,并没有调用CoGetInstanceFromFile。见BindToObject的实现代码。

18 5 复合名字对象概念及其绑定过程 5.1 概念 对于简单名字对象而言,名字对象仅仅是利用字符串间接创建COM对象的一种手段。而且客户通过名字对象引用组件程序的内部对象也非常不方便(比如我们通过以上办法得到了Excel Application对象,但是如果我们要访问其中的一个单元格,以上步骤是不够的。)复合名字对象将真正体现名字对象的优越性。 复合名字对象按从左到右的顺序保存各成员名字对象。比如“c:\My Documents\MyTable.xls! Sheet2 ! R1C1:R2C2”表示一个复合名字对象,它表示存储在文件My Documents\MyTable.xls 的工作表Sheet2的Range R1C1:R2C2对象。 从根本上说,复合名字对象是为了描述现实中存在的一些对象模型,比如Office中的对象模型,这些对象模型中存在着上下级的包容关系。比如Excel 的Application对象是WorkBook对象的上级,WorkBook对象是WorkSheet对象的上级,WorkSheet对象是Range对象的上级。这些都是COM对象,而且都是永久对象,自动化对象。这些对象我们称为单项名字对象. 每个对象只有在它的上级对象(或者称为包容器对象)的环境下才有意义。所以这些对象天生就是“复合”的。而且它们的名字只有在上级对象的命名空间下才有意义。而且,上级对象应该能控制下级对象,比如解析它的名字,创建子对象等等。

19 复合名字对象由一组名字对象组成,也可以包括其他的复合名字对象。
创建一个复合名字对象。 WINOLEAPI CreateGenericComposite( LPMONIKER pmkFirst, //第一个名字对象 LPMONIKER pmkRest, //第二个名字对象 LPMONIKER FAR *ppmkComposite//结果名字对象的指针 ); 从此函数的结构可知复合名字对象是如何构成的。

20 5.2 单项和复合名字对象的创建 创建一个单项名字对象。 WINOLEAPI CreateItemMoniker(
5.2 单项和复合名字对象的创建 创建一个单项名字对象。 WINOLEAPI CreateItemMoniker( LPCOLESTR lpszDelim, //分割符 LPCOLESTR lpszItem, //显示名 LPMONIKER FAR *ppmk//结果名字对象的指针 ); 以下代码可以创建一个复合名字对象“File1!Item1!Item2”: CreateFileMoniker(“File1”,&pmkFile); CreateItemMoniker(“!”,”Item1”,&pmkItem1); pmkFile->ComposeWith(pmkItem1,FALSE,&pmkComp1) //或者 CreateGenericComposite(pmkFile,pmkItem1,&pmkComp1) CreateItemMoniker(“!”,”Item2”,&pmkItem2); pmkComp1->ComposeWith(pmkItem2,FALSE,&pmkComp2) //或者 CreateGenericComposite(pmkComp1,pmkItem2,&pmkComp2)

21 5.3 单项名字对象的绑定过程 单项名字对象所命名的对象的上级对象(或容器对象)必须实现IOleItemContainer接口.以把容器对象和下级对象联系起来, 接口的定义如下: // from oleidl.idl [ object,uuid( c C ) ] interface IOleItemContainer : IOleContainer { // ask for object named by pszItem HRESULT GetObject(         [in] LPOLESTR pszItem, // 对象的显示名     [in] DWORD dwSpeedNeeded, // 时限   [in, unique] IBindCtx *pbc,// 绑定环境     [in] REFIID riid, // 对象的接口       [out, iid_is(riid)] void **ppv); // 返回的接口指针 ……// 其他方法 } 此接口类似于类厂接口, GetObject则类似于类厂接口的CreateInstance函数. 容器对象利用此接口来创建下级对象. 单项名字对象的绑定过程如下:

22 STDMETHODIMP ItemMoniker::BindToObject(IMoniker. pmkToLeft, IBindCtx
STDMETHODIMP ItemMoniker::BindToObject(IMoniker *pmkToLeft, IBindCtx *pbc,   REFIID riid, void **ppv) {*ppv = 0; if (pmkToLeft == 0) return E_INVALIDARG; // 需要一个作用域    // 首先绑定左边的。 IOleItemContainer *poic = 0; HRESULT hr = pmkToLeft->BindToObject(0 ,pbc,   IID_IOleItemContainer, (void**)&poic); //返回左边名字对象所指的对象的IOleItemContainer接口。如果左边也是一个单项名字对象,则会继续递归调用.直到能够单独绑定为止. if (SUCCEEDED(hr)) { pbc->RegisterObjectBound(poic); //在绑定环境中缓存已绑定对象。   DWORD dwBindSpeed = this->MyGetSpeedFromCtx(pbc); //绑定过程比较耗时,所以要设定时间参数。 hr = poic->GetObject(m_pszItem, //即字符串“Sheet2” dwBindSpeed, //实际限制 pbc, //绑定环境 riid, //客户指定的目标对象的接口IID,比如说IID_IWorkSheet ppv);  //返回指针   poic->Release(); } } 注意poic是左边的名字对象所指的对象的IOleItemContainer接口指针。它当然应该知道如何创建自己的下级对象。并返回接口指针。

23 5.4 复合名字对象的绑定过程 IBindCtx *pbc=0; ULONG cchEaten; IMoniker *pmk=0;
在简单文件名字的对象的绑定过程中演示了“c:\My Documents\MyTable.xls”的绑定过程. 文件“c:\My Documents\MyTable.xls”代表了一个WorkBook对象。而这个WorkBook的第二张工作表Sheet2是它的一个下级对象。Sheet2对象的状态数据存储在复合文档c:\My Documents\MyTable.xls的某一个子存储对象或流对象中。如果我们要在程序中访问这个对象,那么可以使用“c:\My Documents\MyTable.xls!Sheet2”这个名字。以下将演示如何绑定这个复合名字对象. 首先仍然要使用CreateFileMoniker或MkParseDisplayName创建文件名字对象。 OLECHAE pwsz[]=OLESTR(“c:\My Documents\MyTable.xls!Sheet2”) IBindCtx *pbc=0; ULONG cchEaten; IMoniker *pmk=0; HRESULT hr=CreatBindCtx(0,&pbc); hr=MkParseDisplayName(pbc,pwsz,&cchEaten,&pmk); 得到名字对象以后进行绑定工作: IMoniker::BindToObject();

24 绑定工作分为以下几步: 检查ROT表,如果找到与它相等的名字对象,则调用此名字对象所指对象的IUnknown接口的QueryInterface函数返回接口指针即可.避免启动同一个实例.如果没有找到,则下一步: 把名字对象分成两个部分.pmkLeft和pmkRight.pmkRight是一个简单的名字对象,左边可能是一个复合的或简单的名字对象. pmkRight->BindToObject(pmkLeft……). 在以上函数中,将调用pmkLeft->BindToObject.如果pmkLeft是一个简单名字对象,则按照上节的途径进行绑定工作,如果pmkLeft本身是一个复合名字对象,则重新回到步骤1. 绑定过程形成了递归循环. 复合名字对象的成员个数有限,所以循环一定会终止.以下条件会终止循环: 在ROT表中找到了要绑定的对象. pmkLeft是简单名字对象 pmkRight不需要左边的对象进行绑定支持. 绑定过程完成以后,最左边的对象(最上层的)使用简单名字对象到达方式创建对象(CoCreateInstance见前节). 然后上层容器对象的IOleItemContainer接口 的GetObject函数完成下层对象的创建过程.

25 前缀 绑定方向 名字对象 后缀 构造方向 绑定和构造的方向

26 File!Item1!Item2的绑定和构造过程
pmkItem2->BindToObject( , pmkFileItem1, IID_IUnknown, ppvObj) 客户程序调用 pComp->BindToObject( , IID_IUnknown, &pUnk) 复合名字对象被拆分成 pmkFileItem1 pmlItem2 两部分 pmk File Item 1 ->BindToObject( , NULL , IID_I OleItemContainer &p Item1Obj) 被拆分成 pmkFile pmlItem1 pmkItem , pmkFile, IID_ I & p &pFileObj ) 创建文件对象并返回其 IOleItemContainer 接口指针 pFileObj 指向文件对象的 - >GetObject(..., IID_I Item1Obj p 指向文件对象中 Item1 对象的 Item1Obj - IID_IUnknown, &pUnk) pUnk Item2 I Unknown File!Item1!Item2的绑定和构造过程


Download ppt "十四. 名字对象 概念 IMoniker接口 名字对象的创建 根据显示名创建名字对象 类名字对象的创建 其他名字对象的创建"

Similar presentations


Ads by Google