Как я могу получить РУЧКУ к директории, содержащей файл, из файла РУЧКА? - PullRequest
0 голосов
/ 11 июня 2019

Учитывая HANDLE к файлу (например, C:\\FolderA\\file.txt), я хочу функцию, которая будет возвращать HANDLE в содержащий каталог (в предыдущем примере это HANDLE для C:\\FolderA).Например:

HANDLE hFile = CreateFileA(
                  "C:\\FolderA\\file.txt",
                  GENERIC_READ,
                  FILE_SHARE_READ,
                  NULL,
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL,
                  NULL);
HANDLE hDirectory = somefunc(hFile);

Возможная реализация для someFunc:

HANDLE someFunc(HANDLE h)
{
    char *path = getPath(h);             // "C:\\FolderA\\file.txt"
    char *parent = getParentPath(path);  // "C:\\FolderA"
    HANDLE hFile = CreateFileA(
              parent,
              GENERIC_READ,
              FILE_SHARE_READ,
              NULL,
              OPEN_EXISTING,
              FILE_ATTRIBUTE_NORMAL,
              NULL);
    free(parent);
    free(path);
    return hFile;
}

Но есть ли способ реализовать someFunc без getParentPath или не заставляя его смотреть на строкуи удалить все после последнего разделителя каталогов (потому что это ужасно с точки зрения производительности)?

1 Ответ

3 голосов
/ 11 июня 2019

Я не знаю, что такое getParentPath. Я предполагаю, что это функция, которая выполняет поиск обратной косой черты в строке и использует ее для удаления спецификации файла. Вам не нужно определять такую ​​функцию самостоятельно; Windows уже предоставит вам такую ​​возможность - PathCchRemoveFileSpec. (Обратите внимание, что предполагается, что указанный путь на самом деле содержит имя файла для удаления. Если путь не содержит имени файла, он удалит конечное имя каталога. Существуют и другие функции, которые можно использовать для проверки того, содержит ли путь файл спецификация.)

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

Помимо Windows API, есть и другие способы сделать то же самое. Если вы нацелены на C ++ 17, есть класс filesystem::path. Boost предоставляет нечто подобное. Или вы можете написать это самостоятельно с помощью функции-члена find_last_of класса std::string, если вам абсолютно необходимо. (Но предпочитайте не изобретать колесо. Существует множество крайних случаев, когда речь идет о манипулировании траекториями, о которых вы, вероятно, не подумаете, и что ваши испытания, вероятно, не выявят.)

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

Но реализация, вероятно, даже не будет такой наивной. Вы можете оптимизировать его, начав поиск с end строки пути, сократив количество символов, через которое вы должны проходить, и вы можете полностью избежать любого типа копирования памяти, если вам разрешено манипулировать исходной строкой. Со строкой в ​​стиле C вы просто заменяете конечный разделитель пути (тот, который разграничивает начало спецификации пути) на символ NUL (\0). Со строкой в ​​стиле C ++ вы просто вызываете функцию-член erase.

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

Получив путь к каталогу, вы можете получить HANDLE к нему, вызвав функцию CreateFile с флагом FILE_FLAG_BACKUP_SEMANTICS. (Это необходимо передать этот флаг, если вы хотите получить дескриптор в каталог.


Я измерил, что это медленно и я ищу более быстрый путь.

Ваши измерения неверны. Либо вы допустили распространенную ошибку, сравнивая отладочную сборку, в которой стандартные функции библиотеки (например, std::string) не оптимизированы, и / или реальным узким местом производительности является файловый ввод-вывод. CreateFile - это , а не - быстрая функция при любом напряжении воображения. Я почти гарантирую, что это будет ваша горячая точка.


Обратите внимание, что если у вас еще нет пути, будет просто получить путь от HANDLE к файлу. Как было указано в комментариях, в Windows Vista и более поздних версиях. вам просто нужно вызвать функцию GetFinalPathNameByHandle. Более подробная информация доступна в этой статье на MSDN, включая пример кода и альтернативу для использования в версиях Windows более низкого уровня.

Как уже упоминалось в комментариях к вопросу, вы можете оптимизировать это дальше, выделив в стеке буфер длиной MAX_PATH (или, возможно, даже больше).Это компилируется в одну инструкцию для настройки указателя стека, поэтому она также не будет узким местом в производительности.(Хорошо, я солгал: вам на самом деле понадобятся две инструкции - одна для создания пространства в стеке, а другая для освобождения выделенного пространства в стеке. Все еще не проблема с производительностью.) Таким образом, выдаже не нужно делать никакого динамического выделения памяти.

Обратите внимание, что для максимальной надежности, особенно в Windows 10, вы хотите обрабатывать случай, когда путь длиннее MAX_PATH.В таких случаях ваш выделенный стеком буфер будет слишком маленьким, и функция, которую вы вызываете для его заполнения, выдаст ошибку.Обработайте эту ошибку и выделите больший буфер в свободном хранилище.Это будет медленнее, но это крайний случай и, вероятно, не тот, который стоит оптимизировать.В 99% общем случае будет использоваться выделенный стеком буфер.

Кроме того, eryksun указывает (в комментариях к этому ответу), что, хотя это удобно, GetFinalPathNameByHandle требует нескольких системных вызовов для сопоставления объекта файламежду пространствами имен NT и DOS и нормализовать путь.Я не разбирал эту функцию, поэтому не могу подтвердить его заявления, но у меня нет оснований сомневаться в них.При нормальных обстоятельствах вы не будете беспокоиться о таких накладных расходах или возможных затратах на производительность, но, поскольку это, по-видимому, является серьезной проблемой для вашего приложения, вы можете использовать альтернативное предложение eryksun о вызове GetFileInformationByHandleEx изапрашивая класс FileNameInfo.GetFileInformationByHandleEx - это универсальная многоцелевая функция, которая может извлекать все виды информации о файле, включая путь.Его реализация проще, вызывая непосредственно нативную функцию NtQueryInformationFile.Я бы подумал, что GetFinalPathNameByHandle - это просто оболочка в пользовательском режиме, предоставляющая именно эту услугу, но исследования eryksun показывают, что она выполняет дополнительную работу, которую вы могли бы избежать, если это действительно горячая точка производительности.Я должен немного уточнить это, отметив, что GetFileInformationByHandleEx, для извлечения FileNameInfo, придется создать пакет запроса ввода-вывода (IRP) и обратиться к базовому драйверу устройства.Это недешевая операция, поэтому я не уверен, что дополнительные издержки нормализации пути действительно будут иметь значение.Но в этом случае нет никакого реального вреда в использовании подхода GetFileInformationByHandleEx, поскольку это документированная функция.


Если вы написали код, как описано, но все еще испытываете измеримые проблемы с производительностью, тогдапожалуйста, опубликуйте этот код, чтобы кто-то мог проверить его и помочь оптимизировать.Сайт Code Review Stack Exchange - отличное место для получения такой помощи по рабочему коду.Не стесняйтесь оставлять мне ссылку на такой вопрос в комментарии под этим ответом, чтобы я не пропустил его.

Что бы вы ни делали, пожалуйста прекратите звонить в ANSIверсии функций Windows API (заканчивающиеся суффиксом A). Требуются версии с широкими символами (Unicode).Они заканчиваются суффиксом W и работают со строками, состоящими из WCHAR (== wchar_t) символов.Помимо того факта, что версии ANSI уже десятилетиями устарели, потому что они не обеспечивают поддержку Unicode (ни для одного приложения, написанного после 2000 года, не обязательно поддерживать Unicode-символы в путях), поскольку вы заботитесь о производительности,Вы должны знать о том, что все функции API с * * * * * * * - это просто заглушки, которые преобразуют переданную строку ANSI в строку Unicode, а затем делегируют версии W.Если функция возвращает строку, второе преобразование также должно быть выполнено с помощью версии A с суффиксом, поскольку все нативные API работают со строками Unicode.Производительность не является реальной причиной, по которой вам следует избегать вызова функций ANSI, но, возможно, вы найдете ее более убедительной.

Там может быть способом сделать то, что вы хотите (сопоставить файловый объект через HANDLE с содержащим его каталогом), но это потребует недокументированного использования собственного API NT. Я ничего не вижу в документированных функциях, которые позволили бы вам получить эту информацию. Это, конечно, не доступно через функцию GetFileInformationByHandleEx. Что бы там ни было, API файловой системы пользовательского режима почти полностью основан на пути. Предположительно, он отслеживается внутренне, но даже документированные функции нативного API NT, которые принимают корневой каталог HANDLE (например, NtDeleteFile через структуру OBJECT_ATTRIBUTES), позволяют этому полю иметь значение NULL, в в этом случае используется полная строка пути.

Как всегда, если бы вы предоставили более подробную информацию о более широкой картине, мы, вероятно, могли бы предложить более подходящее решение. Это то, к чему стремились комментаторы, когда упоминали о проблеме XY. Да, люди ставят под сомнение ваши мотивы, потому что именно так мы предоставляем наиболее подходящую помощь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...