IShellFolder.CompareIDs 的 SHCIDS_ALLFIELDS 标志是什么?

What is the SHCIDS_ALLFIELDS flag of IShellFolder.CompareIDs trying to be?

精简版

IShellFolder.CompareIDsSHCIDS_ALLFIELDS标志是什么意思?

长版

在Windows95中,微软引入了shell。它不是假设计算机由文件和文件夹组成,而是由项目的抽象 命名空间 组成。

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

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

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

IShellFolder 的方法之一用于请求名称空间扩展以 compare 到 ID 列表 (PIDLs):

IShellFolder::CompareIDs method

Determines the relative order of two file objects or folders, given their item identifier lists.

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 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.)

注意这里的要点:

正如雷蒙德陈指出的那样,它是the moral equivalent of a Unicode ordinal comparison

header 文件甚至指出我们使用 假设第 0 列是 "fastest" 排序.但是现在我们将使用一个标志来 say "use the fastest sort available":

Before we had SHCIDS_CANONICALONLY we assumed column 0 was the "efficient" sort column.

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

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

SHCIDS_CANONICALONLY

Version 5.0. When comparing by name, compare the system names but not the display names. When this flag is passed, the two items are compared by whatever criteria the Shell folder determines are most efficient, as long as it implements a consistent sort function. This flag is useful when comparing for equality or when the results of the sort are not displayed to the user. This flag cannot be combined with other flags.

但是 SHCIDS_ALLFIELDS 我们开始 运行 关闭 rails

header 文件指出 AllFields 只能与 CanonicalOnly:

组合

only be used in conjunction with SHCIDS_CANONCALONLY or column 0.

但是SDK说CanonicalOnly必须单独出现:

This flag cannot be combined with other flags.

那是哪一个?

我们可以确定 header 文件是错误的,SDK 是大炮,然后按照它说的去做。

但是 AllFields 在说什么?

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

Compare all the information contained in the ITEMIDLIST structure, not just the display names.

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
}

所以我们有完全冲突的文档、headers 和样本:

SHCIDS_ALLFIELDS

它想问什么

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

This is reinforced by the fact that the shell STRRET structure lets you point to a string inside your pidl.

Bonus Reading: The kooky STRRET structure

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

这对 canonical 标志有意义

但是当他们谈论 所有字段 选项时,SDK 示例意味着什么:

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.

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

MemCmp(pidl1, pidl2)

不是吗?

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

CompareIDs是用来比较ID的,还是用来比较objects的?

我想知道 CompareIDs 的目的是否是 绝对不 访问底层数据存储(例如硬盘,phone over USB,Mapi),并且只比较根据你在 pidl.on-hand 中的内容。

这有两个原因:

所以也许 SHCIDS_CANONICALONLY 意味着:

是这样吗?

奖金问题

itemID 列表 "sort" 是什么意思?

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

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

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

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

File system folders have a large standard set of information fields. The first four fields are standard for all file system folders.

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

File system folders may support a number of additional fields. However, they are not required to do so and the column indexes assigned to these fields may vary.

Each virtual folder has its own unique set of information fields. Typically, the item's display name is in column zero, but the order and content of the available fields depend on the implementation of the particular folder object.

然后在 Windows 2000 中,添加了对 shell 扩展 column handlers 的支持后,情况发生了变化。这是支持 Vistas 堆叠支持等的 属性 系统的基础,列索引是穷人映射 to/from 项目属性的 PROPERTYKEYPROPERTYKEY 已知一个 SHCOLUMNID 当时)。

SHCIDS_CANONICALONLY:

这里的重要部分是 CANONICAL。

MSDN 说

When comparing by name, compare the system names but not the display names.

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

例如,文件夹视图可能包含 "foo" 和 "foo" 文件,但实际上它们是 "foo.jpg" 和 "foo.png",但 "hide file extensions" 功能隐藏了真实姓名。

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不管你比的是什么,可以是localized/customized也可以不是。在 pidl 中存储本地化数据是个坏主意,因此在大多数情况下并非如此。

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

项目属性有两个消费者:

  • shell 视图。这些被 return 编辑为 localized/customized 字符串,通常显示为列表视图项目。旧的 IShellDetails 可用于以文件夹认为正确的任何方式格式化为纯字符串来检索它们。

  • 属性系统。 IShellFolder2::GetDetailsEx 作为 VARIANT 返回。日期和数字由用户而不是文件夹格式化。

IShellFolder::GetDisplayNameOf 检索 "main column",其中 SHGDN_NORMAL 是 localized/customized 名称,而 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;
}