Данной заметки не должно было быть, но мы живем не в идеальном мире.Есть много пользователей, что предпочли стандартному проводнику Windows альтернативу и наверное еще больше пользователей архиватора 7-zip. Имеет смысл поделиться с ними, подумал я и вот мы здесь. Опустим лишние подробности, случилось так, что я заметил в используемом файловом менеджере значительную задержку появления окна контекстного меню при выборе большого количества файлов. Даже учитывая что его код меню написан без учета современных рекомендаций MS, лаг был подозрительно большим. И хоть никого не вдохновляет идея потратить личное время на неоплачиваемую работу с непредсказуемым результатом, я решил немного разобраться в причинах. Что делает обычный эникейщик в подобной ситуации? Конечно же берет в руки первый подвернувшийся профайлер.  Итак, локальным виновником оказался метод IContextMenu::QueryContextMenu, а по факту все время съедает... 7-zip.dllЭта библиотека является расширением оболочки и отвечает за добавление команд архиватора 7-zip в контекстное меню. Не то что ожидаешь увидеть по итогу, но стоит проверить. Отключаем показ меню архиватора в настройках программы. И убеждаемся, что собака порылась именно тут.Ну раз такое дело, почему бы не проверить WinRar? Ничего похожего не наблюдается, лаг отсутствует... Для более чистого эксперимента закинем тестовую директорию на RAMDISK, а файловый менеджер заменим на что-то более очевидное, например Total Commander. Сразу стоит отметить, что все изложенное не затрагивает проводник Windows, там своя внутренняя API магия, которая не ограничивается двухступенчатой инициализацией (сначала для первых 16 объектов, чтобы быстро отобразить меню, а после выбора команды, Invoke уже для полного набора). Все остальные файловые менеджеры почти поголовно подвержены проблеме, как впрочем и практически любой другой софт. Что ж, попробуем вызвать меню в TC для файлов в нашей папке. Для наглядности сделаем количество побольше.  Тест на 20k файлах выявил замедление более чем в х20 раз, а именно 3 секунды без 7-zip и более минуты! с ним. Вообще, по логике вещей, расширения оболочки не должны вмешиваться в процесс создания меню настолько инвазивно. Как обычно приложения создают контекстные меню.Приведу упрощенное изложение стандартного алгоритма. Небольшая отсылка к MSDNДля большинства не секрет, что ОС для внутренних операций с объектами файловой системы использует не текстовые представления путей к которым мы привыкли, а некие альтернативные идентификаторы которые могут определять и виртуальные объекты. Идентификатор элемента (функционально эквивалентен имени файла\папки) фактически является структурой SHITEMID. Список таких идентификаторов определяется структурой ITEMIDLIST. Для API оболочки объекты пространства имен обычно идентифицируются указателем как раз на эту самую структуру - PIDL (Pointer to an ITEMIDLIST). В общем, изначально, тем или иным образом приложение (например ФМ) должно преобразовать пути в список идентификаторов. Независимо от API (ILCreateFromPath, SHParseDisplayName итп) в конечном счете будет вызван метод интерфейса IShellFolder рабочего стола Desktop.IShellFolder::ParseDisplayName, внутри которого для стандартных путей идентификаторы будут собраны из WIN32_FIND_DATAW, тут (на мой взгляд) можно было бы немного оптимизировать, но разработчикам виднее.На Win10+ этот метод вызывается из windows.storage.dll (CFSFolder::ParseDisplayName), как и ряд методов ниже. Затем обычно следует цепочка вызовов (упрощенный вариант): В GetUIObjectOf мы передаем массив указателей на наши относительные идентификаторы и получаем на выходе интерфейс с дефолтным меню. При, казалось бы, таком простом действии как создание меню, в действительности "за кулисами" происходит просто огромный объем работы.Описание всего этого "цирка с конями" потянет на цикл отдельных статей, поэтому сфокусируемся на области используемой 7-zip. Мы уже выяснили, что инициатор проблемной цепочки это QueryContextMenu.Внутри CDefFolderMenu::QueryContextMenu происходит много разногоВ зависимости от флагов поведение различается, однако базово, к пунктам дефолтного меню добавляются новые с объединением.По типам объектов в списке сканируются соответствующие ветки реестра на наличие статических и динамических обработчиков. Для динамики вызываются: CDefFolderMenu::_AddHandlersToDCA HDXA_QueryContextMenu Условный CDefFolderMenu: При сборе инфы реестра заполняются соответствующие пулы данных, в основном это: Ассоциативный массив IAssociationArray (упорядоченный список путей реестра с информацией о типе, обработчиках, псевдонимах команд, иконках итп) Три динамических массива структур - для статических (hdsaStatics), динамических (HDXA) обработчиков и массив SEARCHINFO (hdsaCustomInfo). Впоследствии при вызове метода CDefFolderMenu::InvokeCommand, если команда (verb) не каноническая (из списка дефолтного меню) и не статика, функция HDXA_LetHandlerProcessCommandEx начнет перебор структур CONTEXTMENUINFO из массива HDXA и будет пробовать вызвать метод IContextMenu::InvokeCommand по указателю в начале структуры. И тут, либо какой-то метод успешно отработает, либо, если массив кончился, CDefFolderMenu::InvokeCommand вернет E_INVALIDARG На этом этапе нужно подгрузить в адресное пространство процесса библиотеки всех востребованных динамических расширений меню, а в случае 7-zip, это практически всегда. И тут мы подходим к инициализации нашего обработчика. Стоит держать в уме, что такой обработчик меню для программиста это зона высокой ответственности, ведь данный код будет выполняться при каждом вызове контекстного меню для объектов ФС и писаться он должен с максимальной оптимизацией. Проверим сначала на единственном файле.Создаем пустую папку и в ней пустой текстовый файл, аттачимся к TC и ставим bp 7_zip!DllGetClassObjectЗапускаем выполнение процесса и вызываем контекстное меню файла. После остановки делаем трассировку. На данный момент мы увидим лишь портянку со змейкой вызовов много раз повторяющегося кода 7_zip!DllUnregisterServer+offset Суммарный результат (не полной) трассировки: У меня конечно есть нелюбовь к построению причинно-следственных связей, но даже мне кажется, что в текущем контексте, вызовов для модуля достаточно много. Конечно можно уже пробовать курить исходники и делать какие-то выводы, но мы пока не будем, хотя сырки все равно нужны.  Чтобы было красиво и наглядно нам не хватает символов для 7-zip.dll. Ну, это дело пяти минут. Забираем с оффсайта архив, в CPP/Build.mak дописываем в CFLAGS /Zi, в LFLAGS /DEBUG и проставим -Od в CFLAGS_OX Выполняем в командной строке студии (x64 Native) cd /d X:\7z2201-src\CPP\7zip nmake /f makefile CPU=AMD64  (Если сильно спешите, скомпилируйте только dll) Теперь у нас есть отладочные символы. Добавим путь к ним и сыркам для отладчика.Также подменим библиотеку в папке архиватора. Повторим трассировку и посмотрим топ.  С символами все стало достаточно наглядно. Посмотрим стек на самой часто вызываемой функции.  Тк отладка у нас с исходным кодом, быстро пробежавшись по строчкам, понимаем что к чему. Все эти аллокации происходят при парсинге файла локализации (src\CPP\Common\Lang.cpp). Конечно странное решение.Почему бы не брать строки по указателям из закешированного шаблона?Зачем каждый раз самоотверженно парсить один и тот же файл локализации (16Кб для ru) при каждом нажатии ПКМ? Медленные строковые операции стоит минимизировать прежде всего. Хотя что я докопался, это даже "не те дроиды" которых мы ищем... Если указать профайлеру путь к полученным символам, мы увидим больше подробностей и там, но ничего принципиально нового, можно сразу открывать отладчик. Перейдем в TC в тестовую директорию, где файлов побольше.После срабатывания bp 7_zip!DllGetClassObject поставим точку остановки на SHELL32!DragQueryFileAorW и глянем стек.На третьей остановке мы уже попадаем в цикл функции и можем отследить всю проблемную цепочку. 🡻 🡻 🡻 🡻 🡻 SHELL32!DragQueryFileW 🡻  Коротко по сути - мы кликаем ПКМ на файлах (например хотим посмотреть свойства, или удалить), TC инициализирует процесс создания меню и подгружает расширения, дело доходит до 7-zip...И тут 7-zip такой: - Минуточку! Есть мнение, что вы недостаточно страдаете от оверинжиниринга реализации контекстного меню MS и я буду в буфер получать строки файловых путей для каждого выбранного объекта. Мне ведь надо проверить, что вы там выбрали и соответствующие пункты меню показать, чтобы все "по красоте". - Кто вам сделал удобное меню? - Кто молодец? А?... вот то-то же! Безусловно стояла задача сделать пользователю "удобно", но стоило бы потестить и конечную реализацию удобняшек.  А по факту мы имеем много бесполезной работы съедающей время. Также в CDrop::QueryFileName вместо того чтобы один раз выделить буфер достаточного размера, вся цепочка с DragQueryFileW для каждого объекта вызывается дважды, умножая и мою печаль.  В общем, стоит все таки иногда прислушиваться к рекомендациям MS, как это делает WinRar и не пытаться объять "необъятное".  Причины найдены. Делаем выводы. Ситуация неоднозначная, поскольку с одной стороны это проблема подавляющего большинства софта, мало кто следует гайдлайнам и мы имеем медленные меню. Но с другой, 7-zip забивший на рекомендации, вносит дополнительные задержки.  Как и где поправить код для 7zip (тем кто захочет это сделать), надеюсь понятно.Сам я еще тот гуманитарий и поэтому оставлю поиск и разбор прочих недоработок в сырках архиватора тем кто разбирается в вопросе и кому это интересно. Тестирование производилось на последнем ванильном билде 7-zip  v22.01 от 2022-07-15 Windows 10 Pro 21H2 19044.1320 x64, CPU 3800МГц При желании быстро воспроизвести похожий результат (задумайтесь о жизни, если оно возникло), можете воспользоваться командным файлом для создания тестовой директории. При запуске создаст рядом с собой папку TEST с 20k текстовыми файлами нулевого размера.Затем открываете любой ФМ кроме штатного проводника, Ctrl+A и ПКМ. Многие пользователи TC скорее всего не используют архиватор "классическим" образом, но это совершенно не важно в рамках рассматриваемой проблемы. Можно было бы обойтись без отладки и сразу смотреть исходники, но часто ситуация бывает не столь однозначна, как выглядит на первый взгляд и в попытке сэкономить десяток минут, можно потратить больше, или что-то упустить. Кто-то может сказать, что все это ерунда и можно в общем-то игнорировать при современных мощностях железа, а с действительно большими количествами файлов мало кто работает.  Это ваш выбор, пусть этой зимой вам будет чуть теплее, чем остальным, просто чаще вызывайте меню  😜   Мораль проста - не все динамические расширения одинаково полезны, а если вы их пишете, надеюсь, умеете "в оптимизацию".Ну и убирайте всё неиспользуемое из меню (ShellExView в помощь).