скрыть

скрыть

  Форум  

Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Google  
 

Следить за изменениями в каталоге



Автор: FliNT

Использование ReadDirectoryChangesW

Очень хорошая функция, только " Windows 95/98/Me: Unsupported" . Т.е. в Win9x этой функции нет. Но мы же пишем для NT, а там с этой функцией все в порядке (если 3-ий сервис пак для NT3.1 поставили :)))

Кратко пройдемся по описанию функции (взято из windows


function ReadDirectoryChangesW(
  hDirectory: THandle; // описатель каталога, за которым надо следить
  lpBuffer: Pointer;  // Указатель на буфер, в который будет записана информация
  nBufferLength: DWORD; // Размер буфера
  bWatchSubtree: Bool; // Следить ли за подкаталогами
  dwNotifyFilter: DWORD; // Фильтр действий
  lpBytesReturned: LPDWORD; // Сколько было записано в буфер
  lpOverlapped: POverlapped; // Для асинхронной работы
  lpCompletionRoutine: FARPROC // Функция, которая будет вызвана при окончании операции
  ): BOOL; stdcall;

Ну а теперь пример работы этой функции (исходник этого примера (пока без комментариев!! и на Delphi6)

Чтобы программа могла нормально работать во время ожидания очередного изменения, мы функции мониторинга выделим отдельный поток. Поток " сделан" на WinAPI (функция WorkThread). При нажатии на одну кнопку он будет создаваться, а на другую - жестоко уничтожаться. Вся полезная информация будет выводиться в TListView.

Функция потока будет описана так:


procedure WorkThread(LV: TListView); stdcall;

LV - это то, во что мы будем выводить инфу. И не забывайте stdcall;

А вот ее текст:


procedure WorkThread(LV : TListView);stdcall;
var
 hDir : THandle;

 lpBuf : Pointer;
 Ptr   : Pointer;

 cbReturn : Cardinal;
 FileName : PWideChar;

 Item : TListItem;
 sTime : _SYSTEMTIME;
begin
 // Сначала нам надо получить описатель каталога, за которым мы будем следить
 // В данном примере это будет весь диск C:
 hDir := CreateFile ('C:\',GENERIC_READ,FILE_SHARE_READ or FILE_SHARE_WRITE
   or FILE_SHARE_DELETE,nil,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,0);
 // Если ошиблись...
 if hDir = INVALID_HANDLE_VALUE
  then begin ShowMessage(SysErrorMessage(GetLastError)); exit; end;

 // Выделяем память под буфер
 // const BUF_SIZE = 2048 - думаю вполне достаточно
 GetMem(lpBuf,BUF_SIZE);

 repeat
  // очищаем память перед записью в нее (на всякий случай)
  ZeroMemory(lpBuf,BUF_SIZE);

  // Теперь мы будем ждать пока чего-нибудь в интересующем нас каталоге
  // изменится или произойдет ошибка (и мы выйдем из цикла)
  // FILE_NOTIFY_CHANGE - это список флагов - о них ниже.
  if not ReadDirectoryChangesW(hDir,lpBuf,BUF_SIZE,true,
                   FILE_NOTIFY_CHANGE,@cbReturn,nil,nil)
   then Break;

  // Сюда мы попадаем, если функция выполнилась успешно
  // и lpBuf указывает на одну или несколько структур FILE_NOTIFY_INFORMATION
  Ptr:=lpBuf;

Отойдем пока от исходного кода и рассмотрим, что у нас появится в буфере. В данный момент lpBuf и Ptr указывают на первую структуру FILE_NOTIFY_INFORMATION. Вторым полем этой структуры является - Action -тип действия, которое было совершено. Четвертым - FileName - первый символ имени файла. Имя файла не заканчивается нулем #0 и для определения его длины используется 3 параметр - FileNameLength. При этом надо учесть, что имя файла в формате Unicode т.е. каждый символ занимает 2 байта, а FileNameLength дается в байтах. Придется эту длину делить на 2, чтобы узнать кол-во символов.

Но возникает вопрос - как узнать, сколько таких структур было записано в буфер. Для этого используется 1 параметр структуры - NextEntryOffset. Если он не равен нулю, то в нем будет кол-во байт, через которые находится следующая запись и нам надо сдвинуть указатель на это кол-во байт, чтобы " получить" следующую структуру. И так далее, пока NextEntryOffset не будет равен 0 (т.е. эта запись была последней).


  repeat
   // Добавляем новый элемент в TListView (ViewStyle = vsReport )
   Item := LV.Items.Add;

   // Выделяем память под имя файла
   GetMem(FileName,PFileNotifyInformation(Ptr).FileNameLength+2);
   // Очищаем память - чтобы последним символом после копирования
   // был бы #0 нуль
   ZeroMemory(FileName,PFileNotifyInformation(Ptr).FileNameLength+2);
   // WinAPI функция для копирования Unicode строки
   lstrcpynW(FileName,PFileNotifyInformation(Ptr).FileName,
             PFileNotifyInformation(Ptr).FileNameLength div 2+1);
   // Имя файла у нас дается относительно папки
   // т.е.если изменится файл C:\File\test.dat, то FileName
   // будет равно File\test.dat
   Item.Caption:='C:\'+FileName;
   // Имя файла нам больше не нужно - очищаем память
   FreeMem(FileName);

   // Определяем тип произошедшего действия
   case PFileNotifyInformation(Ptr).Action of
    FILE_ACTION_ADDED    : Item.SubItems.Add('Файл был создан');
    FILE_ACTION_REMOVED  : Item.SubItems.Add('Файл был удален');
    FILE_ACTION_MODIFIED : Item.SubItems.Add('Файл был изменен');
    FILE_ACTION_RENAMED_OLD_NAME :
   Item.SubItems.Add('Файл был переименован и в имени файла - предыдущее имя');
    FILE_ACTION_RENAMED_NEW_NAME :
  Item.SubItems.Add('новое имя после переименования');
    else Item.SubItems.Add('Произошло что-то странное');
   end;

   // Время, когда произошло событие
   GetLocalTime(sTime);
   with sTime do
    Item.SubItems.Add(Format('%.2d:%.2d:%.2d',[wHour,wMinute,wSecond])); // 13:54:20

   // Если эта запись не последняя (NextEntryOffset < >  0), то...
   if PFileNotifyInformation(Ptr).NextEntryOffset=0
    then Break
     else begin
   // ... добавляем строку в примечания (если интересно посмотреть смещение)
      Item.SubItems.Add('Offset : '+
       IntToStr(PFileNotifyInformation(Ptr).NextEntryOffset));
   //Передвигаем указатель на NextEntryOffset байт вперед
      Inc(Cardinal(Ptr),PFileNotifyInformation(Ptr).NextEntryOffset);
   // Теперь Ptr указывает на следующую запись
     end;

  // Передвигать надо именно Ptr, а не lpBuf
  until false;

 until false;

 // Очищаем память
 FreeMem(lpBuf);
end;

Параметр функции dwNotifyFilter - действия, информацию о которых мы хотим получать.

  • FILE_NOTIFY_CHANGE_FILE_NAME - создание, удаление, переименование файла.
  • FILE_NOTIFY_CHANGE_DIR_NAME - созданием и удаление каталога.
  • FILE_NOTIFY_CHANGE_ATTRIBUTES - изменение атрибутов файла.
  • FILE_NOTIFY_CHANGE_SIZE - изменение размера файла.
  • FILE_NOTIFY_CHANGE_LAST_WRITE - изменение файла (это и предыдущее уведомление будет получено только в момент реальной записи файла на диск).
  • FILE_NOTIFY_CHANGE_LAST_ACCESS - изменение времени последнего доступа.
  • FILE_NOTIFY_CHANGE_CREATION - изменение времени создания файла.
  • FILE_NOTIFY_CHANGE_SECURITY - изменение параметров безопасности (прав доступа и т.д.)

У меня в примере используются FILE_NOTIFY_CHANGE_FILE_NAME, FILE_NOTIFY_CHANGE_DIR_NAME и FILE_NOTIFY_CHANGE_LAST_WRITE.

А теперь надо только запустить поток.


procedure TForm1.Button1Click(Sender: TObject);
var
 ThID : Cardinal;
begin
 // hThread - THandle - глобальная переменная
 // Создаем поток
 // LV - TListView, WorkThread - функция выше
 hThread:=CreateThread(nil,0,@WorkThread,LV,0,ThID);
 // В случае неудачи выводим сообщение
 if hThread=0 then ShowMessage(SysErrorMessage(GetLastError));
end;

У меня в исходниках поток останавливается функцией TerminateThread(hThread,Cardinal(-1)). Но при таком завершении не будут освобождены все ресурсы, занятые потоком (а это как минимум BUF_SIZE байт памяти. Вместо этой функции было бы лучше использовать SuspendThread(hThread), а при запуске проверять на существование потока WaitForSingleObject(hThread,0)= WAIT_TIMEOUT и если он существует - делать ResumeThread(hThread)... но в исходниках этого пока нет :)






Copyright © 2004-2016 "Delphi Sources". Delphi World FAQ




Группа ВКонтакте   Ссылка на Twitter   Группа на Facebook