Delphi - объектно-ориентированный язык программирования, разработанный компанией Borland в 1995 году. Он основан на языке программирования Pascal, но имеет более расширенные возможности и добавлены новые функции.
Delphi является интегрированной средой разработки (IDE), которая позволяет разрабатывать программное обеспечение для различных платформ, включая Windows, macOS, Android и iOS. Delphi достигает многоплатформенности с помощью...
Очень хорошая функция, только " Windows 95/98/Me: Unsupported" . Т.е. в Win9x этой функции нет. Но мы же пишем для NT, а там с этой функцией все в порядке (если 3-ий сервис пак для NT3.1 поставили :)))
Кратко пройдемся по описанию функции (взято из windows
functionReadDirectoryChangesW( hDirectory: THandle; // описатель каталога, за которым надо следить lpBuffer: Pointer; // Указатель на буфер, в который будет записана информация nBufferLength: DWORD; // Размер буфера bWatchSubtree: Bool; // Следить ли за подкаталогами dwNotifyFilter: DWORD; // Фильтр действий lpBytesReturned: LPDWORD; // Сколько было записано в буфер lpOverlapped: POverlapped; // Для асинхронной работы lpCompletionRoutine: FARPROC // Функция, которая будет вызвана при окончании операции ): BOOL; stdcall
;
Ну а теперь пример работы этой функции (исходник этого примера (пока без комментариев!! и на Delphi6) можно скачать здесь)
Чтобы программа могла нормально работать во время ожидания очередного изменения, мы функции мониторинга выделим отдельный поток. Поток " сделан" на WinAPI (функция WorkThread). При нажатии на одну кнопку он будет создаваться, а на другую - жестоко уничтожаться. Вся полезная информация будет выводиться в TListView.
Функция потока будет описана так:
procedureWorkThread(LV: TListView); stdcall
;
LV - это то, во что мы будем выводить инфу. И не забывайте stdcall;
А вот ее текст:
procedureWorkThread(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);
// Если ошиблись... ifhDir = 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 est.dat, то FileName // будет равно File est.dat Item.Caption:='C:'+FileName; // Имя файла нам больше не нужно - очищаем память FreeMem(FileName); // Определяем тип произошедшего действия casePFileNotifyInformation(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); withsTime do
Item.SubItems.Add(Format('%.2d:%.2d:%.2d',[wHour,wMinute,wSecond]));
// 13:54:20 // Если эта запись не последняя (NextEntryOffset < > 0), то... ifPFileNotifyInformation(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 untilfalse
; until
false
;
// Очищаем память FreeMem(lpBuf); end;
Параметр функции dwNotifyFilter - действия, информацию о которых мы хотим получать.
У меня в примере используются FILE_NOTIFY_CHANGE_FILE_NAME, FILE_NOTIFY_CHANGE_DIR_NAME и FILE_NOTIFY_CHANGE_LAST_WRITE.
А теперь надо только запустить поток.
procedureTForm1.Button1Click(Sender: TObject); var
ThID : Cardinal; begin
// hThread - THandle - глобальная переменная // Создаем поток // LV - TListView, WorkThread - функция выше hThread:=CreateThread(nil
,0,@WorkThread,LV,0,ThID);
// В случае неудачи выводим сообщение ifhThread=0 then
ShowMessage(SysErrorMessage(GetLastError)); end
;
У меня в исходниках поток останавливается функцией TerminateThread(hThread,Cardinal(-1)). Но при таком завершении не будут освобождены все ресурсы, занятые потоком (а это как минимум BUF_SIZE байт памяти. Вместо этой функции было бы лучше использовать SuspendThread(hThread), а при запуске проверять на существование потока WaitForSingleObject(hThread,0)= WAIT_TIMEOUT и если он существует - делать ResumeThread(hThread)... но в исходниках этого пока нет :)