Inside 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;

}

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iot-genius

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值