Пути — это уникальный компонент языка C++, который до C++17 на самом деле не реализовывался в кросс-платформенной, стандартизированной части языка. Операционная система Windows имеет собственный подход к работе с путями к файлам и каталогам, который сильно отличается от операционных систем UNIX. В этом руководстве рассматриваются функциональные возможности и свойства путей Windows, а также то, как их можно использовать в языке C++.

Путь — это строка символов, представляющая расположение файла в файловой системе. Файловая система использует определенные кодировки для представления путей. Исторически сложилось так, что пути в окнах представлялись только в кодировках из кодовых страниц ANSI. В современных системах Windows ANSI означает «8-битные кодовые страницы», так как в них присутствуют нестандартные кодировки, такие как UTF-8. Windows API внутри используют кодовые страницы Unicode, чаще всего UTF-16. В программе C++ для Windows вы можете указать, какую кодовую страницу вы используете, используя функцию AreFileApisANSI из windows.h.

Для упрощения подумайте о следующих значениях почти для всех случаев:

  • Кодовая страница ANSI -› может использовать пути UTF-8
  • Кодовая страница OEM -> может использовать пути UTF-16

API-интерфейсы Windows предоставляют отдельные функции для путей UTF-8 и UTF-16. Кроме того, для каждого из них задействован другой тип персонажа.

Типы персонажей

В Windows пути могут быть в кодировке UTF-8 или UTF-16. Когда они закодированы в UTF-8, они используют тип char. Когда они закодированы в UTF-16, они используют тип wchar_t. Какая кодировка должна использоваться вашей программой или приложением, зависит от целевой версии Windows, а также от конкретных потребностей приложения. Если требуется поддержка обоих, есть несколько доступных вариантов. Первый — это письменный подход Microsoft под названием TCHAR из заголовка tchar.h. Этот тип определяется как char или wchar_t в зависимости от определения макроса UNICODE. Для целей этой статьи мы будем использовать наш собственный тип символов кросс-кодирования, называемый echar_t. Кроме того, мы будем основывать базовый тип echar_t на том, определен ли _PATH_UTF16.

Следовательно, определение echar_t таково:

Большинство функций, работающих с char или wchar_t, уже будут включены, если включен <windows.h>. Однако обычно они существуют в <string.h> и <wchar.h> соответственно. Эти два типа символов также используют разный литеральный синтаксис. Литералы широких символов принимают форму const wchar_t* foo = L"foo";. Макрос, разрешающий литералы кросс-кодирования, можно записать так:

Эти два определения позволят нам писать такие операторы, как const echar_t* str1 = ECHAR("abcd");. Важной частью пути является разделитель между каталогами. В операционной системе Windows разделителем является обратная косая черта, \. В системах UNIX это косая черта, /. Для удобства мы можем определить специальный макрос для этого разделителя.

В Windows абсолютные пути, указывающие абсолютное местоположение файла от начальной точки корня, содержат буквы дисков. Буква диска — это буква в начале пути в формате <letter>:, указывающая, на каком аппаратном диске находится путь. В зависимости от установки и конфигурации системы Windows эта буква диска может быть любой A, B, C, D или более букв. Когда в файловой системе создаются разделы, это может привести к назначению большего количества букв диска и, следовательно, к созданию большего количества дисков. Однако чаще всего используется диск C:. Однако такой путь, как \foo.txt, также является абсолютным путем текущего диска. Как правило, путь должен начинаться либо с C:\, либо с \, чтобы считаться абсолютным путем.

Напротив, абсолютные пути UNIX начинаются с корневого каталога /, например /usr/bin/python.

Теперь, когда основные типы символов для путей определены, следующим шагом является их обертка с помощью класса, который может обрабатывать их как объекты.

Объекты пути

Чтобы сделать обработку имен путей более эффективной и лаконичной, лучше обрабатывать их как объекты с помощью методов класса C++. Реализуемый нами класс WPath будет неизменяемой строкой, представляющей путь к файловой системе с echar_t символами. Однако, чтобы упростить методы класса, нам нужно установить, как каждая операция будет по-разному обрабатывать символы UTF-8 или UTF-16. Для начала нам понадобится функция копирования строки, которая может работать для обоих. Кроме того, для обеспечения наиболее фундаментальной строковой функциональности нам также потребуется аналогичное определение для функции длины. Это можно сделать следующим образом:

Функция wcscpy из <wchar.h> действует так же, как strcpy из string.h, но для расширенных символов (16-битные символы). Другое различие между строками UTF-8 и UTF-16 заключается в размере нулевого символа. В UTF-8 любой нулевой символ из ASCII равен одному байту. Но в UTF-16 это два байта. Мы можем, как и в других примерах, использовать условное определение для управления нулевым размером для обеих кодировок.

Исходное определение нашего класса WPath должно иметь конструктор, который принимает в качестве аргумента литеральную или иным образом заканчивающуюся нулем строку const echar_t*. Чтобы не было утечки памяти, давайте еще добавим деструктор.

На данный момент у нас есть путь Windows, представленный в нашем классе, но мы не можем сделать с ним ничего, связанного с тем, что он является файлом. Первая операция с файловой системой, которую мы можем реализовать для WPath, это exists(). Этот метод сообщит нам, существует ли путь, содержащийся в экземпляре класса, или нет. Эту задачу можно выполнить с помощью функций GetFileAttributesA и GetFileAttributesW, используемых для UTF-8 и UTF-16 соответственно. Как и в предыдущих примерах, их можно условно определить в одном макросе.

Эти две функции возвращают DWORD, особый тип Windows, представляющий собой 32-битное целое число. Он содержит информацию о файле, который существует по указанному пути. Это не удается, если путь не существует, и это основной способ проверки существования файла. К счастью, Windows предопределяет макрос для представления недопустимого файла как DWORD. Такой метод просто выглядит для нашего класса следующим образом:

Теперь, когда можно проверить существование WPath, необходим способ создания и объединения новых путей. Поскольку WPath является неизменяемой строкой, операцию объединения можно выполнить с помощью операторной функции, которая принимает два экземпляра WPath и возвращает новый экземпляр. Это может быть удобно реализовано в методе operator/, поскольку он позволяет соединять пути с помощью синтаксиса WPath c = a / b. Для этого нам также понадобится конструктор, который принимает только size_t и создает пустой путь, по существу, резервируя размер.

Информация о пути

Теперь у нас есть объект пути, который можно использовать для создания путей. Далее мы хотим иметь возможность получить больше информации о конкретном пути. Например, это файл или каталог? Он только для чтения? В предыдущем разделе echar_info была определена как функция для получения атрибутов пути. На самом деле он возвращает 32-битное целое число без знака, которое действует как битовый флаг. Описанный здесь, этот набор битов может дать много информации о пути. Чтобы упростить этот компонент, можно добавить свойство к классу WPath, DWORD _info;, наряду с некоторыми булевыми методами, позволяющими определять его характеристики. В частности, будут добавлены три метода для определения того, является ли путь файлом, каталогом или символической ссылкой на файл.

С добавлением соответствующих новых свойств и методов WPath теперь выглядит так:

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

Каталоги

В последней части этого руководства мы рассмотрим каталоги в операционной системе Windows. Как и в предыдущих операциях, в заголовках Windows CreateDirectoryA и CreateDirectoryW существует набор функций как для путей в кодировке UTF-8, так и для путей в кодировке UTF-16. Как и раньше, мы можем обернуть их в один макрос echar_mkdir() внутри метода mkdir(), который будет выглядеть следующим образом:

Здесь BOOL — это настраиваемый логический тип, определенный в системных заголовках Windows. Этот метод сбрасывает свойство информации о путях после вызова echar_mkdir, потому что, если создание каталога прошло успешно, информация, ранее сохраненная в _info, больше не является точной или действительной. Для дальнейшего расширения этой функциональности доступно множество функций для более сложных операций с каталогами, таких как удаление, копирование, перемещение и т. д.