COM in Wine(2)——基本代码分析

提前声明!!!!!
声明:本文以及后续相关博客只是总结,大部分内容都是翻译MSDN、Wine官网、《Inside OLE2》以及《COM原理与应用》中的内容,也参考了很多网络上的博客和论文,但时间久远,无法一一列出(鞠躬)
ps:在上一篇博客中对COM的一些基本概念进行了介绍,本篇博客是在上一篇的基础上,对其涉及的简单过程进行的很基本很基本的代码分析(很久之前写的),一开始是为了简单理解相关内容(可能有不对的地方),更具体的代码调用分析(也许)会在后续博客中给出

COM库与类厂的交互

以dlls/ole32/tests/marshal.c中的test_cocreateinstance_proxy为例:
在这里插入图片描述
它的函数调用关系图大致如下:
在这里插入图片描述
  CoGetClassObject、CoCreateInstance和CoCreateInstanceEx是COM库中用于对象创建的三个API函数。
  CoGetClassObject 函数先找到由clsid指定的COM类的类厂,然后连接到类厂对象,如果需要的话,CoGetClassObject 函数装人组件代码。如果 COM 对象是进程内组件对象的话,CoGetClassObject调用DLL模块的DllGelClassObject引出函数,把参数clsid、iid和ppv传进去,由DllGelClassObject创建类厂,并返回类厂对象接口指针。
  CoCreateInstanceEx获得类厂对象接口指针之后,调用类厂的对象创建函数,创建COM对象。代码如下:
在这里插入图片描述
  类厂把COM对象返回给CoCreateInstance(CoCreateInstanceEx)之后,CoCreateInstance函数返回,客户直接调用COM对象。如上图的test_cocreateinstance_proxy代码。

列集和散集过程

列集过程

列集过程可通过两种方式实现:自定义列集法和标准列集法。自定义列集法以整个对象唯列集单位,而标准列集法以接口为列集单位。从程序实现上讲,除非有一些特殊目的,否则一般采用标准列集法。

以dlls/ole32/tests/marshal.c中的test_normal_marshal_and_unmarshal为例,列集过程如下:
在这里插入图片描述
(1)函数CoMarshalInterface首先向要列集的对象请求IMarshal接口指针,如果对象实现了IMarshal接口,则使用自定义列集方法;调用GetUnmarshalClass函数获取代理对象的CLSID。如果没有实现,使用调用函数CoGetStandardMarshal来使用标准列集方法,也就是COM提供的缺省代理对象;其CLSID为CLSID_StdMarshal。

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

STDAPI CoMarshalInterface(
IStream * pStm,       //接收列集产生结果的流,最终这个流会进行传递
REFIID riid,          //预列集的接口IID
IUnknown * pUnk,    //预列集的对象的指针(riid必须是pUnk所支持的接口)
DWORD dwDestContext, //列集包括的信息,也就是决定列集的数据可以被谁来散集
void * pvDestContext,  //为NULL
DWORD mshlflags  //指定列集的属性, 常规,强表格,弱表格,不执行PIN
);

函数内部逻辑如下:
当调用这个函数时pUnk首先查询是否支持riid接口,若不支持则返回错误。
查询pUnk对象是否支持IMarshal接口,若支持则表示对象将使用自定义列集,而若不支持则表示使用标准列集器。
标准列集器的建立是通过调用CoGetStandarMarshal来完成的。函数的定义如下:

STDAPI CoGetStandardMarshal(
REFIID riid,                            //需要列集的接口IID
IUnknown * pUnk,               //预列集的对象的指针
DWORD dwDestContext,
LPVOID pvDestContext,
DWORD mshlflags,
LPMARSHAL * ppMarshal    //[OUT] 输出一个IMarshal接口指针(对象的存根管理器对象实现的接口!!!));
);

(2)调用GetMarshalSizeMax函数确定列集数据包大小的最大可能值,并分配一定的空间。
(3)调用MarshalInterface函数建立列集数据包。具体如下:
在这里插入图片描述

  1. marshal_object函数主要负责把一个object通过marshal转为一个stdobjref的数据结构。首先调用get_stub_manager_from_object函数,获得与对象相关的存根管理器。

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

  2. get_facbuf_for_iid函数是获取一个代理/存根类厂接口IPSFactoryBuffer,首先调用CoGetPSClsid找到对指定接口实现了IPSFactoryBuffer的代理/存根类厂的CLSID,然后根据CLSID调用CoGetClassObject函数来创建一个代理/存根类厂接口IPSFactoryBuffer的对象。

  3. 调用IPSFactoryBuffer的CreateStub方法,创建一个接口存根对象。当建立一个接口存根后得到的是一个IRpcStubBuffer接口指针,所有的接口存根必须实现这个指针。

    每个存根管理器将维护着一个接口存根列表。当找到存根管理器后,存根管理器再根据riid查找其所维护的接口存根对象。所谓接口存根对象也是一个COM对象。因此每个接口存根需要跟这个接口的指针进行关联。当存根管理器没有查到riid对应的接口存根时, COM将会根据riid这个接口信息在注册表的HKEY_CLASSES_ROOT/Interface下查找子键riid的ProxyStubClsid32子健下的默认值,这个默认值是一个对象的CLSID。然后COM根据CLSID调用CoGetClassObject函数请求代理类厂接口IPSFactoryBuffer,并调用接口的CreateStub建立一个接口存根对象。IPSFactoryBuffer的定义如下:

    interface IPSFactoryBuffer : IUnknown
    {
    HRESULT CreateProxy    //建立一个接口代理对象
    (
       [in] IUnknown *pUnkOuter,
       [in] REFIID riid,
      [out] IRpcProxyBuffer **ppProxy,
      [out] void **ppv
    );
    
     HRESULT CreateStub   //建立一个接口存根对象
     (
     [in] REFIID riid,               //欲建立接口存根的IID
       [in, unique] IUnknown *pUnkServer,  //指定外部对象,这个参数就是外部函数传递的pUnk
      [out] IRpcStubBuffer **ppStub     //接口存根必须实现的接口
      );
    }
    
  4. 调用stub_manager_new_ifstub方法,向存根管理器注册一个新的接口存根COM对象,并返回注册记录。这个过程中会为这个接口存根分配一个唯一标识符叫IPID。并将接口存根接口和IPID保存到由对象存根管理器所维护的接口存根列表中去。

    经过一系列操作后marshal_object方法返回得到一个stdobjref的对象(在整个marshal_object过程中,stdobjref对象信息不断完善),MarshalInterface调用IStream_Write方法把该对象写入IStream中,然后CoMarshalInterface终于返回了,这样就可以通过IStream中的内容转化为数据流返回给客户端了。

散集过程

散集过程如下:
在这里插入图片描述
(1)CoUnmarshalInterface函数首先调用get_unmarshaler_from_stream,根据列集信息判断列集方式,如果是标准列集方式,就使用COM提供的缺省的代理对象,如果是自定义列集方式,就根据列集信息中代理对象的CLSID值调用CoCreateInstance函数来创建代理对象,并得到IMarshal接口指针。

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

STDAPI CoUnmarshalInterface(
IStream * pStm,
REFIID riid,  //[IN], 需要散集的接口IID
void ** ppv   //[OUT], 散集的结果
);

(2)如果是标准列集方式,就把列集数据包传给函数std_unmarshal_interface;如果是自定义列集方式,就把列集数据包传给IMarshal::UnmarshalInterface函数,代理对象读入数据包中的连接信息建立它与对象之间的连接,并把IMarshal:: UnmarshalInterface函数调用得到的接口指针返回给客户。具体如下:
在这里插入图片描述

  1. std_unmarshal_interface方法首先通过方法apartment_get_current_or_mta来获得当前所在的apartment,并且通过IStream_Read函数从IStream流中获得stdobjref结构的对象obj,该对象就是正在散集的对象。
  2. 调用get_stub_manager方法获得与obj相关的存根管理器。如果当前的apartment就是obj列集的apartment,那么直接通过存根管理器获得原始对象并返回原始对象。如果不是的话,就调用unmarshal_object方法。
  3. 在unmarshall_object方法中,首先要获得与obj相关的代理管理器,如果当前apartment中没有的话就调用proxy_manager_construct方法创建一个代理管理器。通过代理管理器获得与需要散集的接口相关的接口代理ifproxy,如果没有的话,就调用方法proxy_manager_create_ifproxy创建一个。
  4. 在proxy_manager_create_ifproxy方法中,调用get_facbuf_for_iid函数来获取一个代理/存根类厂接口IPSFactoryBuffer,首先调用CoGetPSClsid找到对指定接口实现了IPSFactoryBuffer的代理/存根类厂的CLSID,然后根据CLSID调用CoGetClassObject函数来创建一个代理/存根类厂接口IPSFactoryBuffer的对象。
  5. 调用IPSFactoryBuffer的CreateProxy创建一个接口代理对象。通过接口代理对象即可获得散集结果。
    HRESULT CreateProxy    //建立一个接口代理对象
        (
            [in] IUnknown *pUnkOuter,   //代理对象指针,因为代理对象聚合了接口代理
            [in] REFIID riid,
            [out] IRpcProxyBuffer **ppProxy,
            [out] void **ppv   //得到的接口代理指针,这个指针将最终返回给客户端使用
        );
    

接口代理和接口存根(标准列集)

  把针对特定接口的列集代码和散集代码分别称为“接口代理”和“接口存根”。客户代理中代理对象中有一部分代码专门管理接口代理,成为代理管理器,负责接口代理的装卸。接口代理本身也是COM对象,代理对象用聚合的方式把所有接口代理组成一个大的整体,形成一个大的COM对象,它实现了所有组件对象支持的接口。

  每个接口代理不仅实现了它所代理的接口,同时也实现了接口IRpcProxyBuffer接口,以便使接口代理与RPC通道连接起来。
  组件进程中,与代理对象对应的事存根代码,也常称为存根对象,但实际上它只是进程中的一组代码,包括存根管理器和接口存根。COM要求同一个接口的接口代理和接口存根必须在同一个COM对象上实现,称为接口代理/存根对象。虽然在同一COM对象上实现,但接口代理和接口存根在使用上事完全分开的,位于不同的进程。

类型库

Typelib的生成

(1)在idl文件中加入library项来描述相应的组件
library表达式描述一个类型库,这个描述包括一个MIDL输入文件包含的所有信息:

[attributes]library libname{
	definitions
};

以dlls/oleaut32/tests/tmarshal.idl为例看library表达式:
在这里插入图片描述

uuid、version、helpstring为attributes(其中只有uuid是必须的),TestTypelib为类型库的名字。

而definitions是输入库、数据类型、模块、接口、调度接口和COM类等相关信息的描述:
在这里插入图片描述
(2)用MIDL编译得到相应的tlb文件dlls/oleaut32/tests/tmarshal.tlb
在这里插入图片描述
(3)通过resource script 文件将类型库嵌入组件
在这里插入图片描述

Typelib的加载

Typelib的加载是通过函数LoadTypeLib实现的,有两个参数,szFile是要加载的文件名,为输入参数,pptLib是返回的一个指向ITypeLib对象的指针,为输出参数:

HRESULT WINAPI LoadTypeLib(const OLECHAR *szFile, ITypeLib * *pptLib)

该函数的调用过程大致如下:
在这里插入图片描述
函数TLB_ReadTypeLib的功能是查找typelib文件的类型并将typelib资源映射到内存中。

TLB_ReadTypeLib首先调用CreateFileW函数,CreateFileW做了一些准备工作之后调用NtCreateFile函数,如果文件存在就打开文件,如果不存在就创建文件。

之后,TLB_ReadTypeLib在typelib cache中找这个文件路径,如果找到就直接addref,并返回ITypeLib2指针。否则就真正的加载并解析typelib,步骤如下:

先根据文件类型调用函数TLB_PEFile_Open或者TLB_NEFile_Open或者TLB_Mapping_Open来打开文件资源,之后根据typelib的类型调用函数ITypeLib2_Constructor_MSFT或者ITypeLib2_Constructor_SLTG从内存映像中加载对应类型的typelib。

(TLB_ReadTypeLib返回之后,LoadTypeLibEx会判断regkind,如果需要注册,则调用RegisterTypeLib。)

Typelib相关接口

接口说明
ITypeLib类型库包含一个或多个对象的描述,并且可以通过ITypeLib接口进行访问。单个对象的描述可通过ITypeInfo接口访问。
ITypeInfo通常用于读取有关对象的信息的接口。可以使用ITypeInfo从类型库中提取有关对象的特征和功能的信息。
ITypeComp用于绑定到类型的变量和方法,或绑定到类型库中的全局变量和方法。也可绑定到包含在类型库中的类型说明。
ICreateTypeLib用于创建一个类型库。创建完是一个空的类型库,调用方法可以设置类型库的属性以及为类型库创建元素。
贝叶斯分类算法是一种基于贝叶斯定理的统计算法,常用于文本分类、垃圾邮件过滤和数据挖掘等任务中。在对wine数据集进行分类时,我们可以使用贝叶斯分类算法。 首先,我们需要了解wine数据集的特征和标签。根据数据集的描述,wine数据集包含了一些葡萄酒的化学分析结果作为特征,以及该葡萄酒所属的类别作为标签。这些特征可以包括酒精含量、苹果酸含量、灰分含量等。 贝叶斯分类算法的核心思想是基于训练集计算每个类别的先验概率和条件概率,然后使用贝叶斯定理来计算给定特征时,每个类别的后验概率,最终选择后验概率最大的类别作为预测结果。 为了使用贝叶斯分类算法对wine数据集进行分类,我们需要进行以下步骤: 1. 数据预处理:对原始数据进行清洗和处理,包括去除缺失值、标准化特征等。 2. 特征选择:根据具体问题的要求,选择合适的特征来训练模型,可以使用相关性分析等方法进行特征选择。 3. 训练模型:将数据集分成训练集和测试集,使用训练集来计算每个类别的先验概率和条件概率。 4. 预测分类:对测试集中的每个样本,根据贝叶斯定理计算该样本属于每个类别的后验概率,选择后验概率最大的类别作为预测结果。 5. 模型评估:使用测试集评估模型的性能,可以使用准确率、精确率、召回率等指标来评估模型的好坏。 贝叶斯分类算法的优点是简单、直观,能够处理多分类问题和高维数据。然而,贝叶斯分类算法也有一些限制,例如对特征之间的关联性要求较高,对输入的先验概率分布有一定假设等。 在应用贝叶斯分类算法对wine数据集进行分类时,我们需要根据具体情况选择适合的特征和合适的先验分布,对模型进行调优,以获得更好的分类结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值