【问题标题】:What is the SHCIDS_ALLFIELDS flag of IShellFolder.CompareIDs trying to be?IShellFolder.CompareIDs 的 SHCIDS_ALLFIELDS 标志是什么?
【发布时间】:2018-11-07 10:22:17
【问题描述】:

短版

IShellFolder.CompareIDsSHCIDS_ALLFIELDS标志是什么意思?

加长版

在 Windows 95 中,Microsoft 引入了 shell。与其假设计算机是由文件和文件夹组成的,不如说它是由一个抽象的命名空间项组成的。

  • 而不是从驱动器根目录开始的路径(例如C:\Documents & Settings\Ian
  • 路径从命名空间的根目录开始 (Desktop)

为了容纳不是文件和文件夹的东西(例如网络打印机、控制面板、我的 Android 手机):

  • 您不要使用由反斜杠分隔的一系列名称(例如 C: \ Users \ Ian
  • 你使用 pidls,一系列不透明的 blob(例如 Desktop This PC OS (C:) 用户 伊恩)

PIDL 是不透明的 blob,每个 blob 仅对生成它的文件夹有意义。

为了扩展(或使用)shell 命名空间,您实现(或调用)一个 IShellFolder 接口。

IShellFolder 的方法之一用于要求命名空间扩展比较 到 ID 列表(PIDLs)

IShellFolder::CompareIDs 方法

根据项目标识符列表确定两个文件对象或文件夹的相对顺序。

HRESULT CompareIDs(
      [in] LPARAM             lParam,
      [in] PCUIDLIST_RELATIVE pidl1,
      [in] PCUIDLIST_RELATIVE pidl2
);

多年来,LPARAM 被记录为几乎始终为 0。来自shlobj.h c。 1999:

// IShellFolder::CompareIDs(lParam, pidl1, pidl2)
//   This function compares two IDLists and returns the result. The shell
//  explorer always passes 0 as lParam, which indicates "sort by name".
//  It should return 0 (as CODE of the scode), if two id indicates the
//  same object; negative value if pidl1 should be placed before pidl2;
//  positive value if pidl2 should be placed before pidl1.

所以你比较了两个 ID 列表——无论是什么意思,我们都完成了。

Windows 2000 添加了额外的排序选项标志

从 shell 的 版本 5 开始,LPARAM 的高 16 位现在可以包含额外的标志来控制 IShellFolder 应如何处理排序。

来自ShObjIdl.idl c。 Windows 8.1 SDK:

// IShellFolder::CompareIDs lParam flags
// *these should only be used if the folder supports IShellFolder2*
//
// SHCIDS_ALLFIELDS
//
// only be used in conjunction with SHCIDS_CANONCALONLY or column 0.
// This flag requests that the folder test for *pidl identity*, that is
// "are these pidls logically the same". This implies that cached fields
// in the pidl that would distinguish them should be tested.
// Without this flag, you are comparing the *object* s the pidls refer to.
//
// SHCIDS_CANONICALONLY
//
// This indicates that the sort should be *the most efficient sort possible*, the implication
// being that the result will not be displayed to the UI: the SHCIDS_COLUMNMASK portion
// of the lParam can be ignored. (Before we had SHCIDS_CANONICALONLY
// we assumed column 0 was the "efficient" sort column.)

注意这里的重点:

  • SHCIDS_CANONICALONLY 旨在成为我们拥有的最快最有效的排序方式
  • 从 UI 可用性的角度来看,它不必是合乎逻辑的;它必须是一致的

正如 Raymond Chen 指出的那样,它是the moral equivalent of a Unicode ordinal comparison

头文件甚至指出我们使用只是假设第 0 列是 “最快” 排序。但是现在我们将使用一个标志来 “使用可用的最快排序”

在我们使用 SHCIDS_CANONICALONLY 之前,我们假设第 0 列是“高效”的排序列。

它还指出,您可以忽略 LPARAM 的低 16 位(即列),因为我们不在乎 - 我们使用的是最有效的。

官方文档中反映了很多内容:

SHCIDS_CANONICALONLY

版本 5.0。 按名称比较时,比较系统名称而不是显示名称。传递此标志时,只要 Shell 文件夹实现一致的排序功能,就会根据 Shell 文件夹确定最有效的任何标准来比较这两项。当比较相等或排序结果不向用户显示时,此标志很有用。此标志不能与其他标志组合。

但是有了 SHCIDS_ALLFIELDS,我们开始偏离轨道

头文件说明AllFields只能与CanonicalOnly结合:

只能与 SHCIDS_CANONCALONLY 或第 0 列结合使用。

但 SDK 规定 CanonicalOnly 必须单独出现:

此标志不能与其他标志组合。

那是什么?

我们可以判断头文件是错误的,SDK 是大炮,照它说的做。

但是 AllFields 在说什么?

AllFields 正在试图 要求一些概念,但在文档后面却被掩盖了。

比较 ITEMIDLIST 结构中包含的所有信息,而不仅仅是显示名称。

ItemIDList 不包含显示名称,它包含一个 ItemIDList。他们是想说我应该查看 pidl blob 的内容吗?

  • 例如,如果这两个项目是文件,则文件夹应比较它们的名称、大小、文件时间、属性以及结构中的任何其他信息。

在什么情况下,对*同一个**文件的两个引用可能具有不同的名称、大小、文件时间、属性等?

SDK 示例做了一些不同的事情

Windows SDK Explorer Data Provider Shell Extension 示例 (github) 似乎会出现 CanonicalOnlyAllFields 标志一起:

HRESULT CFolderViewImplFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
{
   if (lParam & (SHCIDS_CANONICALONLY | SHCIDS_ALLFIELDS))
   {
      // First do a "canonical" comparison, meaning that we compare with the intent to determine item
      // identity as quickly as possible.  The sort order is arbitrary but it must be consistent.
      _GetName(pidl1, &psz1);
      _GetName(pidl2, &psz2);
      ResultFromShort(StrCmp(psz1, psz2));
    }

    // If we've been asked to do an all-fields comparison, test for any other fields that
    // may be different in an item that shares the same identity.  For example if the item
    // represents a file, the identity may be just the filename but the other fields contained
    // in the idlist may be file size and file modified date, and those may change over time.
    // In our example let's say that "level" is the data that could be different on the same item.
    if ((ResultFromShort(0) == hr) && (lParam & SHCIDS_ALLFIELDS))
    {
       //...
    }
}
else
{
   //...Compares by the column number in LOWORD of LPARAM
}

所以我们有完全冲突的文档、标题和示例:

SHCIDS_ALLFIELDS

  • SDK:永远不能与 SHCIDS_CANONICALONLY 一起出现
  • 标题:可以随时出现
  • 示例:只能与 SHCIDS_CANONICALONLY 一起出现

它想问什么

Windows 始终假定第 0 列是 fast 列。这可能是因为 Windows shell API 作者假定 PIDL 的 ItemID 将始终包含名称inside pidl opaque blob。

shell STRRET 结构允许您指向 pidl 中的字符串这一事实强化了这一点。

红利阅读:The kooky STRRET structure

所以在某个时候,他们添加了一个明确的标志,上面写着:

  • 我们不关心本地化、特定于区域设置的语言排序规则和 unicode 规范化算法
  • 只需对它们进行排序,因为我们需要查找重复项并检查是否相等

这对于 canonical 标志是有意义的:

  • 只要告诉我两个 IDList 是否指向同一个 对象

但是当他们谈论 All Fields 选项时,SDK 示例意味着什么:

如果我们被要求进行所有字段比较,请测试具有相同身份的项目中可能不同的任何其他字段。例如:

  • 如果项目代表一个文件,标识可能只是文件名
  • 但 idlist 中包含的其他字段可能是文件大小和文件修改日期,这些可能会随着时间而改变。

如果两个 PIDL 代表同一个 文件,比较它们的大小、日期等有何意义?我已经告诉过你它们是同一个 file,你用 All Fields 标志问我什么?为什么我不能只对 blob 进行二进制比较?为什么没有壳? CompareIDs 有什么作用

MemCmp(pidl1, pidl2)

没有?

  • SHCIDS_ALLFIELDS只会SHCIDS_CANONICALONLY一起出现吗?
  • SHCIDS_ALLFIELDS永远不会SHCIDS_CANONICALONLY一起出现吗?
  • SHCIDS_ALLFIELDS 可以在有和没有SHCIDS_CANONICALONLY 的情况下同时出现吗?
  • SHCIDS_ALLFIELDS with SHCIDS_CANONICALONLY 是什么意思?
  • SHCIDS_ALLFIELDS 没有 SHCIDS_CANONICALONLY 是什么意思?

如果SHCIDS_ALLFIELDS 被通过,它要我做什么?我应该点击底层数据存储来查询我知道的所有字段吗?

CompareIDs是用来比较ID的,还是用来比较对象的?

我想知道 CompareID 的目的是否是绝对不访问底层数据存储(例如硬盘、USB 电话、Mapi),并且仅根据您拥有的进行比较-hand 在 pidl 中。

这有两个原因:

  • 更快;许多命名空间在其 PIDL blob 中包含一些元数据 - 无需返回磁盘/Internet
  • 即使 pidls 可能引用同一个对象,它们的元数据也可能已过期
  • SHCIDS_CANONICALONLY 让调用者意识到两个 pidl 是同一个东西
  • 但是单独调用 SHCIDS_CANONICALONLY | SHCIDS_ALLFIELDS 可以告诉我们额外的元数据可能已经过时(尽管我不知道该信息对调用者有什么用处)

也许SHCIDS_CANONICALONLY 的意思是:

  • 请限制自己使用 pidl - 不要触摸磁盘来执行比较
  • 省略它的意思是:“是的,如果你真的需要的话,你可以打硬盘”

是这样吗?

  • 如果SHCIDS_CANONICALONLY 的意思是:“不要看除了 pidl 中的内容之外的任何东西,并告诉我这两件事是否是同一个对象”
  • 那么SHCIDS_ALLFIELDS得到了什么?
  • 它们什么时候会有所不同?
  • shell 在问我什么?

奖金问题

  • 如果SHCIDS_CANONICALONLY 表示执行最有效的排序,
  • 缺少SHCIDS_CANONICALONLY 是否意味着可以根据名称的本地化和自定义进行排序?
  • 缺少SHCIDS_CANONICALONLY 是否意味着强制根据名称的本地化和自定义进行排序?

“排序” 到 itemID 列表是什么意思?

SDK 示例根据每一列执行switch,并查找每一列的值。如果这意味着我必须通过网络加载视频才能加载音频采样率?

  • 我在比较 PIDLs
  • 或者我是在比较那些 pidls 指向的对象吗?

【问题讨论】:

标签: windows winapi windows-shell shell32


【解决方案1】:

SDK 示例基本正确(取决于 pidl 内容)。 if (lParam & (SHCIDS_CANONICALONLY | SHCIDS_ALLFIELDS)) 显然与 if ((lParam & SHCIDS_CANONICALONLY) || (lParam & SHCIDS_ALLFIELDS)) 相同,但没有告诉我们它们是否可以组合,答案是我不知道。我不明白为什么不。

只有 Microsoft shell 团队的成员知道真正的答案,但我们可以推测。

Win95基本上有4个标准字段。您可以在旧版IShellDetails 接口的文档中看到它们:

文件系统文件夹具有大量标准信息字段。 前四个字段是所有文件系统文件夹的标准字段。

Index | Title
-------------
0       Name
1       Size
2       Type
3       Date Modified

文件系统文件夹可能支持许多附加字段。 但是,他们不需要这样做,列索引 分配给这些字段可能会有所不同。

每个虚拟文件夹都有自己独特的一组信息字段。 通常,项目的显示名称在第 0 列中,但顺序 和可用字段的内容取决于 特定的文件夹对象。

然后在 Windows 2000 中,当添加了对 shell 扩展 column handlers 的支持时,情况发生了变化。这是支持 Vistas 堆叠支持等的属性系统的基础,列索引是项目属性与PROPERTYKEY 之间的映射(PROPERTYKEY 那时称为SHCOLUMNID)。

SHCIDS_CANONICALONLY:

这里的重要部分是 CANONICAL。

MSDN 说

按名称比较时,比较系统名称而不是显示名称。

shell 与其对术语显示名称的使用不一致,但它的实际含义是,比较解析名称,而不是您在资源管理器中看到的名称。

例如,文件夹视图可能包含“foo”和“foo”文件,但实际上它们是“foo.jpg”和“foo.png”,但“隐藏文件扩展名”功能隐藏了真实名称。

IShellFolder 实现知道其 pidl 中的哪个属性(列)对于其文件夹中的每个项目是唯一的,并且应该使用它来进行比较。

SHCIDS_ALLFIELDS:

这只是意味着您要比较所有支持的列,直到发现差异为止。

可以这样实现:

for (UINT i = 0; i < mycolumcount; ++i)
{
  hr = CompareIDs(i, pidl1, pidl2);
  if (hr && SUCCEEDED(hr)) break;
}
return hr;

奖金问题

SHCIDS_CANONICALONLY 不在乎你比较什么,它可以本地化/定制或不。将本地化数据存储在 pidl 中是个坏主意,因此在大多数情况下并非如此。

其他列通常也不作为本地化数据进行比较。理想情况下,您的比较函数比您的显示代码级别低,并且仅当您必须将字符串返回给调用者时才返回本地化字符串。

项目属性有两个消费者:

  • 外壳视图。这些作为本地化/自定义字符串返回,通常显示为列表视图项。旧的IShellDetails 可用于检索这些纯字符串,以文件夹认为正确的任何方式格式化。

  • 属性系统。由IShellFolder2::GetDetailsEx 作为VARIANT 返回。日期和数字由消费者而非文件夹格式化。

IShellFolder::GetDisplayNameOf 检索“主列”,其中SHGDN_NORMAL 是本地化/自定义名称,SHGDN_FORPARSING 通常与SHCIDS_CANONICALONLY 比较的属性相同。

实现示例

typedef struct { UINT16 cb; WCHAR name[99]; UINT size; bool isFolder } MYITEM;
enum { COL_NAME = 0, COL_SIZE, COLCOUNT, COLCANONICAL = COL_NAME };

MYITEM* GetDataPtr(PCUIDLIST_RELATIVE pidl) { ... }
bool IsFolder(MYITEM*p) { ... }

void GetForDisplay_Name(WCHAR*buf, MYITEM*p)
{
  lstrcpy(buf, p->name);
  SHGetSetSettings(...);
  if (!ss.fShowExtensions && !IsFolder(p)) PathRemoveExtension(buf); // Assuming p->name is a "filenameish" property.
}

void GetForDisplay_Size(WCHAR*buf, MYITEM*p)
{
  // Localized size string returned by GetDetailsOf, not used by CompareIDs
}

HRESULT CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
{
  HRESULT hr = E_FAIL; // Bad column
  MYITEM *p1 = GetDataPtr(pidl1), *p2 = GetDataPtr(pidl2); // A real implementation must validate items

  if (lParam & (SHCIDS_CANONICALONLY | SHCIDS_ALLFIELDS))
  {
    hr = ResultFromShort(StrCmp(p1->name, p2->name));

    if ((ResultFromShort(0) == hr) && (lParam & SHCIDS_ALLFIELDS))
    {
      for (UINT i = 0; i < COLCOUNT; ++i)
      {
        // if (COLCANONICAL == i) continue; // This optimization might be valid, depends on the difference between a items canonical and display name
        hr = CompareIDs(i, pidl1, pidl2);
        if (hr && SUCCEEDED(hr)) break;
      }
    }

    return hr;
  }

  WCHAR b1[99], b2[99];
  switch(LOWORD(lParam))
  {
  case COL_NAME:
    GetForDisplay_Name(b1, p1);
    GetForDisplay_Name(b2, p2);
    return ResultFromShort(StrCmp(b1, b2));
  case COL_SIZE:
    return ResultFromShort(p1->size - p2->size);
  }
  return hr;
}

【讨论】:

    猜你喜欢
    • 2011-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-04
    • 1970-01-01
    • 1970-01-01
    • 2019-06-07
    • 1970-01-01
    相关资源
    最近更新 更多