Учимся работать с файлами через Windows API

27 ноября 2013

Из предыдущих постов, посвященных WinAPI, мы научились настраивать Visual Studio и узнали, как в нем писать простые консольные приложения. Следующим маленьким шажком в изучении WinAPI будет освоение работы с файлами.

Для этого нелегкого дела нам понадобятся следующие процедуры:

CreateFile(szName, dwAccess, dwShareMode, lpSecurityAttributes,
           dwCreationDisposition, dwFlags, hTemplateFile);

В Windows для того, чтобы открыть или создать файл, нужно вызвать процедуру, имеющую целых семь аргументов. К счастью, большинство из них приходится использовать крайне редко. Аргумент szName задает имя файла, а dwAccess — желаемый доступ к файлу, обычно это GENERIC_READ, GENERIC_WRITE или оба значения, объединенные логическим или. Параметр dwShareMode определяет, что могут делать с файлом другие процессы, пока мы с ним работаем. Возможные значения — FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE и их комбинации, однако часто этот параметр просто устанавливают в ноль. Параметр dwCreationDisposition определяет, как именно мы хотим открыть файл, может быть, например, CREATE_NEW, CREATE_ALWAYS, OPEN_EXISTING, OPEN_ALWAYS. О семантике этого хозяйства нетрудно догадаться самостоятельно. С помощью dwFlags можно указать дополнительные свойства файла, например, хранить ли его в зашифрованном или сжатом виде, или сказать, что файл является скрытым, временным или системным. Обычно сюда передают FILE_ATTRIBUTE_NORMAL. Наконец, про lpSecurityAttributes и hTemplateFile сейчас знать не нужно, сюда можно смело передавать NULL.

В случае успешного создания или открытия файла, процедура CreateFile возвращает его хэндл. В случае ошибки возвращается специальное значение INVALID_HANDLE_VALUE. Узнать подробности об ошибке можно с помощью GetLastError.

ReadFile(hFile, lpBuff, dwBuffSize, &dwCount, NULL);

Чтение из файла в буфер lpBuff размером dwBuffSize. В переменную dwCount записывается реальное количество прочитанных байт. Последний опциональный аргумент называется lpOverlapped и о нем сейчас знать не нужно.

WriteFile(hFile, lpBuff, dwBuffSize, &dwCount, NULL);

Аргументы и семантика процедуры WriteFile полностью аналогичны ReadFile.

CloseHandle(hFile);

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

Посмотрим теперь на все это хозяйство в действии. Следующая программа пишет в файл counter.dat количество собственных запусков. Первые пять запусков ничем не примечательны. На шестой и последующие запуски программа сообщает, что у нее истек триал и просит приобрести полную версию.

#include <windows.h>

#define MAX_TRIAL_RUNS 5

const TCHAR szCounterFileName[] = L"counter.dat";
const TCHAR szMsgTmpl[] = L"Вы запустили программу в %d-й раз. %s.";
const TCHAR szCheckOk[] = L"Все в порядке, продолжайте работу";
const TCHAR szCheckFailed[] = L"Триал истек, купите полную версию";

DWORD ReadCounter() {
  DWORD dwCounter, dwTemp;
  HANDLE hFile = CreateFile(szCounterFileName, GENERIC_READ, 0, NULL,
                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if(INVALID_HANDLE_VALUE == hFile) {
    return 1;
  }
  ReadFile(hFile, &dwCounter, sizeof(dwCounter), &dwTemp, NULL);
  if(sizeof(dwCounter) != dwTemp) {
    CloseHandle(hFile);
    return 1;
  }
  CloseHandle(hFile);
  return dwCounter;
}

VOID WriteCounter(DWORD dwCounter) {
  DWORD dwTemp;
  HANDLE hFile = CreateFile(szCounterFileName, GENERIC_WRITE, 0, NULL,
                           CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  if(INVALID_HANDLE_VALUE == hFile) {
    return;
  }
  WriteFile(hFile, &dwCounter, sizeof(dwCounter), &dwTemp, NULL);
  CloseHandle(hFile);
}

int main() {
  TCHAR szMsg[256];
  DWORD dwCounter = ReadCounter();
  LPCWSTR lpCheckResult = dwCounter > MAX_TRIAL_RUNS ?
                          szCheckFailed : szCheckOk;
  wsprintf(szMsg, szMsgTmpl, dwCounter, lpCheckResult);
  MessageBox(0, szMsg, L"Сообщение", 0);

  if(dwCounter <= MAX_TRIAL_RUNS) {
    WriteCounter(dwCounter+1);
  }

  ExitProcess(0);
}

Как обычно, программа также успешно компилируется при помощи MinGW и запускается под Wine.

В качестве домашнего задания можете попробовать модифицировать программу так, чтобы она выводила время, когда производились все ее запуски. Для этого вам понадобятся процедуры GetLocalTime, SetFilePointer и GetFileSizeEx. Если это задание покажется вам слишком простым, попробуйте найти информацию о том, как при помощи процедур, упомянутых в этой заметке, (1) написать консольное приложение и (2) открыть диск C: на чтение, словно он является обычным файлом.

Если у вас есть дополнения или возникли вопросы, смелее пишите комментарии, не стесняйтесь!

Дополнение: Рекурсивный поиск файлов с использованием WinAPI

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.