COM---调度接口与自动化

本文介绍了COM自动化中IDispatch接口的作用与实现细节,包括如何通过IDispatch实现组件的自动化控制,以及如何利用IDispatch接口进行函数调用和异常处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自动化服务器:实现了IDispatch接口的COM组件。

自动化控制程序:通过IDispatch接口同自动化服务器进行通信的COM客户。

IDispatch

通过COM接口提供的任何服务都可以通过IDispatch接口提供。有了IDispatch,COM可通过一个标准的接口提供它所支持的服务,而无需提供多个特定于服务的接口。

IDispatch将接受一个函数名,并执行它。

IDispatch定义
可以在编译器的\Include目录下找到IDispatch定义

interface IDispatch : IUnknown
{
    typedef [unique] IDispatch * LPDISPATCH;

    HRESULT GetTypeInfoCount(
                [out] UINT * pctinfo
            );

    HRESULT GetTypeInfo(
                [in] UINT iTInfo,
                [in] LCID lcid,
                [out] ITypeInfo ** ppTInfo
            );
    //读取一个函数名称并返回其调度ID,即DISPID
    HRESULT GetIDsOfNames(
                [in] REFIID riid,
                [in, size_is(cNames)] LPOLESTR * rgszNames,
                [in] UINT cNames,
                [in] LCID lcid,
                [out, size_is(cNames)] DISPID * rgDispId
            );

    //为执行某个函数,自动化控制程序把DISPID传给Invoke
    //Invoke的行为由受到的DISPID决定
    [local]
    HRESULT Invoke(
    //控制程序待调用函数的DISPID
                [in] DISPID dispIdMember,
    //保留的,必须为IID_NULL
                [in] REFIID riid,
    //保存位置信息
                [in] LCID lcid,
    //调用的函数是哪种类型
    //DISPATCH_METHOD  常规函数
    //DISPATCH_PROPERTYGET  获取属性的含
    //DISPATCH_PROPERTYPUT  设置属性的函数
    //DISPATCH_PROPERTYPUTREF  通过引用设置属性的函数
                [in] WORD wFlags,
    /*
     传给被调用函数的参数
     typedef struct tagDISPPARAMS
    {
       参数数组,只有那些能放入VARIANTARG结构中的类型才可以通过调度接口进行传递
      VARIANTARG *rgvarg; 
      DISPID *rgdispidNamedArgs;  命名参数的DISPID,对于C++永远为NULL
      UINT cArgs;  数组中元素个数
      UINT cNamedArgs;  命名参数个数,对于C++永远为0
    }   DISPPARAMS;
    */
                [in, out] DISPPARAMS * pDispParams,
    //用于保存Invoke所执行的函数或propget的结果
    //对于没有返回值的成员函数或propputs及propputrefs,为NULL
    //VARIANT是一个union结构,方便用相同的方式保存不同类型变量的值
                [out] VARIANT * pVarResult,
    /*
      若Invoke执行的函数或属性遇到一个例外情况,此结构被填入关于例外的信息。
     typedef struct FARSTRUCT tagEXCEPINFO
   {
   unsigned short wCode;               // Error code.
   Unsigned short wReserved;           // Reserved.
   BSTR bstrSource;                    // Exception source.
   BSTR bstrDescription;    //Exception description.
   BSTR bstrHelpFile;       // Help file path.
   Unsigned long dwHelpContext;       // Help context ID.
   Void FAR* pvReserved;              // Reserved.
   HRESULT (STDAPICALLTYPE FAR* pfnDeferredFillIn) // Pointer to a
         (struct tagEXCEPINFO FAR*);  // function that
                                      // fills in help and
                                      // description info.
   SCODE scode;                       // Return value.
   } EXCEPINFO, FAR* LPEXCEPINFO;
    */
                [out] EXCEPINFO * pExcepInfo,
   //若Invoke的返回值为DISP_E_PARAMNOTFOUND或DISP_E_TYPEMISMATCH
   //putArgErr保存与此错误相应的参数的索引
                [out] UINT * puArgErr
            );

IDispatch的使用

HRESULT hr = OleInitialize(NULL);

//得到应用程序的CLSID
wchar_tprogid[] = L"InsideCom.Cmpnt1";
CLSID clsid;
CLSIDFromProgID(progid, &clsid);

//创建组件,得到IDispatch指针
IDispatch *pDispach = NULL;
CoCreateInstance(clsid,
    NULL,CLSCTX_INPROC_SERVER,
    IID_IDispatch,(void**)&pDispach);

//得到调度函数Fx的DISPID
DISPID dispid;
OLECHAR* name = L"Fx";
pDispach->GetIDsOfNames(
    IID_NULL,   //必须为IID_NULL
    &name,      //函数名称
    1,          //函数个数
    GetUserDefaultLCID(),
    &dispid);   //DISPID

//函数Fx的参数
DISPPARAMS dispparamsNoArgs = {
    NULL,NULL,0,0
}

pDispach->Invoke(dispid,   //DISPID
    IID_NULL,              //必须为IID_NULL
    GetUserDefaultLCID(),
    DISPATCH_METHOD,       //方法
    &dispparamsNoArgs,     //方法参数
    NULL,                  //结果
    NULL,                  //例外
    NULL);                 //错误索引

以上代码可变成不用参数的任意调度函数,只需要向客户询问ProgID和待用函数的名称即可。
Invoke的威力在于可以被一致地调用,任何组件只要实现了Invoke,均可以用相同的代码使用它。

例外情形

用到Invoke的倒数第二个参数

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

调度接口

IDispatch::Invoke的一个实现所实现的函数集被称作一个调度接口。
调度接口的一个图示:
这里写图片描述

双重接口

让实现IDispatch::Invoke的COM组件继承IDispatch而不是IUnkown。双重接口也是一种调度接口,使得通过Invoke访问的函数也能直接通过vtbl访问。

这里写图片描述

示例

构造一个实现双重接口的IX的组件。

//
// Server.idl - IDL source for Server.dll
//
// This file will be processed by the MIDL compiler to
// produce the type library (Server.tlb) and marshaling code.
//

// Interface IX
[
    object,
    uuid(32BB8326-B41B-11CF-A6BB-0080C7B2D682),
    helpstring("IX Interface"),
    pointer_default(unique),
    dual,
    oleautomation
]
interface IX : IDispatch
{
    import "oaidl.idl" ;

    HRESULT Fx() ;
    HRESULT FxStringIn([in] BSTR bstrIn) ; 
    HRESULT FxStringOut([out, retval] BSTR* pbstrOut) ; 
    HRESULT FxFakeError() ;
} ;

客户使用双重接口连接到组件:
演示客户如何调用FxStringIn

这里写图片描述
这里写图片描述

类型库

COM中类型库,是与语言无关的、适合于解释性语言和宏编程语言的C++头文件等价的,为程序的正常运行提供某种程度的保证。
类型库将提供有关组件、接口、方法、属性、参数及结构的类型信息。类型库是一个二进制文件。

类型库的创建

可用IDL、MIDL建立类型库。
MIDL编译server.def后,会生成server.tlb的类型库。

关键语句是library,library之后的方括号中的内容都将被编译到类型库中。

//
// Server.idl - IDL source for Server.dll
//
// This file will be processed by the MIDL compiler to
// produce the type library (Server.tlb) and marshaling code.
//

// Interface IX
[
    object,
    uuid(32BB8326-B41B-11CF-A6BB-0080C7B2D682),
    helpstring("IX Interface"),
    pointer_default(unique),
    dual,
    oleautomation
]
interface IX : IDispatch
{
    import "oaidl.idl" ;

    HRESULT Fx() ;
    HRESULT FxStringIn([in] BSTR bstrIn) ; 
    HRESULT FxStringOut([out, retval] BSTR* pbstrOut) ; 
    HRESULT FxFakeError() ;
} ;

//
// Component and type library descriptions
//
[
    uuid(D3011EE1-B997-11CF-A6BB-0080C7B2D682),  //GUID
    version(1.0),                                //版本号
    helpstring("Inside COM, Chapter 11 1.0 Type Library") //帮助字符串
]
library ServerLib
{
    importlib("stdole32.tlb") ;

    // Component
    [
        uuid(0C092C2C-882C-11CF-A6BB-0080C7B2D682), 
        helpstring("Component Class")    
    ]
    coclass Component  //定义一个组件
    {
            //具有IX接口的组件
            //MIDL将编译生成一个包含组件和IX的类型库
            //之所以包含组件,是因为coclass包含library语句
            //之所以包含IX,是因为在library语句中引用了它
        [default] interface IX ;  
    } ;
} ;

类型库的分发

在生成了类型库之后,既可以将其作为一个单独的文件发行,也可以将其作为一个资源包含在EXE或DLL中,后者可简化安装过程。

类型库的使用

首先应装载并注册类型库。

//
// 装载并注册类型库
//
HRESULT CA::Init()
{
    HRESULT hr ;

    // Load TypeInfo on demand if we haven't already loaded it.
    if (m_pITypeInfo == NULL)
    {
        ITypeLib* pITypeLib = NULL ;
        // 从Windows注册表中装载指定的类型库
        // 返回ITypeLib接口类型的指针
        hr = ::LoadRegTypeLib(LIBID_ServerLib, 
                              1, 0, // Major/Minor version numbers
                              0x00, 
                              &pITypeLib) ;
        if (FAILED(hr)) 
        {
            trace("LoadRegTypeLib Failed, now trying LoadTypeLib.", hr) ;
            // 如果注册表中没有,从硬盘上装载

            // Get the fullname of the server's executable.
            char szModule[512] ;
            DWORD dwResult = ::GetModuleFileName(CFactory::s_hModule,
                                                 szModule,
                                                 512) ; 

            // Split the fullname to get the pathname.
            char szDrive[_MAX_DRIVE];
            char szDir[_MAX_DIR];
            _splitpath(szModule, szDrive, szDir, NULL, NULL) ;

            // Append name of registry.
            char szTypeLibFullName[_MAX_PATH];
            sprintf(szTypeLibFullName,
                    "%s%s%s",
                    szDrive,
                    szDir,
                    szTypeLibName) ;

            // convert to wide char
            wchar_t wszTypeLibFullName[_MAX_PATH] ;
            mbstowcs(wszTypeLibFullName, szTypeLibFullName, _MAX_PATH) ;  
            // 从硬盘上装载指定文件名称的类型库
            // 装载之后在注册表中注册
            // 但若提供了一个完整的路径名称,不会注册
            hr = ::LoadTypeLib(wszTypeLibFullName,
                               &pITypeLib) ;
            if(FAILED(hr))        
            {
                trace("LoadTypeLib Failed.", hr) ;
                return hr;   
            }

            // 注册类型库
            hr = RegisterTypeLib(pITypeLib, wszTypeLibFullName, NULL) ;
            if(FAILED(hr))        
            {
                trace("RegisterTypeLib Failed.", hr) ;
                return hr ;   
            }
        }

        // 为了获取某个组件或接口信息,将其CLSID或IID传给        
        // GetTypeInfoOfGuid,会返回一个指向所需项目的ITypeInfo指针
        hr = pITypeLib->GetTypeInfoOfGuid(IID_IX,
                                          &m_pITypeInfo) ; 

        pITypeLib->Release() ;
        if (FAILED(hr))  
        { 
            trace("GetTypeInfoOfGuid failed.", hr) ;
            return hr ;
        }   
    }
    return S_OK ;
}

注册表中的类型库

这里写图片描述

IDispatch接口的实现

前面通过GetTypeInfoOfGuid得到了ITypeInfo指针,此指针可用来实现IDispatch接口。

//
// IDispatch implementation
//
HRESULT __stdcall CA::GetTypeInfoCount(UINT* pCountTypeInfo)
{
    trace("GetTypeInfoCount call succeeded.") ;
    *pCountTypeInfo = 1 ;
    return S_OK ;
}

HRESULT __stdcall CA::GetTypeInfo(
    UINT iTypeInfo,
    LCID,          // This object does not support localization.
    ITypeInfo** ppITypeInfo)
{    
    *ppITypeInfo = NULL ;

    if(iTypeInfo != 0)
    {
        trace("GetTypeInfo call failed -- bad iTypeInfo index.") ;
        return DISP_E_BADINDEX ; 
    }

    trace("GetTypeInfo call succeeded.") ;

    // Call AddRef and return the pointer.
    m_pITypeInfo->AddRef() ; 
    *ppITypeInfo = m_pITypeInfo ;
    return S_OK ;
}

HRESULT __stdcall CA::GetIDsOfNames(  
    const IID& iid,
    OLECHAR** arrayNames,
    UINT countNames,
    LCID,          // Localization is not supported.
    DISPID* arrayDispIDs)
{
    if (iid != IID_NULL)
    {
        trace("GetIDsOfNames call failed -- bad IID.") ;
        return DISP_E_UNKNOWNINTERFACE ;
    }

    trace("GetIDsOfNames call succeeded.") ;
    HRESULT hr = m_pITypeInfo->GetIDsOfNames(arrayNames,
                                             countNames,
                                             arrayDispIDs) ;
    return hr ;
}

HRESULT __stdcall CA::Invoke(   
      DISPID dispidMember,
      const IID& iid,
      LCID,          // Localization is not supported.
      WORD wFlags,
      DISPPARAMS* pDispParams,
      VARIANT* pvarResult,
      EXCEPINFO* pExcepInfo,
      UINT* pArgErr)
{        
    if (iid != IID_NULL)
    {
        trace("Invoke call failed -- bad IID.") ;
        return DISP_E_UNKNOWNINTERFACE ;
    }

    ::SetErrorInfo(0, NULL) ;

    trace("Invoke call succeeded.") ;
    HRESULT hr = m_pITypeInfo->Invoke(
        static_cast<IDispatch*>(this),
        dispidMember, wFlags, pDispParams,
        pvarResult, pExcepInfo, pArgErr) ; 
    return hr ;
}

异常的引发

1、组件中实现ISupportErrorInfo接口

class CA : public CUnknown,
           public IX,
           public ISupportErrorInfo
{
    ... ...
    // ISupportErrorInfo
    virtual HRESULT __stdcall InterfaceSupportsErrorInfo(const IID& riid)
    {
        return (riid == IID_IX) ? S_OK : S_FALSE ;
    }
    ... ...
}

2、在IDispatch::Invoke实现中,在ITypeInfo::Invoke实现之前,调用SetErrorInfo(0, NULL)

HRESULT __stdcall CA::Invoke(   
      DISPID dispidMember,
      const IID& iid,
      LCID,          // Localization is not supported.
      WORD wFlags,
      DISPPARAMS* pDispParams,
      VARIANT* pvarResult,
      EXCEPINFO* pExcepInfo,
      UINT* pArgErr)
{        
    if (iid != IID_NULL)
    {
        trace("Invoke call failed -- bad IID.") ;
        return DISP_E_UNKNOWNINTERFACE ;
    }

    ::SetErrorInfo(0, NULL) ;

    trace("Invoke call succeeded.") ;
    HRESULT hr = m_pITypeInfo->Invoke(
        static_cast<IDispatch*>(this),
        dispidMember, wFlags, pDispParams,
        pvarResult, pExcepInfo, pArgErr) ; 
    return hr ;
}

3、

HRESULT __stdcall CA::FxFakeError()
{
    trace("FxFakeError is faking an error.") ;

    // 创建异常信息对象
    ICreateErrorInfo* pICreateErr ;
    // 当发生异常时,调用CreateErrorInfo
    // 获取一个ICreateErrorInfo指针
    HRESULT hr = ::CreateErrorInfo(&pICreateErr) ;
    if (FAILED(hr))
    {
        return E_FAIL ;
    }

    // 使用ICreateErrorInfo指针填充关于错误的信息
    // pICreateErr->SetHelpFile(...) ;
    // pICreateErr->SetHelpContext(...) ;
    pICreateErr->SetSource(L"InsideCOM.Chap11") ;
    pICreateErr->SetDescription(
        L"This is a fake error generated by the component.") ;
    IErrorInfo* pIErrorInfo = NULL ;
    hr = pICreateErr->QueryInterface(IID_IErrorInfo,
                                     (void**)&pIErrorInfo) ;
    if (SUCCEEDED(hr))
    {
        // 将ICreateErrorInfo指针作为第2个参数传过去
        // 第1个参数是保留的,恒为0
        ::SetErrorInfo(0L, pIErrorInfo) ;
        pIErrorInfo->Release() ;
    }
    pICreateErr->Release() ;
    return E_FAIL ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值