Categories: C/C++

winapi-file-search/

Продолжаем дружно вспоминать WinAPI . В отличие от всех предыдущих заметок сегодня мы напишем первую программу, которая делает что-то действительно полезное. А именно, позволяет найти файл на жестком диске по части его имени.

В этом нам помогут следующие процедуры и структуры данных.

GetLogicalDrives ( ) ;

Процедура GetLogicalDrives возвращает битовую маску, по которой можно судить, какие диски есть в системе. Например, если 0-ой бит установлен в единицу, значит диск «A:» есть, иначе его нет. Аналогично, 1-ый бит соответствует диску «B:», 2-ой — диску «C:», и так далее до 25-го бита.

FindFirstFile ( szPath , & fdFindData ) ;

В качестве аргументов принимает строку типа «C:windows*.exe» и указатель на структуру WIN32_FIND_DATA . В случае успеха процедура возвращает хэндл, соответствующий поиску, а в fdFindData записывается информация о первом найденном файле — его имя, размер, атрибуты, время создания и так далее. В случае ошибки возвращается INVALID_HANDLE_VALUE.

FindNextFile ( hFind , & fdFindData )

Принимает хэндл, полученный от FindFirstFile , и указатель на WIN32_FIND_DATA. Если FindNextFile вернул TRUE, значит в fdFindData записана информация о следующем файле. Если FALSE, значит все файлы, соответствующие маске, были перечислены.

FindClose ( hFind ) ;

Когда поиск завершен, FindClose закрывает хэндл, полученный от FindFirstFile.

Теперь с помощью перечисленных процедур, а также процедур, с которыми мы с вами познакомились в заметке Пишем простое консольное приложение на чистом WinAPI , не представляет труда написать программу, которая рекурсивно ищет файлы на всех дисках в системе, выводя те имена файлов, в которых содержится заданная в качестве аргумента подстрока:

#include <windows.h>
#include <shlwapi.h>

#define STRLEN(x) (sizeof(x)/sizeof(x[0]) — 1)

VOID ProcessFoundFile ( HANDLE CONST hStdOut , LPWSTR CONST szPath ,
WIN32_FIND_DATA CONST * CONST fdFindData ,
LPWSTR CONST lpSearch ) {
TCHAR szEnd [ ] = L » r n » ;
DWORD dwTemp ;
if ( NULL != StrStrI ( fdFindData -> cFileName , lpSearch ) ) {
WriteConsole ( hStdOut , szPath , lstrlen ( szPath ) , & dwTemp , NULL ) ;
WriteConsole ( hStdOut , szEnd , STRLEN ( szEnd ) , & dwTemp , NULL ) ;
}
}

VOID FindFirstFileFailed ( HANDLE CONST hStdOut , LPWSTR CONST szPath ) {
TCHAR CONST szMsgTmpl [ ] = L «FindFirstFile() failed, »
L «GetLastError() = %d, szPath = %s r n » ;
TCHAR szMsg [ MAX_PATH * 2 ] ;
DWORD dwTemp ;
wsprintf ( szMsg , szMsgTmpl , GetLastError ( ) , szPath ) ;
WriteConsole ( hStdOut , szMsg , lstrlen ( szMsg ) , & dwTemp , NULL ) ;
}

VOID RecursiveSearch ( HANDLE CONST hStdOut , LPWSTR szPath ,
LPWSTR CONST lpSearch ) {
WIN32_FIND_DATA fdFindData ;
HANDLE hFind ;
TCHAR * CONST lpLastChar = szPath + lstrlen ( szPath ) ;

lstrcat ( szPath , L «*» ) ;
hFind = FindFirstFile ( szPath , & fdFindData ) ;
* lpLastChar = ;

if ( INVALID_HANDLE_VALUE == hFind ) {
FindFirstFileFailed ( hStdOut , szPath ) ;
return ;
}

do {
if ( ( 0 == lstrcmp ( fdFindData. cFileName , L «.» ) ) ||
( 0 == lstrcmp ( fdFindData. cFileName , L «..» ) ) ) {
continue ;
}
lstrcat ( szPath , fdFindData. cFileName ) ;
if ( fdFindData. dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
lstrcat ( szPath , L » \ » ) ;
RecursiveSearch ( hStdOut , szPath , lpSearch ) ;
} else {
ProcessFoundFile ( hStdOut , szPath , & fdFindData , lpSearch ) ;
}
* lpLastChar = ;
} while ( FindNextFile ( hFind , & fdFindData ) ) ;

FindClose ( hFind ) ;
}

VOID SearchOnAllDrives ( HANDLE CONST hStdOut , LPWSTR CONST lpSearch ) {
TCHAR szCurrDrive [ ] = L «A: \ » ;
TCHAR szPath [ MAX_PATH + 1 ] ;
DWORD i , dwDisksMask = GetLogicalDrives ( ) ;

for ( i = 0 ; i < 26 ; i ++ ) {
if ( dwDisksMask & 1 ) {
lstrcpy ( szPath , szCurrDrive ) ;
RecursiveSearch ( hStdOut , szPath , lpSearch ) ;
}
dwDisksMask >>= 1 ;
szCurrDrive [ 0 ] ++;
}
}

INT main ( ) {
DWORD dwTemp ;
INT nArgs = 0 ;
LPWSTR CONST lpCmd = GetCommandLine ( ) ;
LPWSTR CONST * CONST lpArgs = CommandLineToArgvW ( lpCmd , & nArgs ) ;
HANDLE CONST hStdOut = GetStdHandle ( STD_OUTPUT_HANDLE ) ;
CONST TCHAR szUsage [ ] = L «Usage: find.exe <part-of-file-name> r n » ;

if ( nArgs < 2 ) {
WriteConsole ( hStdOut , szUsage , STRLEN ( szUsage ) , & dwTemp , NULL ) ;
} else {
SearchOnAllDrives ( hStdOut , lpArgs [ 1 ] ) ;
}
LocalFree ( ( PVOID ) lpArgs ) ;
ExitProcess ( 0 ) ;
}

Чтобы эта программа успешно заработала, нужно кое-что поменять в свойствах нашего шаблонного проекта. Во-первых, тип приложения нужно изменить с GUI на CLI. Во-вторых, в свойствах проекта нужно найти Linker → Input → Additional Dependencies, и прописать там «shlwapi.lib» без кавычек. Зачем это нужно, будет рассказано чуть ниже. Также в свойствах проекта нужно указать аргументы, передаваемые программе при ее запуске из IDE. Для этого идем в Debugging → Command Arguments и пишем там, например, «.txt».

Если теперь скомпилировать и запустить программу, вы увидите примерно следующее:

В приведенном выше коде есть несколько тонких моментов. Во-первых, как вы могли заметить, в нем активно используется макрос CONST. Он определен в файле windef.h как:

#define CONST const

Как большие специалисты по Си (вы же прочитали Кернигана и Ритчи , как я совевтовал?), вы, конечно же, прекрасно знаете, для чего нужно ключевое слово const. Но чисто на всякий случай освежим эти знания. Ключевое слово const говорит о том, что переменная является неизменяемой. Например, если написать:

HANDLE CONST hStdOut = GetStdHandle ( STD_OUTPUT_HANDLE ) ;

… а затем попытаться присвоить переменной hStdOut новое значение, компилятор откажется компилировать программу. Когда в дело вступают указатели, все становится чуточку интереснее. Проще всего показать это на примерах:

// неизменяемый указатель на массив неизменяемых LPWSTR
LPWSTR CONST * CONST lpArgs = CommandLineToArgvW ( lpCmd , & nArgs ) ;

// неизменяемый указатель на массив изменяемых TCHAR’ов
TCHAR * CONST lpLastChar = szPath + lstrlen ( szPath ) ;

// изменяемый указатель на массив неизменяемых TCHAR’ов
// TCHAR CONST * lpLastChar = …

Вместо «HANDLE CONST» также можно писать «CONST HANDLE». Учитывая, что в Си, как известно, можно с легкостью прострелить себе ногу, по возможности следует использовать const как можно чаще. Даже несмотря на то, что из-за него становится несколько сложнее читать код.

Во-вторых, вы могли обратить внимание на использование процедуры StrStrI . Как нетрудно догадаться, она предназначена для сравнения строк без учета регистра. Данная процедура экспортируется динамической библиотекой shlwapi.dll, поэтому нам пришлось написать в коде #include <shlwapi.h> и в свойствах проекта указать, чтобы линковщик использовал библиотеку shlwapi.lib.

Наконец, в-третьих, наверняка вам не дает покоя следующая проверка:

if ( ( 0 == lstrcmp ( fdFindData. cFileName , L «.» ) ) ||
( 0 == lstrcmp ( fdFindData. cFileName , L «..» ) ) ) {
continue ;
}

Так сделано, потому что наряду с обычными файлами и каталогами, FindFirstFile и FindNextFile возвращают информацию о специальных каталогах «.» и «..», которые являются ссылками на текущий и родительский каталоги соответственно. Такая проверка гарантирует, что наш рекурсивный алгоритм не скушает весь стек, обращаясь к каталогам типа «C:..…и так много раз…». Следует обратить внимание, что в Windows имена файлов могут начинаться с точки (cmd.exe → echo 123 > .test ), поэтому имя нужно проверять целиком, а не только его первый символ.

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

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

Дополнение: Как выяснилось, у приведенного кода есть ряд недостатков. Во-первых, при запуске под Wine длина имени файла может превосходить MAX_PATH. Во-вторых, код не учитывает существование симлинков и junction points . В продакшн коде все это должно учитываться.

Дополнение: Получаем список запущенных процессов на Windows API

admin

Share
Published by
admin
Tags: C/C++

Recent Posts

Консоль удаленного рабочего стола(rdp console)

Клиент удаленного рабочего стола (rdp) предоставляет нам возможность войти на сервер терминалов через консоль. Что…

2 месяца ago

Настройка сети в VMware Workstation

В VMware Workstation есть несколько способов настройки сети гостевой машины: 1) Bridged networking 2) Network…

2 месяца ago

Логи брандмауэра Windows

Встроенный брандмауэр Windows может не только остановить нежелательный трафик на вашем пороге, но и может…

2 месяца ago

Правильный способ отключения IPv6

Вопреки распространенному мнению, отключить IPv6 в Windows Vista и Server 2008 это не просто снять…

2 месяца ago

Ключи реестра Windows, отвечающие за параметры экранной заставки

Параметры экранной заставки для текущего пользователя можно править из системного реестра, для чего: Запустите редактор…

2 месяца ago

Как управлять журналами событий из командной строки

В этой статье расскажу про возможность просмотра журналов событий из командной строки. Эти возможности можно…

2 месяца ago