函数接口Inside COM读书笔记调度接口与自动化

摘要:
自动化控制程序将把DISPID传递给Invoke成员函数。以下是调度接口的示意图:左侧是传统的COM接口IDispatch,它以vtbl的方式实现,右侧是调度接口。Invoke识别的DISPID对调度接口至关重要。同时,还可以使用COM接口来实现IDispatch::Invoke:dual接口。上图不是使用COM组件实现调度接口的唯一方法。

新手发帖,很多方面都是刚入门,有错误的地方请大家见谅,欢迎批评指正

    

1.  一种新的通信方式

    

    IDispatch为客户和组件提供了另外一种通信方式,有了IDispatch后,COM组件可以通过一个标准的接口提供它所支撑的服务,而无需提供多个特定与服务的接口。

    

1.1旧的通信方式

    

    客户和组件之间的通信是通过接口实现的,接口拥有一个有函数指针形成的数组。客户代码须要包括一个以抽象基类形式描述接口的头文件。编译器将读取此头文件,然后为抽象基类中的每一个成员函数分配一个索引。此索引实际上就是响应函数的指针在函数指针数组中的索引。

    pIX->FxStringOut(msg);

    将解释成:

    (*(pIX->pvtbl[indexOfFxStringOut]))(pIX,msg);

    其中pvtbl为指向响应类的vtbl的指针,而IndexOfFxStringOut则为FxStringOut的指针在函数指针表中的索引。

    在开发宏语言是,宏语言如可能获得到函数在vtbl中的指针的索引以便可以调用它们呢?宏语言调用COM组件中的一个函数时,可以应用三种类型的信息:实现被调用函数的组件ProID、函数名称以及传给函数的参数。我们须要在宏运行时系统可以提供一种通过函数的名称老执行函数的简单方法。这种方法就是IDispatch接口。

    

1.2IDispatch接口

    

    简单来讲,IDispatch将接收一个函数的名称并执行它。IDispatch的IDL描述

    interfaceIDispatch : 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

            );

    [local]

    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

            );

    [call_as(Invoke)]

    HRESULT RemoteInvoke(

                [in] DISPID dispIdMember,

                [in] REFIID riid,

                [in] LCID lcid,

                [in] DWORD dwFlags,

                [in] DISPPARAMS * pDispParams,

                [out] VARIANT * pVarResult,

                [out] EXCEPINFO * pExcepInfo,

                [out] UINT * pArgErr,

                [in] UINT cVarRef,

                [in, size_is(cVarRef)] UINT *rgVarRefIdx,

                [in, out, size_is(cVarRef)]VARIANTARG * rgVarRef

            );

    IDispatch最使人感兴趣的是GetIDsOfNames和Invoke.GetIDsOfNames将读取一个函数的名称并返回其调度ID(DISPID)。DISPID是一个长整数,它标识一个函数。自动化控制程序将把DISPID传给Invoke成员函数。Invoke可以将DISPID作为函数指针数组的索引。

    

  • 调度接口

    IDispatch::Invoke的实现与vtbl还有另外一方面是相似的:他们都可以定义接口。IDispatch::Invoke的一个实现所实现是我函数集被称为一个调度接口。而COM接口是一个指向一个函数指针数组的指针,此数组的前三个元素分别是:Queryinterface、AddRef和Release。如下给出调度接口的一个图示:

    函数和接口

    左边是一个传统的COM接口IDispatch,他是以vtbl的方式实现,右边是一个调度接口,Invoke能识别的DISPID对于调度接口至关重要。上图给出的时一种可能实现,GetIDsOfNames通过函数名称获得其DISPID,Invoke通过DISPID来找到响应的函数指针,来执行函数。

    同时也可以用一个COM接口实现IDispatch::Invoke:

    函数和接口

    

  • 两重接口

    上图不是用COM组件实现调度接口的唯一方法。也可以应用如下的方法。让实现IDispatch::Invoke的COM组件继承IDispatch而不是IUnknown。这样是称作两重接口的方法,通过Invoke可以访问的函数也可以直接通过vtbl访问到。

    函数和接口

    

2.IDispatch的应用

    

           考虑下面的VB程序:

                   Dim Cmpnt As Object

    Set Cmpnt = CreateObject("InsideCom.Cmpnt1")

    Cmpnt.Fx

    通过COM组件实现的IDispatch接口调用Fx,下面看看C++如何实现这个任务。

    HRESULT hr = OleInitialize(NULL);

    wchar_tprogid[] = L"InsideCom.Cmpnt1";

    CLSID clsid;

    CLSIDFromProgID(progid, &clsid);

    IDispatch *pDispach = NULL;

    CoCreateInstance(clsid,

    NULL,CLSCTX_INPROC_SERVER,

    IID_IDispatch,(void**)&pDispach);

    DISPID dispid;

    OLECHAR* name = L"Fx";

    pDispach->GetIDsOfNames(

    IID_NULL,&name,

    1,GetUserDefaultLCID(),&dispid);

    DISPPARAMS dispparamsNoArgs = {

    NULL,NULL,0,0

    }

    pDispach->Invoke(dispid,

    IID_NULL,

    GetUserDefaultLCID(),

    DISPATCH_METHOD,

    &dispparamsNoArgs,

    NULL,NULL, NULL);

    通过CoCreateInstance返回一个IDispatch指针,通过IDispatch::GetIDsOfNames可以将函数名称转换成一个DISPID在通过IDispatch::Invoke调用响应的函数。

    

2.1Invoke函数的参数

    

    第一个参数是控制程序待调用函数的DISPID,第二个是保存的必须为IID_NULL。第三个是保存的位置信息。其他的下面讨论。

    

  • 方法和属性

    COM接口中所有成员都是函数,应用“Set”和“Get”类函数来模拟岁成员变量的访问。例如SetVisible可以设置一个窗口的可见性。则GetVisible可以获得这一属性。

    If(pIWindows->GetVisible() == FALSE)

         pIWindows->SetVisible(TRUE);

    对于VB用Set和Get就不太理想,在vb中如下调用

    If Window.Visible = False Then

         Window.Visible= True

    End If

    IDL中的propget核propput属性可以将COM函数说明成将其当成一个属性看待。

    

  • 调度参数

    IDispatch::Invoke的第五个参数包括的时传给被调用函数的参数。结构如下

    typedef struct tagDISPPARAMS

    {

   VARIANTARG *rgvarg;

   DISPID *rgdispidNamedArgs;

   UINT cArgs;

   UINT cNamedArgs;

    }   DISPPARAMS;

    结构的第一个参数是一个参数数组,cArgs成员则是数组中元素的个数。每一个参数类型都是VARIANTARG类型。VARIANT是很多不同类型的一个大结合。

    

  • 返回值的获得

    第六个参数pVarResult指向一个VARIANT结构的指针。保存Invoke所执行函数或propget的结果。

    

  • 异常情况

    IDispatch::Invoke的倒数第二个参数为指向EXCEPINFO结果指针。若Invoke执行函数或属性遇到一个异常情况,则此结构被填入关于此异常情况的一些信息。

    typedef struct tagEXCEPINFO {

   WORD  wCode;

   WORD  wReserved;

   BSTR  bstrSource;

   BSTR  bstrDescription;

   BSTR  bstrHelpFile;

   DWORD dwHelpContext;

   PVOID pvReserved;

   HRESULT (__stdcall *pfnDeferredFillIn)(struct tagEXCEPINFO *);

   SCODE scode;

    } EXCEPINFO, * LPEXCEPINFO;

    在错误代码(wCode)或返回值(scode)中必须包括一个标识的值。而另外一个为0;

    EXCEPINFO excepinfo;

    HRESULT hr = pDispach->Invoke(...,&excepinfo);

    if(FAILED(hr))

    {

    if(hr == DISP_E_EXCEPTION)

    {

        if(excepinfo.pfnDeferredFillIn( != NULL))

            (*(excepinfo.pfnDeferredFillIn))(&excepinfo);

        cout<<"Exception information: "<<endl

            <<"Source: "<<excepinfo.bstrSource<<endl

            <<"Description: "<<excepinfo.bstrDescription<<ends;

    }

    }

    

3.类型库

    

         类型库将提供有关组件、接口、方法、属性、参数及结构的类型信息。类型库是一个二进制文件。

    

    

3.1类型库的创建

    

    自动化库函数CreateTypeLib可以创建一个类型库。该函数将返回一个可以用响应的信息填充类型库的ICreatetypeLib接口,可以应用IDL生成代理/存根DLL代码。

    

  • Library语句

    应用IDL建立类型库的关键是library语句,library关键字以后的方括号的内容都将边缘到类型库中。

    [

    object,

    uuid(7AAAE05F-283E-4D79-B98C-5D3DCBE733BB), //IID_IPoint

    每日一道理
无知者为梦想中的虚幻而苦苦等待,换回的不是所求的,而是岁月在脸上留下的印痕,一事无成的人一生便是虚度。生活中,与其花时间去等待,不如加快步伐去追寻理想,试着与时间赛跑,也许身躯、心理会感到劳累,但这样的生活毕竟是充实的。

    helpstring("IPointinterface"),

    oleautomation,

    dual

    ]

    interface IPoint : IDispatch

    {

    [propget, helpstring("Returns and sets X coordinate")]

    HRESULTx([out,retval]int *retval);

    [propput, helpstring("Returns and sets X coordinate")]

    HRESULTx([in]intValue);

    [propget, helpstring("Returns and sets Y coordinate")]

    HRESULTy([out,retval]int *retval);

    [propput, helpstring("Returns and sets Y coordinate")]

    HRESULTy([in]intValue);

    [helpstring("Displaythe Point.")]

    HRESULTdisplay();

    }

    [

    uuid(13BA7D38-8F44-4E80-9DAE-6E01776BEE16),//LIBID_Point

    helpstring("PointComponent Type Library"),

    version(1.0)

    ]

    library Point

    {

    importlib("stdole32.tlb");

    interface IPoint;

    [

        uuid(0615EA9A-0ED9-458D-9F0D-A9E3645338AB),//CLSID_Point

        helpstring("PointClass"),

        appobject

    ]

    coclass Point

    {

        [default] dispinterfaceIPoint;

    }

    }

    

  • 类型库的分发

    在生成了类型库以后,既可以将其作为一个单独的文件刊行,也可以将其作为一个资源包括在EXE或DLL中。

    

3.2类型库的应用

    

    应用类型库的第一步是装载它,LoadRegTypeLib他将试图从Windows的注册表中装载指定的类型库。若失败则可以应用LoadTypeLib从磁盘装载指定的类型库或者LoadTypeLibFromResource从EXE/dll装载指定的类型库。

    HRESULThr;

    LPTYPELIBpTypeLib = NULL;

    hr= LoadRegTypeLib(LIBID_Point,1,0,0,&pTypeLib);

    if(FAILED(hr))

    {

        hr= LoadTypeLib(OLESTR("Point.tlb"),&pTypeLib);

    }

    if(FAILED(hr))

        return hr;

    hr= pTypeLib->GetTypeInfoOfGuid(IID_IPoint,&s_pTypeInfo);

    pTypeLib->Release();

    if(FAILED(hr))

        return hr;

    s_pTypeInfo->AddRef();

    return hr;

    

3.3注册表中的类型库

    

    在HKEY_CLASS_ROOT\TypeLib关键字下地LIBID类表。

    函数和接口

    

4.类型库的实现

    

    STDMETHODIMPCPoint::GetTypeInfoCount(__RPC__out UINT *pctinfo)

    {

    *pctinfo= 1;

    return S_OK;

    }

    STDMETHODIMP CPoint::GetTypeInfo(UINTiTInfo, LCID lcid, ITypeInfo **ppTInfo)

    {

    HRESULThr;

    if(iTInfo != 0)

        return TYPE_E_ELEMENTNOTFOUND;

    if(ppTInfo == NULL)

        return E_POINTER;

    if(s_pTypeInfo)

    {

        s_pTypeInfo->AddRef();

        hr= S_OK;

    }

    else

    {

        hr = LoadMyTypeLib();

    }

    if (!hr)

        *ppTInfo= s_pTypeInfo;

    return hr;

    }

    STDMETHODIMP CPoint::GetIDsOfNames(REFIIDriid, LPOLESTR *rgszName, UINT cNames, LCID lcid, DISPID *rgDispId)

    {

    HRESULThr;

    if(IsEqualIID(riid,IID_NULL))

        return DISP_E_UNKNOWNINTERFACE;

    if (!s_pTypeInfo)

    {

        hr= LoadMyTypeLib();

        if(FAILED(hr))

            return hr;

    }

    hr= s_pTypeInfo->GetIDsOfNames(rgszName,cNames,rgDispId);

    return hr;

    }

    STDMETHODIMP CPoint::Invoke(DISPIDdispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)

    {

    HRESULThr;

    if(IsEqualIID(riid,IID_NULL))

        return DISP_E_UNKNOWNINTERFACE;

    if (!s_pTypeInfo)

    {

        hr= LoadMyTypeLib();

        if(FAILED(hr))

            return hr;

    }

    hr= s_pTypeInfo->Invoke(this,dispIdMember,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr);

    return hr;

    }

    HRESULT CPoint::LoadMyTypeLib()

    {

    HRESULThr;

    LPTYPELIBpTypeLib = NULL;

    hr= LoadRegTypeLib(LIBID_Point,1,0,0,&pTypeLib);

    if(FAILED(hr))

    {

        hr= LoadTypeLib(OLESTR("Point.tlb"),&pTypeLib);

    }

    if(FAILED(hr))

        return hr;

    hr= pTypeLib->GetTypeInfoOfGuid(IID_IPoint,&s_pTypeInfo);

    pTypeLib->Release();

    if(FAILED(hr))

        return hr;

    s_pTypeInfo->AddRef();

    return hr;

    }

文章结束给大家分享下程序员的一些笑话语录: 不会,Intel会维持高利润,也会维持竞争局面,国外的竞争不是打死对方的那种。你看日本有尼康,佳能,索尼,都做相机,大家都过得很滋润。别看一堆厂,其实真正控制的是后面的那几个财团——有些竞争对手,后面其实是一家人。

--------------------------------- 原创文章 By 函数和接口 ---------------------------------

免责声明:文章转载自《函数接口Inside COM读书笔记调度接口与自动化》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇算法竞赛专题解析(6):搜索进阶(1)--搜索基础ThinkPHP的增、删、改、查下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

C++——双指针 (转)

转自: https://www.cnblogs.com/kyoner/p/11087755.html 左右指针示例: /** 二分查找 */ int find(vector<int> &values, int left, int right, int target) { while(left<...

Android系统--输入系统(六)模拟输入驱动程序

Android系统--输入系统(六)模拟输入驱动程序 1. 回顾输入子系统 简单字符设备驱动:应用程序通过调用驱动所实现的函数使能硬件。 输入子系统:由于有多个应用程序使用输入子系统,故肯定使用的是早已规定好驱动接口,我们所需要实现的这是实现硬件相关的操作。 2. 输入子系统特性 有多套open/read/write接口 当应用程序调用这些接口,驱...

kafka时间轮的原理(一)

概述早就想写关于kafka时间轮的随笔了,奈何时间不够,技术感觉理解不到位,现在把我之前学习到的进行整理一下,以便于以后并不会忘却。kafka时间轮是一个时间延时调度的工具,学习它可以掌握更加灵活先进的定时器技术,补益多多。本文由浅到深进行讲解,先讲解定时器基础以及常用定时器,接着就是主要的kafka时间轮实现。大部分都是原理。后期作者写第二部分的时候专门...

Repository总结

Repository有两种实现思想1.自由式。领域层(DomainModel和DomainService)需要什么样的数据,都问Repository拿,Repository根据领域层的需要,增加许多方法。2.固定式。Repository只提供有限的几个接口,其中查询数据只提供FindById、GetAll几种方法。 注意,固定式、特别是泛型的固定式(泛型的...

第十章 数组和指针

一 数组 数组由一系列相同的元素构成。 告诉编译器需要一个数组的方式叫数组声明(array declaration),声明需要告诉编译器数组元素的个数和类型。  初始化 int main(void) {   int powers[8] = {1,2,4,8,16,32,64,128};  ... } 未初始化的数组元素值为内存中的实际值,此值是不确定...

Linux 内核 hlist 详解

在Linux内核中,hlist(哈希链表)使用非常广泛。本文将对其数据结构和核心函数进行分析。 和hlist相关的数据结构有两个:hlist_head 和 hlist_node //hash桶的头结点struct hlist_head {   struct hlist_node *first;//指向每一个hash桶的第一个结点的指针};//hash桶的...