DCOM列集散集的内部具体实现的研究初探

转至网络!
 
我也不罗嗦什么理论基础,相信看这篇文章的人肯定对COM有一定的功底了。下面的介绍只是我对COM实现的自己的理解因此肯定有错误的地方,目的是希望大家指出和讨论,以求共同进步。

1.套间的注册

无论是在服务器端还是在客户端,首先执行的总是CoInitialize来建立一个STA或者MTA套间。每个套间由一个8字节的数字唯一标识称为OXID, 当调用CoInitialize(Ex)时系统将会把套间的信息注册到SCM中,因此每一台机器上的SCM都管理着本机所有注册的套间。那么注册到SCM中的每个套间还应该包括什么信息呢? 据我考虑注册到SCM中的套间信息应该包括如下结构:

而当系统调用CoUninitialize时则会将套间的注册信息从SCM中删除掉。

为什么要注册这些信息后面将会讨论到。


2.EXE服务器端类厂对象的注册


因为EXE不能提供DllGetObject导出函数,所以必须提供一个方法告诉SCM以便SCM能够找到类厂对象。因此EXE服务器需要调用CoRegisterClassObject来将类厂信息保存到SCM。这个函数格式如下:


而当EXE服务器退出前则需要调用CoRevokeClassObject来将类厂信息从SCM中取消



从上面的注册代码中可以看出,每个类厂对象注册时保存到SCM中的有: 对象CLSID, 类厂接口指针, 上下文, 属性等,但实际还会保存更多的东西比如类厂所输的套间的OXID(也有可能在SCM中的每个OXID上维护着类厂列表,这样每次注册时将根据类厂所属的OXID来查找SCM中的OXID列表并将对应的类厂信息保存到特定的OXID标识的套间信息中去)

1,2注册的信息都为以后服务有用。


3.客户调用CoGetClassObject得到进程外服务器类厂接口的过程.


当客户通过指定对象的CLSID, 接口IID,对象的生存环境,以及对象所在的服务器名调用CoGetClassObject时, COM将这个调用发向客户端的SCM。 客户端的SCM将根据指定的计算机服务器名跟目标机器上的SCM通信,并传递要求的建立的CLSID,IID等信息(因为指定的计算机名,所以很容易可以跟目标计算机上的SCM通信,具体是如何通信的可以不必研究,但我认为因该是RPC)。而目标服务器机器上的SCM收到请求后查询所有注册的类厂信息(第2步会将类厂的CLSID注册到SCM中)。若没有找到则根据CLSID查注册表并启动组件,而当找到注册的类厂信息后,向类厂注册的套间发送RPC请求以便得到请求接口的列集数据包。然后SCM将得到的数据包返回给客户的SCM,SCM再将数据返回给调用CoGetClassObject的客户(也许客户在调用CoGetClassObject时并不需要本地SCM的介入!!而是直接跟目标计算机上的SCM通信)。客户端根据得到的列集数据包调用CoUnmarsalInterface进行散集,并建立代理对象,并将代理对象的接口返回给客户端。这样就完成了客户到服务器上的第一个连接的建立过程(调用CCoUnmarshalInterface是在CoGetClassObject内部完成的)。

现在的问题是: SCM是如何将请求发送给类厂所在的套间的,以及发送请求时带了些什么参数?
回答(猜测): 若是类厂在STA则根据类厂所在的套间,在SCM中找到套间的类厂接口指针,以及套间的窗口,和线程,和其他需要的信息,然后发送一个窗口消息给套间(记得第1步注册的那些信息吗),STA套间的隐藏窗口在消息处理函数中调用CoMarshalInterface进行数据的列集.并将得到的列集数据返回给SCM;而对于MTA中则是直接发出一个RPC调用,让类厂所在服务器上执行这个调用,而这个调用的实现也是调用CoMarshalInterface进行数据的列集。

现在的问题是集中到了CoMarshalInterface和CoUnmarshalInterface这两个函数了,这两个函数到底做了什么?他们又是如何建立代理对象和存根管理器的等等。


4.CoMarshalInterface的实现逻辑


CoMarshalInterface的调用总是在对象所在的套间之中, 而且这个函数还负责实现存根管理器的建立,接口存根的建立等等。函数的定义如下:

这个函数的内部实现逻辑大概如下:

(1).当调用这个函数时pUnk首先查询是否支持riid接口,若不支持则返回错误.
(2).查询pUnk对象是否支持IMarshal接口,若支持则表示对象将使用自定义列集,而若不支持则表示使用标准列集器
(3).标准列集器的建立是通过调用CoGetStandarMarshal来完成的,这个函数内部将同时会建立对象的存根管理器对象, 接口存根对象等。函数的定义如下:

那么这个函数是如何来实现的呢?

(3.1). COM运行库维护着套间(或者是本进程)所建立的所有存根管理器,每个存根管理器都维护着对象的一个引用。当函数调用时根据传递进来的pUnk查找是否有存根管理器与此对象有关联(通过查询维护的所有存根管理器来实现,因为每个存根管理器维护着对象的引用,因此这步很容易实现),若无则建COM建立一个存根管理器,并为存根管理器分配一个8字节的标识符称为OID来唯一标识这个存根管理器,同时存根管理器将保存这个对象的一个引用。COM建立的这个存根管理器将实现IMarshal接口。


(3.2). 每个存根管理器将维护着一个接口存根列表。当找到存根管理器后,存根管理器再根据riid查找其所维护的接口存根对象。所谓接口存根对象也是一个COM对象,这个对象负责将接口函数的RPC调用数据包进行散集并构件出物理栈(这些都有原代码可以看到的,在IDL生成的文件中),然后再调用真实的接口函数。因此每个接口存根需要跟这个接口的指针进行关联。当存根管理器没有查到riid对应的接口存根时, COM将会根据riid这个接口信息在注册表的HKEY_CLASSES_ROOT/Interface下查找子键riid的ProxyStubClsid32子健下的默认值,这个默认值是一个对象的CLSID。然后COM根据CLSID调用CoGetClassObject函数请求代理类厂接口IPSFactoryBuffer,并调用接口的CreateStub建立一个接口存根对象。IPSFactoryBuffer的定义如下:


当COM建立起接口存根后,会为这个接口存根分配一个16位的唯一标识符叫IPID。并将接口存根接口和IPID保存到由对象存根管理器所维护的接口存根列表中去
(现在不确定的是一个接口的接口存根是唯一建立一次还是每一个存根管理器都将建立不同的接口存根,我更偏向于前者).


(3.3).COM将查询对象pUnk是否实现了IExternalConnection接口,若实现了则表明由对象来控制存根管理器的生存周期,而若没有实现则由系统来管理存根管理器的生存周期.

(3.4).存根管理器也建立起来了,接口存根也建立起来的,CoGetStandardMarshal函数将完成,这样就将得到的存根管理器IMarshal接口指针返回给CoMarshalInterface函数。


(4).当CoMarshalInterface得到了一个IMarshal接口指针后,他将会调用IMarshal的接口成员函数,先看看IMarshal的定义:



具我观察,当调用CoMarshalInterface时只会调用:
(4.1)GetUnmarshalClass函数得到在客户端建立的代理对象的CLSID值,并写入到流中
(4.2)调用MarshalInterface列集数据。那么列集出来的数据结构是怎么样的呢?。COM公开了结构定义如下:



若是一个自定义接口的MEOW则格式如下:



(4.3)因为一个存根管理器中实现的IMarshal接口,所以很容易得到这些信息.
(4.4)因为mshlflags中指定了列集的属性,包括一次列集, 强表格, 弱表格.所以将会增加存根管理器的外部引用。(具体参考列集的实现)

(5).经过一系列操作后CoMarshalInterface终于返回了,这样SCM就可以通过IStream中的内容转化为数据流返回给客户端了.

5.CoUnmarshalInterface的实现逻辑

因为CoGetClassObject会在内部调用CoUnmarshalInterface.
CoUnmarshalInterface总在客户所在的套间中调用,而且这个函数还负责实现代理对象的建立,接口代理的建立等。这个函数定义如下:



(1). COM将流信息转化为一个列集数据包结构(前面已经定义),然后判断Flag表示看是使用什么类型的列集,若是自定义的列集,则根据根据CLSID在注册表中查找代理对象并
建立代理对象,然后查询代理对象的IMarshal接口,并将流对象和riid,ppv传递给IMarshal的UnmarshalInterface函数继续执行散集.因为代理对象实现了
IMarshal所以当然知道该如何散集了.

(2).若是一个标准列集则处理不同, 每个客户的套间上都维护着很多的代理对象列表, 而代理对象通过(OXID,OID)来唯一标识一个代理对象。这样当列集数据包到来时则搜索匹配的(OXID, OID)所定义的代理对象,若是找到了则对这个代理对象查询riid指定的接口,若是找到这个接口了,则直接返回给客户端。而若是没有找到代理对象呢.因为列集数据包中指定是用标准列集的方法.因此客户端还是调用CoGetStandarMarshal来建立标准的代理对象.标准的代理对象也必须实现IMarshal接口, 调用CoGetStandarMarshal的格式如下:


经过这样代理对象就建立起来了.当建立好了代理对象后COM库会将代理对象以及对应的OXID,OID保存起来以便以后使用.

不管如何我们最终都是要得到代理对象的IMarshal接口.

(3).当得到代理对象的IMarshal接口后,就调用接口成员函数:UnmarshalInterface.以便散集出接口代理指针.那么UnmarshalInterface内部是如何实现的呢.先看这个函数定义:
这个函数内部同样在注册表:HKEY_CLASSES_ROOT/Interface中的信息(同建立接口存根一样)。通过得到的CLSID建立接口代理类厂得到IPSFactoryBuffer接口指针,然后IPSFactoryBuffer调用CreateProxy建立一个接口代理,这个函数调用如下:


经过调用这个函数返回给UnmarshalInterface,而UnmarshalInterface又返回给CoUnmarshalInterface,而CoUnmarshalInterface又最终返回给客户端.

着就是列集和散集的全过程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值