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

公开 COM 对象中的表格数据 - 第三部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (7投票s)

2000年2月21日

CPOL
viewsIcon

68201

downloadIcon

1265

添加书签功能相对容易,它使我们的 ADO 记录集可以与更多的绑定数据控件一起使用。

  • 下载支持书签的简单数据对象 - 50 KB
  • 下载 Visual Basic 提供程序测试程序 - 5 KB
  • 获取上面测试程序中使用的 Janus GridEx 的试用版.
  • IRowsetLocate 和书签

    为了支持一些更苛刻的绑定数据控件,我们需要支持书签。我们在上一篇文章中开发的代理行集已经内置了一些对书签的支持,但行集本身并不公开 IRowsetLocate 或与书签相关的属性,因此消费者无法使用书签功能。在本文中,我们将通过添加对 IRowsetLocate 的支持来解决这个问题。

    在 MSDN 中快速搜索 IRowsetLocate 会引导我们找到一个非常有用的接口实现,该实现可以在 OLE DB 提供程序模板文档的“增强简单的只读提供程序”部分找到。不幸的是,提供的原始实现存在一些错误,最新版本更好,但在行数超过 256 行时仍然存在错误。我们将修复这些错误,然后将接口集成到代理行集中。完成后,很容易构建 IRowsetScroll 和 IRowsetExactScroll 的实现以完成我们的书签支持。

    支持复杂的绑定数据控件

    OLE DB 的优点之一是您可以选择要公开多少功能。如果您的提供程序相对简单,那么您不必提供关系数据库提供程序中预期的所有复杂功能。虽然 OLE DB 规范有各种“级别”的“一致性”,但要弄清楚您的提供程序确切需要什么功能通常很困难。因此,OLE DB 规范的灵活性是一把双刃剑,虽然符合规范很容易,但消费者要求您在他们可以使用您的提供程序之前实现一些额外功能也同样容易。

    如果所有绑定数据控件都附带说明其确切期望的提供程序的文档,那就没那么糟糕了。不幸的是,我还没有找到附带此类文档的控件。每个控件都对提供程序有一定的任意要求。似乎可以确保您的提供程序能够与所有可能的消费者一起工作的唯一方法是实现整个 OLE DB 规范。然而,即使这样可能也不足以满足某些 Microsoft 控件。例如,如果您想支持 Microsoft 数据网格的读/写功能,那么您似乎需要支持文档或 SDK 中不存在的某些接口!

    如果 OLE DB 使用标准的 COM 功能发现机制 - QueryInterface() - 那么通过简单地监视它们请求的接口,就可以相对容易地弄清楚某些消费者需要您做什么。不幸的是,OLE DB 使用基于属性的机制进行功能发现。如果您未能正确回答“您支持哪些属性”的问题,那么您将永远看不到用于提供消费者所需功能的接口的 QI 调用。我理解为什么设计者会这样做:消费者可能需要一次性发现有关提供程序的许多信息,多次接口请求和调用会非常低效,但这使得尝试和找出第三方消费者需要您的提供程序的什么信息变得非常困难。OLEB 服务提供程序会使事情更加混乱,因为它会在某些情况下,如果它可以从您提供的功能合成您缺少的功能,那么它就会为您提供额外功能。最后,属性机制的 ATL 实现远非易于跟踪,并且没有任何调试输出来确切显示正在发生的属性调用。

    这使得为您的 OLE DB 提供程序添加功能特别困难,因为在实际测试代码与每个控件的兼容性之前,您无法知道您将能够支持多少控件。即使是相对简单的功能,例如书签,也可能由于缺乏关于特定控件所需内容的文档而难以实现。

    确定控件的需求

    作为向提供程序添加功能的难度的示例,请查看示例程序,并使用我们在上一篇文章中开发的对象的运行它。测试是一个简单的 Visual Basic 程序,它创建我们的对象,允许我们从中获取记录集,然后将记录集连接到各种绑定数据控件。正如您将看到的,如果您按“Make Table”,然后按“Get Recordset”,然后按各种按钮将记录集连接到控件,每个控件对我们极简的记录集实现都有不同的反应:只有 MSHFlexGrid 有效。Data List 和 Data Combo 保持空白。Linked Edit 有效,但这“是一个简单的绑定数据控件”——只绑定到一个行的控件——所以我们期望它能工作……Data Grid 告诉我们需要书签,而Janus GrixEx只是报告我们是“无效记录集”。

    当我们在 Cursor Location 框架中选中“Client”复选框时,所得结果与之形成对比。使用客户端游标引擎时,ADO 会介入并为我们实现缺失的功能。这很好,如果我们真的想使用客户端游标,那么我们的工作就完成了。但是,客户端游标存在一个主要问题——它们是客户端的。在这种情况下,这意味着我们为了让数据对象能够保留其数据的所有权并在需要时才进行转换所做的所有努力都浪费了。ADO 游标引擎只是从我们的对象将所有数据提取到游标引擎中,并向行集实现添加功能……并非理想。

    我们可以尝试通过监视我们尝试将提供程序附加到每个控件时提供的调试字符串输出来弄清楚我们需要什么功能。将 _ATL_DEBUG_QI 添加到提供程序的项目设置中,然后执行重新生成全部。然后将 Visual Basic 设置为 OLE DB 提供程序的调试目标,并开始调试运行。在 Visual Basic 中加载测试程序项目并运行它。然后,您应该会在 Visual C++ 调试器中看到调试字符串输出。不幸的是,结果有些误导。正如我上面指出的,虽然我们确实看到了一些 QI 调用,但大部分协商似乎是通过对行集属性的请求发生的(Janus GridEx 甚至没有这样做!)。

    Data Grid QI 请求 IConnectionPointContainer(可能是在查看我们是否支持更改通知),然后是 IColumnsInfo 和 ICommandText,最后进行 get properties 调用……DataList 只请求 ICommandText 和 IColumnsRowset,然后进行 get properties 调用。DataCombo 调用 get properties,然后 QI 请求 IConnectionPointContainer、IColumnsInfo 和 ICommandText,再次进行 get properties 调用,然后 QI 请求 IColumnsRowset。所有这些都不会让我们相信书签和 IRowsetLocate 是缺失的功能……

    只有 Microsoft Data Grid 向我们提供了有关其要求的有意义信息,那是通过一条错误消息!由此,我们可以查看 OLE DB 文档中的书签支持,并发现我们必须实现 IRowsetLocate 并为多个属性值做出正确的回答……正如我们将看到的,在我们的行集中实现书签将使其与测试程序中的某些控件一起工作,其他控件至少会给我们更多关于它们还需要什么的提示。不幸的是,我们只能通过反复试验来发现这一事实。

    支持 IRowsetLocateImpl 

    要添加对书签的支持,我们需要更改我们的 DataObjectRowset 对象,使其从 CProxyRowsetImpl 派生,而 CProxyRowsetImpl 以 IRowsetLocate 而不是 IRowset 作为其基类。 如前所述,我们可以从 OLE DB 提供程序模板文档的“增强简单的只读提供程序”部分(此处)利用(窃取)一个实现。

    我们 DataObjectRowset 的更改结果如下所示

    // This is what we did have...
    
    class CDataObjectRowset : 
       public CProxyRowsetImpl<
          CMyDataObject, 
          CDataObjectRowset, 
          CConversionProviderCommand>
    {
    
    // This is what we have now...
    
    class CDataObjectRowset : 
       public CProxyRowsetImpl<
          CMyDataObject, 
          CDataObjectRowset, 
          CConversionProviderCommand,
          CRowsetStorageProxy<CDataObjectRowset>, 
          CRowsetArrayTypeProxy<
             CDataObjectRowset, 
             CRowsetStorageProxy<CDataObjectRowset> >,
          CSimpleRow,
          IRowsetLocateImpl < CDataObjectRowset > >
    {

    在这样的时刻,你会开始希望你选择了不同的默认模板参数顺序!存储和数组代理类以及简单的行对象都在我们最初的实现中默认设置了。现在,当我们只需要替换最后一个模板参数时,我们必须从 CProxyRowsetImpl 模板中复制默认值,并仅替换基类参数。

    因此,我们的行集现在从 IRowsetLocate 的实现派生,我们现在需要将其连接到我们的接口映射。由于所有接口目前都由 CProxyRowsetImpl 类处理,我们需要向 CDataObjectRowset 添加一个 COM 映射,该映射链接到 CProxyRowsetImpl 中存在的映射,然后添加对 IRowsetLocate 的支持。与此类似

    BEGIN_COM_MAP(CDataObjectRowset)
       COM_INTERFACE_ENTRY(IRowsetLocate)
       COM_INTERFACE_ENTRY_CHAIN(ProxyRowsetClass)
    END_COM_MAP()

    为了使链接更容易,我们在 CProxyRowsetImpl 中添加了一个 typedef,以便派生类可以使用“ProxyRowsetClass”而不是必须再次指定模板和所有模板参数。

    这一切都很好,但除非我们通过正确的 OLE DB 属性告诉我们的消费者我们支持书签,否则他们永远不会请求 IRowsetLocate 接口。

    调整属性映射

    乍一看,您可能会被误导,认为 ATL 提供的默认行集属性包含对书签的支持。毕竟,行集的属性映射如下所示

    BEGIN_PROPSET_MAP(CConversionProviderCommand)
       BEGIN_PROPERTY_SET(DBPROPSET_ROWSET)
          PROPERTY_INFO_ENTRY(IAccessor)
          PROPERTY_INFO_ENTRY(IColumnsInfo)
          PROPERTY_INFO_ENTRY(IConvertType)
          PROPERTY_INFO_ENTRY(IRowset)
          PROPERTY_INFO_ENTRY(IRowsetIdentity)
          PROPERTY_INFO_ENTRY(IRowsetInfo)
          PROPERTY_INFO_ENTRY(IRowsetLocate)
          PROPERTY_INFO_ENTRY(BOOKMARKS)
          PROPERTY_INFO_ENTRY(BOOKMARKSKIPPED)
          PROPERTY_INFO_ENTRY(BOOKMARKTYPE)
          PROPERTY_INFO_ENTRY(CANFETCHBACKWARDS)
          PROPERTY_INFO_ENTRY(CANHOLDROWS)
          PROPERTY_INFO_ENTRY(CANSCROLLBACKWARDS)
          PROPERTY_INFO_ENTRY(LITERALBOOKMARKS)
          PROPERTY_INFO_ENTRY(ORDEREDBOOKMARKS)
       END_PROPERTY_SET(DBPROPSET_ROWSET)
    END_PROPSET_MAP()

    并非一切如你所见。如果你查看 atldb.h,你会发现 property_info_entry 宏会扩展以包含一些特定于提供的每个属性的“默认”标志、类型和值。这些用于填充属性映射。令人困惑的是,为 IRowset 指定属性信息条目会导致标志显示您支持该接口,而为 IRowsetLocate 指定属性信息条目会导致标志显示您不支持该接口!这 hardly is intuitive。要确定您从属性映射中支持哪些属性,您必须搜索 atldb.h 并交叉引用它包含的其他宏。我认为如果所有这些 PROPERTY_INFO_ENTRY 宏实际上是 PROPERTY_INFO_ENTRY_VALUE 宏(这些宏会强制您指定属性值,这样您就可以一目了然地看到您是否支持某个属性等),那将要好得多。当然,我理解为什么会这样做,它使得向导更容易编码...

    所以,默认属性映射实际上是这样说的

    我们支持以下接口:IAccessor、IColumnsInfo、IConvertType、IRowset、IRowsetIdentity、IRowsetInfo。但不支持 IRowsetLocate。

    我们对 CanFetchBackwards、CanHoldRows 和 CanScrollBackwards 这些属性回答 true。但对 Bookmarks、BookmarkSkipped、BookmarkType、LiteralBookmarks 和 OrderedBookmarks 回答 false。

    很明显,不是吗。

    所以,我们应该只需要使用PROPERTY_INFO_ENTRY_VALUE宏并指定正确的值,例如 VARIANT_TRUE 以表示我们的 IRowsetLocate 属性?如果就这样简单就好了。不幸的是,如果这样做,指定的标志意味着该属性是只读的。OLE DB 属性既被提供程序用来指示其支持的功能,也被消费者用来指示其所需的功能。如果我们使用 PROPERTY_INFO_ENTRY_VALUE 宏,我们将强制消费者接受我们必须提供 IRowsetLocate。最好让我们将属性设为读/写,以便消费者可以读取该属性并看到我们支持该接口,然后写入该属性并将其设置为 false(如果不需要我们支持它)。这可以帮助我们优化内存使用,因为我们将知道我们永远不会被要求提供该接口……

    因此,要指定我们的书签属性,我们需要使用所有属性映射宏的“母亲”;PROPERTY_INFO_ENTRY_EX……查看由此产生的映射条目的代码。

    IRowsetLocateImpl 的问题

    现在我们已经准备好了请求 IRowsetLocate 实现的所有代码,我们只需要修复一些我们窃取的代码中的错误。

    可以在此处找到的 IRowsetLocateImpl 的当前版本存在一两个问题。首先,书签声明为 DWORD,但在 IRowsetLocate:: 中,它们被视为 BYTE,这导致如果行集超过 256 行,实现就会失败。我们通过在解引用书签指针之前将其强制转换为 DWORD 指针来修复此问题。其次,使用了一些可疑的锁定实践,这可能导致对象被锁定,并在发生错误时保持锁定状态。

    此实现的一个旧版本(来自同一示例的早期版本)在请求的书签为 DB_BMKLST 的情况下还有一个偏移量为 1 的错误。

    请参阅示例代码以获取修复后的实现。

    与代理行集的集成

    我们的书签实现几乎完成了。代理行集类已经提供了一些对书签的支持,因此我们无需更改任何内容。支持包含在以下位置

    CProxyRowsetImpl<>::OnPropertyChanged() 处理各种行集属性的设置,并根据需要设置相关属性。

    CProxyRowsetImpl<>::BookmarksRequired() 是一个辅助函数,可以调用它来确定我们是否需要返回一个包含书签列的行集。

    CProxyRowsetImpl<>::InternalGetColumnData() 处理并填充对书签列数据的请求。

    CProxyRowsetImpl<>:StorageProxy_GetColumnInfo() 处理调整我们返回的列信息,以便在需要时可以选择包含书签列。

    最后

    CProxyRowsetImpl<>:BuildColumnInfo() 始终构建一个包含书签列的列映射,然后允许 StorageProxy_GetColumnInfo 决定我们是否将此列返回给调用者。

    附加的书签接口

    虽然 IRowsetLocate 是支持书签的主要接口,但还有其他几个,最值得注意的是 IRowsetScroll,然后是短暂存在的 IRowsetExactScroll。

    IRowsetScroll 允许消费者获取位于行集内近似位置的行,在不需要精确定位时可以使用。它派生自 IRowsetLocate,并且可以在 IRowsetScrollImpl.h 中找到其实现。

    IRowsetExactScroll 似乎在 OLE DB 版本 2.0 中引入,然后在 OLE DB 版本 2.1 中被弃用——尽管控件经常仍然请求它。要包含对 IRowsetExactScroll 的支持,您必须使用 Data Access SDK 的 2.x 版本。如果您使用的是 2.1 或更高版本,则必须包含一个“deprecated”定义才能引入该接口——不幸的是,所使用的定义不是命名得更 OLE DB 特定的……IRowsetExactScroll 派生自 IRowsetScroll。由于我们实现 IRowsetScroll::GetApproximatePosition() 的方式——它在精确位置获取行,因为我们的书签也是行号——实现 IRowsetExactScroll 很简单,因为它只需调用 IRowsetScroll。不出所料,我们的 IRowsetExactScroll 实现可以在 IRowsetExactScrollImpl.h 中找到。

    示例代码假定我们使用的是 MDAC SDK v2.1 或更高版本,因此包含了 #DEFINE deprecated。查看 OleDb.H 文件并搜索 OLEDBVER 以了解您正在使用的版本。

    既然我们使用的是 OLE DB v2.1,我们不妨让我们的提供程序宣传这一点。要做到这一点,我们需要更改 DataSource 对象的属性映射。找到 PROVIDEROLEDBVER 属性的 PROPERTY_INFO_ENTRY 宏,并将其替换为该属性的 PROPERTY_INFO_ENTRY_VALUE 宏,并指定值为“2.1”。

    我们现在可以将 IRowsetScroll 和 IRowsetExactScroll 的支持添加到我们的行集的属性映射中。IRowsetScroll 很容易,只需使用 PROPERTY_INFO_ENTRY_EX 宏即可。IRowsetExactScroll 稍微复杂一些,因为 ATL 向导和标头中没有对此属性的支持——这不算大问题,我们可以像往常一样使用该宏,但我们必须向 ATL 提供的字符串表中添加一个条目。ATL 支持的所有属性都在向导添加到您项目的字符串表中都有条目。我们需要为该宏正常工作添加一个 IDS_DBPROP_IRowsetExactScroll 条目到此字符串表中。

    在示例中,我们通过将上面使用的 IRowsetLocateImpl<> 替换为 IRowsetExactScrollImpl<> 并添加相应的 COM 映射条目,将 IRowsetExactScroll 的支持添加到 DataObjectRowset 对象中。

    即使有了 IRowsetExactScroll 支持,Data Grid、Data List 和 Data Combo 仍然无法显示数据。Data List 和 Data Combo 查询并使用 IRowsetExactScroll,并且所有这些控件都创建了一个访问器,但它们实际上都没有拉取任何数据!这至少可以说是令人失望的。尤其是因为控件现在会默默失败,并且没有明显迹象表明我们需要做什么才能使它们工作…… 

    不过,至少 Janus 网格现在可以正常工作了。很可能其他非 Microsoft 控件也可能与我们一起工作! 

    源代码使用 Visual Studio 6.0 SP3 构建。使用 7 月版的 Platform SDK。如果您没有安装 Platform SDK,您可能会发现编译失败,找不到“msado15.h”。您可以通过创建一个名为该名称并包含“adoint.h”的文件来解决此问题。

    请通过电子邮件向我发送任何评论或错误报告。有关本文的更新,请在此处查看我的网站

    历史

    2000 年 7 月 29 日 - 更新源代码

    © . All rights reserved.