Что такое флаг SHCIDS_ALLFIELDS для IShellFolder.CompareIDs?

Укороченная версия

Что означает флаг SHCIDS_ALLFIELDS для IShellFolder.CompareIDs означает?

Длинная версия

В Windows 95 Microsoft представила оболочку. Вместо того, чтобы предполагать, что компьютер состоит из файлов и папок, он состоит из абстрактного пространства имен элементов.

  • а не пути, начинающиеся в корне диска (например, C:\Documents & Settings\Ian)
  • пути начинаются в корне пространства имен (Desktop)

И чтобы разместить вещи, которые не являются файлами и папками (например, сетевые принтеры, панель управления, мой телефон Android):

  • вы не используете серию имен, разделенных обратной косой чертой (например, C: \ Users \ Ian )
  • вы используете pidls, серию непрозрачных BLOB-объектов (например, Desktop This PC OS (C:) Пользователи Ян)

PIDL — это непрозрачные большие двоичные объекты, каждый большой двоичный объект имеет смысл только для папки, в которой он был создан.

Чтобы расширить (или использовать) пространство имен оболочки, вы реализуете (или вызываете) интерфейс IShellFolder.

Один из методов IShellFolder используется для запроса расширения пространства имен на сравнение со списками идентификаторов (PIDL):

Метод 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.

Итак, вы сравнили два списка удостоверений личности — что бы это ни значило, и мы закончили.

В Windows 2000 добавлены дополнительные флаги параметров сортировки.

Начиная с 5-й версии оболочки, старшие 16 бит LPARAM теперь могут содержать дополнительные флаги для управления тем, как IShellFolder должен обрабатывать сортировку.

С ShObjIdl.idl в. SDK для Windows 8.1:

// 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 — это самая быстрая и эффективная сортировка, которая у нас есть.
  • и это не должно быть логично с точки зрения удобства использования пользовательского интерфейса; это просто должно быть последовательно

Как отметил Рэймонд Чен, это моральный эквивалент Unicode порядковое сравнение.

В заголовочном файле даже отмечается, что мы использовали просто предположение, что столбец 0 был самой быстрой сортировкой. Но теперь мы будем использовать флаг, чтобы сказать "использовать самую быструю доступную сортировку":

До того, как у нас появилось SHCIDS_CANONICALONLY, мы предполагали, что столбец 0 был «эффективным» столбцом сортировки.

Также отмечается, что вы можете игнорировать младшие 16 бит LPARAM (то есть столбец), потому что нам все равно — мы используем наиболее эффективный.

Многое из этого отражено в официальной документации:

SHCIDS_CANONICALONLY

Версия 5.0. При сравнении по имени сравнивайте системные имена, а не отображаемые имена. Когда этот флаг передается, два элемента сравниваются по любым критериям, которые папка Shell определяет как наиболее эффективные, если она реализует согласованную функцию сортировки. Этот флаг полезен при сравнении на равенство или когда результаты сортировки не отображаются пользователю. Этот флаг нельзя комбинировать с другими флагами.

Но с SHCIDS_ALLFIELDS мы начинаем сходить с ума

В заголовочном файле указано, что AllFields можно комбинировать только с CanonicalOnly:

использоваться только в сочетании с SHCIDS_CANONCALONLY или столбцом 0.

Но SDK говорит, что CanonicalOnly должен отображаться отдельно:

Этот флаг нельзя комбинировать с другими флагами.

Так что это?

Мы могли бы решить, что заголовочный файл неверен, что SDK — пушка, и сделать то, что он говорит.

Но что говорит AllFields?

Существует некоторая концепция, которую AllFields пытается запросить, но она скрыта за документацией.

Сравните всю информацию, содержащуюся в структуре ITEMIDLIST, а не только отображаемые имена.

ItemIDList не содержит отображаемого имени, он содержит ItemIDList. Они пытаются сказать, что я должен только смотреть содержимое большого двоичного объекта pidl?

  • Например, если два элемента являются файлами, папка должна сравнить их имена, размеры, время файла, атрибуты и любую другую информацию в структурах.

В какой ситуации две ссылки на *один и тот же** файл могут иметь разные имена, размеры, время файла, атрибуты и т. д.?

Примеры SDK делают что-то другое

Пример Windows SDK Расширение оболочки поставщика данных Explorer (github), похоже, действует так, как если бы флаги CanonicalOnly и AllFields отображались бы вместе:

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 является быстрым столбцом. Это могло быть связано с тем, что авторы API оболочки Windows предполагали, что ItemID PIDL всегда будет содержать имя внутри непрозрачного большого двоичного объекта pidl.

Это подкрепляется тем фактом, что структура оболочки STRRET позволяет указать строку внутри вашего pidl.

Дополнительное чтение: странная структура STRRET

И поэтому в какой-то момент они добавили экспресс-флаг, который гласит:

  • мы не заботимся о локализации, специфических для локали правилах языковой сортировки и алгоритмах нормализации юникода.
  • просто отсортируйте их, потому что нам нужно найти дубликаты и проверить на равенство

И это имеет смысл для флага canonical:

  • просто скажите мне, если два списка IDL указывают на один и тот же объект

Но что тогда означает пример с SDK, когда говорят об опции Все поля:

Если нас попросили провести сравнение всех полей, протестируйте любые другие поля, которые могут отличаться в элементе, который имеет один и тот же идентификатор. Например:

  • если элемент представляет файл, идентификатор может быть просто именем файла
  • но другими полями, содержащимися в списке idlist, могут быть размер файла и дата изменения файла, и они могут меняться со временем.

Если два PIDL представляют один и тот же файл, какой смысл сравнивать их размер, дату и т. д.? Я уже говорил вам, что это один и тот же файл, что вы хотите от меня с флагом Все поля? Почему я не могу просто выполнить бинарное сравнение капель? Почему оболочка не работает? Что делает CompareID?

MemCmp(pidl1, pidl2)

нет?

  • Будет ли SHCIDS_ALLFIELDS только отображаться вместе с SHCIDS_CANONICALONLY?
  • SHCIDS_ALLFIELDS никогда не появится рядом с SHCIDS_CANONICALONLY?
  • Может ли SHCIDS_ALLFIELDS появляться как с SHCIDS_CANONICALONLY, так и без него?
  • Что означает SHCIDS_ALLFIELDS с SHCIDS_CANONICALONLY?
  • Что означает SHCIDS_ALLFIELDS без SHCIDS_CANONICALONLY?

Что он хочет, чтобы я сделал, если SHCIDS_ALLFIELDS пройдено? Должен ли я обратиться к основному хранилищу данных, чтобы запросить все поля, о которых я знаю?

Используются ли CompareID для сравнения идентификаторов или для сравнения объектов?

Я задавался вопросом, было ли назначение CompareID в том, чтобы абсолютно не обращаться к основному хранилищу данных (например, жесткий диск, телефон через USB, Mapi), а сравнивать только на основе того, что у вас есть под рукой< /strong> в файле pidl.

Это имеет смысл по двум причинам:

  • это быстрее; многие пространства имен содержат некоторое количество метаданных в своих больших двоичных объектах PIDL - нет необходимости возвращаться к диску/Интернету
  • хотя pidl могут ссылаться на один и тот же объект, возможно, их метаданные устарели.
  • SHCIDS_CANONICALONLY позволяет вызывающей стороне понять, что два pidl — это одно и то же.
  • но отдельный вызов с SHCIDS_CANONICALONLY | SHCIDS_ALLFIELDS может сказать нам, что дополнительные метаданные могут быть устаревшими (хотя я понятия не имею, какая польза от этой информации для вызывающего абонента)

Итак, возможно, SHCIDS_CANONICALONLY означает:

  • пожалуйста, ограничьтесь pidl - не прикасайтесь к диску, чтобы выполнить сравнение
  • а его отсутствие означает: "Да, вы можете нажать на жесткий диск, если вам действительно нужно"

Так ли это?

  • Если SHCIDS_CANONICALONLY означает: "Не смотрите ни на что, кроме того, что в pidl, и скажите мне, являются ли эти две вещи одним и тем же объектом"
  • Тогда что получает SHCIDS_ALLFIELDS?
  • Когда они будут другими?
  • Что спрашивает оболочка?

Бонусный вопрос

  • Если SHCIDS_CANONICALONLY означает выполнение наиболее эффективной сортировки,
  • означает ли отсутствие SHCIDS_CANONICALONLY возможность сортировки на основе локализации и настройки имени?
  • означает ли отсутствие SHCIDS_CANONICALONLY, что обязательна сортировка на основе локализации и настройки имени?

Что означает "сортировать" по спискам itemID?

Пример SDK выполняет switch на основе каждого столбца и ищет значения для каждого столбца. Если это означает, что мне нужно загрузить видео из сети, чтобы загрузить частоту дискретизации звука?

  • Я сравниваю PIDL
  • или я сравниваю объекты, на которые указывают эти pidls?

person Ian Boyd    schedule 28.05.2018    source источник
comment
Некоторый вклад из старого блога от парня из Microsoft (в то время), который, вероятно, немного объясняет: archives.miloush.net/michkap/archive/2009/09/11/9894019.html   -  person Simon Mourier    schedule 29.05.2018
comment
@SimonMourier Я уже нашел этот блог; Я не понял, что это был Майкл Каплан. Я помню его старый пост об этом. разорвать   -  person Ian Boyd    schedule 29.05.2018


Ответы (1)


Пример SDK в основном правильный (зависит от содержимого pidl). if (lParam & (SHCIDS_CANONICALONLY | SHCIDS_ALLFIELDS)), очевидно, то же самое, что и if ((lParam & SHCIDS_CANONICALONLY) || (lParam & SHCIDS_ALLFIELDS)), но не говорит нам, можно ли их комбинировать, и ответ на этот вопрос: я не знаю. Я не понимаю, почему бы и нет.

Только члены команды оболочки Microsoft знают истинный ответ, но мы можем строить догадки.

В Win95 в основном было 4 стандартных поля. Вы можете увидеть их в документации к более старой IShellDetails:

Папки файловой системы имеют большой стандартный набор информационных полей. Первые четыре поля являются стандартными для всех папок файловой системы.

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

Папки файловой системы могут поддерживать ряд дополнительных полей. Однако это не обязательно, и индексы столбцов, назначенные этим полям, могут различаться.

Каждая виртуальная папка имеет свой уникальный набор информационных полей. Обычно отображаемое имя элемента находится в нулевом столбце, но порядок и содержимое доступных полей зависят от реализации конкретного объекта папки.

Затем в Windows 2000 все изменилось, когда появилась поддержка расширения оболочки обработчики столбцов. Это было основой для системы свойств, обеспечивающей поддержку стека Vistas и т. д., а индекс столбца - это сопоставление бедняков с / из PROPERTYKEY для свойств элементов (PROPERTYKEY тогда было известно как SHCOLUMNID).

SHCIDS_CANONICALONLY:

Важным моментом здесь является КАНОНИЧНОСТЬ.

MSDN говорит

При сравнении по имени сравнивайте системные имена, а не отображаемые имена.

Оболочка не согласуется с использованием термина отображаемое имя, но на самом деле это означает, что сравнивайте имя синтаксического анализа, а не имя, которое вы видите в проводнике.

Например, представление папки может содержать файлы «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;
}
person Anders    schedule 29.05.2018