65.9K
CodeProject 正在变化。 阅读更多。
Home

小 ATL 技巧:第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (14投票s)

2002年10月1日

5分钟阅读

viewsIcon

121154

downloadIcon

1252

本系列文章将演示如何以一种节省时间和精力的方式使用 ATL 的一些酷功能。

Sample Image - SmallATLTricks1.jpg

引言

本文是系列文章的第一篇,旨在通过一些小的实现技巧来演示 ATL 的一些精彩功能。本文涵盖了错误分发机制。许多程序员(至少我自己)会因为在这里和那里实现这种抛出每一个小错误的复杂性而回避这个功能。本文提供的方法使开发人员能够以一种可扩展且可用的方式实现强大的设计策略。本文主要目标是使用 ATL 3.0 编写代码,并在 VB6 或 .NET 中使用该代码。

ATL 中的错误分发机制

此功能涉及使用三个接口:ISupportErrorInfoIErrorInfoICreateErrorInfo。以下是使您的 COM 组件支持错误信息功能的步骤:

  1. 如果您使用 ATL 向导创建组件,则在创建“简单对象”时,需要选中带有文本“Support ISupportErrorInfo”的复选框。如下图所示:


    此步骤会自动将 ISupportErrorInfo 的名称添加到组件的基类列表中,并实现 InterfaceSupportsErrorInfo 函数。下面是由此生成的一些代码行,以及它们在源项目中的文件名称:

    Foo.h

    class ATL_NO_VTABLE CFoo : 
      ...
      ...
      public ISupportErrorInfo,
      ...
    {
      ...
      ...
    
      BEGIN_COM_MAP(CFoo)
        ...
        COM_INTERFACE_ENTRY(ISupportErrorInfo)
        ...
      END_COM_MAP()
    
      ...
      ...
    
    // ISupportsErrorInfo
      STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
    };
    

    Foo.cpp

    STDMETHODIMP CFoo::InterfaceSupportsErrorInfo(REFIID riid)
    {
      static const IID* arr[] = 
      {
        &IID_IFoo
      };
      for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
      {
        if (InlineIsEqualGUID(*arr[i],riid))
          return S_OK;
      }
      return S_FALSE;
    }
    

    此函数的实现没有什么特别之处。它只是告诉调用者该对象支持错误信息,因此,如果该组件的任何方法失败,调用者都可以从组件中查询错误信息。C++ 中的查询机制本身就可以写成另一篇文章,因此在此不作赘述。此外,由于代码旨在用于 VB6 和 .NET,因此错误处理足够简单,因为在这些环境编写的程序中,错误会自动作为运行时异常引发。

  2. 第二步涉及编写一些您自己的代码来进行错误分发。这些代码将在代码的许多地方使用,因此我们编写了一个小型类来进行分发。

    请允许我介绍我们自己开发的 CError 类。此类可以在源存档的 Error.hError.cpp 文件中找到。此类只不过是 2 个静态函数的集合,用于帮助分发错误。第一个也是主要的“主力”函数是 DispatchError() 函数。该函数的原型如下:

    HRESULT DispatchError(HRESULT hError, REFCLSID clsid,
      LPCTSTR szSource, LPCTSTR szDescription,
      DWORD dwHelpContext, LPCTSTR szHelpFileName);
    

    该函数将 ATL 错误分发机制所需的大部分组件以 C++ 程序员友好的数据类型,即 TCHAR 字符串表示。该函数接受一个 HRESULT 作为错误标识符。对于我们大多数自定义错误,如果只想向使用我们组件的程序员显示错误消息,则可以将此参数设置为 E_FAIL。但是,如果错误是程序运行时可能频繁发生并需要显示给用户的错误,那么我们必须使用 MAKE_HRESULT 宏创建一个 HRESULT,以便在使用我们组件的程序员可以轻松识别它。使用此函数生成的错误会进入 VB6 下的 Err 对象,然后可以从其属性中检索错误信息。一个典型的 VB 代码,用于捕获错误并显示适当的消息框,如下所示:

    Public Sub MySub()
            'We must have a look at every error
            On Error GoTo ErrLabel
            ....
            ....
            
            'If everything goes fine, avoid error handling
            'by exiting from the subroutine
            Exit Sub
        
    ErrLabel:
            If Err.Number = MY_ERROR_CODE Then
                'Code to display user friendly message
            Else
                'We must throw back any unknown errors
                Err.Raise Err.Number, Err.Source, Err.Description, _
                        Err.HelpFile, Err.HelpContext
            End If
    End Sub
    

    回到 DispatchError(),该函数首先将每个字符串转换为 LPOLESTR,这是错误分发接口可以理解的。但在这样做之前,它会检查任何字符串是否为 NULL,如果是,它当然不会在 LPOLESTR 中设置相应的信息。如果提供的描述为 NULL,该函数会检查它是否可以自行获取一些错误信息。它会检查错误是否为标准 Win32 错误代码,如下所示:

    if(HRESULT_FACILITY(hError) == FACILITY_WIN32)
    {
      // Code to get the Win32 error message
    }
    

    在确认错误为标准 Win32 错误后,它使用 FormatMessage() API 调用获取消息。

  3. 最后,我们准备好了 ATL 分发错误所需的所有内容。我们使用 CreateErrorInfo API 调用创建一个 ICreateErrorInfo 对象,并用错误信息填充它,如下所示:

    // Get the ICreateErrorInfo Interface
    ICreateErrorInfo *pCreateErrorInfo = NULL;
    HRESULT hSuccess = CreateErrorInfo(&pCreateErrorInfo);
    ATLASSERT(SUCCEEDED(hSuccess));
    
    // Fill the error information into it
    pCreateErrorInfo->SetGUID(clsid);
    if(wszError != NULL)
      pCreateErrorInfo->SetDescription(wszError);
    if(wszSource != NULL)
      pCreateErrorInfo->SetSource(wszSource);
    if(wszHelpFile != NULL)
      pCreateErrorInfo->SetHelpFile(wszHelpFile);
    pCreateErrorInfo->SetHelpContext(dwHelpContext);
    

    在此步骤之后,我们查询 ICreateErrorInfo 对象以获取 IErrorInfo 对象,该对象具有上面使用的所有 Set 函数的 Get 等效项。

    // Get the IErrorInfo interface
    IErrorInfo *pErrorInfo = NULL;
    hSuccess = pCreateErrorInfo->QueryInterface(IID_IErrorInfo,
          (LPVOID *)&pErrorInfo);
    

    然后,此错误对象与当前线程关联,以便当任何函数返回错误代码而不是 S_OK 时,调用者都可以从线程中查询错误信息。使用以下 API 调用将错误对象与当前线程关联:

    // Set this error information in the current thread
    hSuccess = SetErrorInfo(0, pErrorInfo);
    

    错误分发类准备就绪后,从组件中抛出错误只需一行代码即可完成:

    Foo.cpp

    STDMETHODIMP CFoo::GenerateError(BSTR Message)
    {
      ...
      ...
      
      return CError::DispatchError(E_FAIL, // This represents the error
        CLSID_Foo,  // This is the GUID of component throwing error
        _T("Foo"),  // This is generally displayed as the title
        szMessage2, // This is the description
        0,      // This is the context in the help file
        NULL);      // This is the path to the help file
    }
    

    现在最后要介绍的函数是 DispatchWin32Error()。此函数是 DispatchError() 的简单包装器,旨在使组件设计者更容易抛出 Win32 错误。该函数的实现如下所示:

    HRESULT CError::DispatchWin32Error(DWORD dwError, REFCLSID clsid,
      LPCTSTR szSource, DWORD dwHelpContext, LPCTSTR szHelpFileName)
    {
      // Dispatch the requested error message
      return DispatchError(
        HRESULT_FROM_WIN32(dwError), // Convert error no. to HRESULT
        clsid, szSource, NULL, dwHelpContext,
        szHelpFileName);
    }
    

    该函数使用 HRESULT_FROM_WIN32 宏将错误编号转换为包含 Win32 设施代码的 HRESULT。它还在 szDescription 参数中填充 NULL,因为它知道 DispatchError() 函数会查找与 HRESULT 中嵌入的 Win32 错误代码对应的消息。

结论

至此,ATL 的错误分发机制就告一段落了。在下一篇文章中,我们将介绍创建 ATL 中不可创建对象的下一个小技巧。

© . All rights reserved.