И внешние проявление внутренних переработок могут быть весьма многообразны и совершенно не очевидны.
Здесь я расскажу об одной особенности функции SHFileOperation, которая проявляется на Висте и выше.
Рассмотрим такую фукнцию (Delphi):
function FileBackup(const fromDir, toDir, fileName: string; IsMove: Boolean): Integer; var fos: TSHFileOpStruct; _fromDir, _toDir: string; begin ForceDirectories(toDir); ZeroMemory(@fos, SizeOf(fos)); if IsMove then fos.wFunc := FO_MOVE else fos.wFunc := FO_COPY; fos.fFlags := FOF_NOERRORUI or FOF_NOCONFIRMATION or FOF_NOCONFIRMMKDIR; _fromDir := AddBackslash(fromDir); _toDir := AddBackslash(toDir); _fromDir := _fromDir + fileName + #0#0; _toDir := _toDir + fileName + #0#0; fos.pFrom := PChar(_fromDir); fos.pTo := PChar(_toDir); Result := SHFileOperation(fos); end;И такой тестовый вызов:
FileBackup('C:\test\folder1\', 'C:\test\folder\\folder2\destination\', 'Project21.dll', False);
Первое, что которая бросается в глаза - это двойной бэкслеш в пути-приемнике. А так вроде бы ничего военного нет. Закроем пока глаза на двойной бэкслеш и запустим тестовое приложение на машине под управлением ОС Windows XP. Код отработал без ошибок и файл был успешно скопирован.
А вот при выполнении того же самого тестового примера на Windows Vista/7 SHFileOperation возвращает ошибку 183 (0xb7). Согласно MSDN это соответствует коду DE_ERROR_MAX.
DE_ERROR_MAX (0xb7) - MAX_PATH was exceeded during the operation
GetLastError() возвращает 0. Да и согласно документации GetLastError не надо использовать для этой функции.
В тоже время, если удалить лишний бэкслеш все работает, как часы - т.е. корень проблемы именно в этом. Стало интересно, каким таким образом два бэкслеша подряд могут вызвать превышение MAX_PATH? Ну что ж, вооружившись Process Monitor-ом, посмотрим, что же происходит на самом деле.
Выяснилось, что происходит следующее: последовательно перебираются все каталоги в пути и проверяется факт их существования - и если каталога нет, он создается.
Такое поведение (последовательный перебор каталогов) - следствие ограничения функции CreateDirectory, т.к. она может создавать только последний каталог в пути.
Так, в случае отсутствия двойного бэкслеша (C:\test\folder\folder2\destination\) система происводит такой перебор:
Если же двойной бэкслеш есть (C:\test\folder\\folder2\destination\), то перебор будет выглядеть таким образом:
Как видно, разбор пути обрывается как раз на том месте, где стоит два бэкслеша подряд. Далее следует вызов CreateDirectoryW("C:\test\folder\folder2\destination"). Функция возвращает False и LastError устанавливается равным 183 (0xb7, ERROR_ALREADY_EXISTS), что и понятно, т.к. такой каталог уже существует.
Похоже происходит следующее: во время перебора директорий встреченный двойной бэкслеш прекращает дальнейший разбор пути-приемника. Система видит, что разбор завершен не до конца и "принудительно" вызывает CreateDirectoryW для конечного пути, которая возвращает ошибку ERROR_ALREADY_EXISTS. После этого осуществляется выход из SHFileOperation функции, которая в качестве результата своей работы возвращает код последней ошибки, т.е. 183 (от последнего вызова CreateDirectoryW).
А это значение случайно совпало с DE_ERROR_MAX, что и может ввести в заблуждение неискушенного программиста. Ведь реально произошла ошибка "Не смог создать каталог-приемник, т.к. он уже существует", а не "Во время операции был превышен MAX_PATH".
Таким образом, данный случай - один из примеров того, как возвращенный код ошибки говорит совсем не о том, что произошло в действительности.
Комментариев нет:
Отправить комментарий