I built this multi threaded file search using Events and Critical sections.
I have a single thread (FolderThread) to locate all the folders recursively, and multiple threads (FileThread) to look for files in each of the folders discovered (I have assigned one file thread per folder discovered). This is done to speed up the application. I also have a final single thread (SearchThread) that matches the file name string in the discovered file paths. I have set up critical sections to prevent data race conditions, and events to notify sleeping threads to handle the required tasks on waking up. But, it won't work as expected.
There are 2 files I want it to locate (both having the same name: My_search_item.txt). One located at D:\search\My_search_item.txt (that it does not pick up), and the other located at D:\search\test\My_search_item.txt (that it picks up twice).
Any ideas on how I can fix this?
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h> // For malloc, realloc, free
#include <string.h>
#include <wchar.h> // For wcslen and wcscpy
HANDLE hNewFileItemEvent;
HANDLE hNewSearchItemEvent;
HANDLE hNoMoreWorkEvent;
WCHAR* filePath;
WIN32_FIND_DATAW fd;
WCHAR subPath[MAX_PATH];
const WCHAR* rootPath = L"D:\\search";
CRITICAL_SECTION g_queueCS;
typedef struct {
WCHAR** data; // Pointer to an array of string pointers
int size; // Current number of strings in the array
int capacity; // Allocated capacity of the array
} StringDynamicArray;
StringDynamicArray myFolders;
StringDynamicArray myFiles;
void initStringDynamicArray(StringDynamicArray* arr, int initialCapacity) {
arr->data = (WCHAR**)malloc(sizeof(WCHAR*) * initialCapacity);
if (arr->data == NULL) {
perror("Failed to allocate initial memory for string array");
exit(EXIT_FAILURE);
}
arr->size = 0;
arr->capacity = initialCapacity;
}
void pushString(StringDynamicArray* arr, const WCHAR* str) {
if (arr->size == arr->capacity) {
arr->capacity *= 2;
arr->data = (WCHAR**)realloc(arr->data, sizeof(WCHAR*) * arr->capacity);
if (arr->data == NULL) {
perror("Failed to reallocate memory for string array");
exit(EXIT_FAILURE);
}
}
size_t strLen = wcslen(str);
arr->data[arr->size] = (WCHAR*)malloc((strLen + 1) * sizeof(wchar_t)); // +1 for null terminator
if (arr->data[arr->size] == NULL) {
perror("Failed to allocate memory for string");
exit(EXIT_FAILURE);
}
// Use wcscpy_s with the correct buffer size (strLen + 1)
errno_t err = wcscpy_s(arr->data[arr->size], strLen + 1, str);
if (err == 0) {
wprintf(L"Successfully copied: %ls\n", arr->data[arr->size]);
arr->size++;
}
else {
wprintf(L"Error copying string. Error code: %d\n", err);
}
}
WCHAR* popString(StringDynamicArray* arr) {
if (arr->size == 0) {
fprintf(stderr, "Error: Cannot pop from an empty array.\n");
return NULL;
}
arr->size--;
WCHAR* poppedStr = arr->data[arr->size];
return poppedStr; // Caller is responsible for freeing this memory
}
void freeStringDynamicArray(StringDynamicArray* arr) {
for (int i = 0; i < arr->size; i++) {
free(arr->data[i]); // Free individual strings
}
free(arr->data); // Free the array of pointers
arr->data = NULL;
arr->size = 0;
arr->capacity = 0;
}
void searchDirectories(const WCHAR* path) {
WIN32_FIND_DATAW findData;
HANDLE hFind = INVALID_HANDLE_VALUE;
WCHAR searchPath[MAX_PATH];
WCHAR subPath[MAX_PATH];
// Construct the search pattern (e.g., "D:\\search\\*")
swprintf_s(searchPath, MAX_PATH, L"%s\\*", path);
// Start the search with FindFirstFileW
hFind = FindFirstFileW(searchPath, &findData);
if (hFind == INVALID_HANDLE_VALUE) {
wprintf(L"Error opening directory %s: %d\n", path, GetLastError());
return;
}
// Iterate through all files and directories
do {
// Skip the current (".") and parent ("..") directories
if (wcscmp(findData.cFileName, L".") == 0 || wcscmp(findData.cFileName, L"..") == 0) {
continue;
}
// Construct the full path of the current file or directory
swprintf_s(subPath, MAX_PATH, L"%s\\%s", path, findData.cFileName);
// Print the full path
wprintf(L"%s\n", subPath);
// If it's a directory, recursively search it
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
pushString(&myFolders, (LPCWSTR)subPath);
searchDirectories(subPath);
}
} while (FindNextFileW(hFind, &findData) != 0);
// Check if the loop ended due to an error
DWORD error = GetLastError();
if (error != ERROR_NO_MORE_FILES) {
wprintf(L"Error during directory search: %d\n", error);
}
// Close the search handle
FindClose(hFind);
}
//IN each directory provided as an argument search for all files in it
void searchFiles(const WCHAR* path) {
WIN32_FIND_DATAW findData;
HANDLE hFind = INVALID_HANDLE_VALUE;
WCHAR searchPath[MAX_PATH];
WCHAR subPath[MAX_PATH];
swprintf_s(searchPath, MAX_PATH, L"%s\\*", path);
// Start the search with FindFirstFileW
hFind = FindFirstFileW(searchPath, &findData);
if (hFind == INVALID_HANDLE_VALUE) {
wprintf(L"Error opening directory %s: %d\n", path, GetLastError());
return;
}
// Iterate through all files and directories
do {
// Skip the current (".") and parent ("..") directories
if (wcscmp(findData.cFileName, L".") == 0 || wcscmp(findData.cFileName, L"..") == 0) {
continue;
}
// Construct the full path of the current file or directory
swprintf_s(subPath, MAX_PATH, L"%s\\%s", path, findData.cFileName);
// Print the full path
wprintf(L"%s\n", subPath);
// If it's NOT a directory, write it to the myFiles Struct
if (findData.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY) {
::EnterCriticalSection(&g_queueCS);
printf("Size: %d\n", myFiles.size);
pushString(&myFiles, (LPCWSTR)subPath);
::LeaveCriticalSection(&g_queueCS);
::SetEvent(hNewSearchItemEvent);
}
} while (FindNextFileW(hFind, &findData) != 0);
// Check if the loop ended due to an error
DWORD error = GetLastError();
if (error != ERROR_NO_MORE_FILES) {
wprintf(L"Error during directory search: %d\n", error);
}
// Close the search handle
FindClose(hFind);
}
BOOL FileMatchesSearch(WCHAR fullPath[], WCHAR targetString[]) {
size_t fullPathLen = wcslen(fullPath);
size_t targetStringLen = wcslen(targetString);
// Check if the full path is long enough to contain the target string
if (fullPathLen >= targetStringLen) {
// Get a pointer to the potential start of the target string in fullPath
WCHAR* endOfPath = fullPath + (fullPathLen - targetStringLen);
// Compare the substring with the target string
if (wcscmp(endOfPath, targetString) == 0) {
//printf("'%ws' exists at the end of the path.\n", targetString);
printf("File path found: %ws\n", fullPath);
return TRUE;
}
else {
printf("'%ws' does NOT exist at the end of the path.\n", targetString);
return FALSE;
}
}
else {
printf("The path is too short to contain '%ws'.\n", targetString);
return FALSE;
}
}
DWORD WINAPI FolderThread(PVOID) {
searchDirectories(rootPath);
::SetEvent(hNewFileItemEvent);
return 42;
}
DWORD WINAPI FileThread(LPVOID lpParam) {
const WCHAR* folderPath = (WCHAR*)lpParam;
wprintf(L"Processing string: %s\n", folderPath);
while (true) {
HANDLE waitHandles[2] = { hNewFileItemEvent, hNoMoreWorkEvent };
DWORD waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
if (waitResult == WAIT_OBJECT_0 + 1) { // hNoMoreWorkEvent is signaled
return 0; // Exit the thread
}
else {
break;
}
}
searchFiles(folderPath);
return 42;
}
DWORD WINAPI SearchThread(PVOID) {
while (true) {
HANDLE waitHandles[2] = { hNewSearchItemEvent, hNoMoreWorkEvent };
DWORD waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
if (waitResult == WAIT_OBJECT_0 + 1) { // hNoMoreWorkEvent is signaled
break; // Exit the thread
}
// Else, hNewItemEvent was signaled. Grab work.
//Sleep(10000);
::EnterCriticalSection(&g_queueCS);
if (myFiles.size != 0) {
filePath = popString(&myFiles);
// If we just took the last item, reset the event
}
else if (myFiles.size == 0) {
::ResetEvent(hNewSearchItemEvent);
}
::LeaveCriticalSection(&g_queueCS);
WCHAR searchPattern[] = L"My_search_item.txt";
// Allocate a WCHAR array on the stack, including space for the null terminator
const int MAX_LENGTH = 256; // Or a suitable maximum length
WCHAR destinationArray[MAX_LENGTH];
// Copy the string
wcscpy_s(destinationArray, MAX_LENGTH, filePath);
if (FileMatchesSearch(destinationArray, searchPattern)) {
printf("File Found...!!!\n");
}
}
return 0;
}
int main()
{
::InitializeCriticalSection(&g_queueCS);
initStringDynamicArray(&myFolders, 5);
initStringDynamicArray(&myFiles, 5);
// Create a manual-reset event in a non-signaled state
hNewFileItemEvent = ::CreateEvent(
NULL, // Default security attributes
TRUE, // Manual-reset event
FALSE, // Initial state is non-signaled
L"NewItemEvent" // Name of the event (optional)
);
// Create a manual-reset event in a non-signaled state
hNewSearchItemEvent = ::CreateEvent(
NULL, // Default security attributes
TRUE, // Manual-reset event
FALSE, // Initial state is non-signaled
L"NewItemEvent" // Name of the event (optional)
);
// Create a manual-reset event in a non-signaled state
hNoMoreWorkEvent = ::CreateEvent(
NULL, // Default security attributes
TRUE, // Manual-reset event
FALSE, // Initial state is non-signaled
L"NewItemEvent" // Name of the event (optional)
);
//************************Folder thread
HANDLE hThreadFolder = ::CreateThread(NULL,0, FolderThread, NULL, 0 ,NULL);
::WaitForSingleObject(hThreadFolder, INFINITE);
// Array to store thread handles
HANDLE* threads = (HANDLE*)malloc(myFolders.size * sizeof(HANDLE));
if (!threads) {
wprintf(L"Failed to allocate memory for thread handles\n");
return 1;
}
//**************MULtiple threads to look for files in each folder discovered (One thread per folder)
// Loop through the data array and create a thread for each index
for (int i = 0; i < myFolders.size; i++) {
threads[i] = CreateThread(
NULL, // Default security attributes
0, // Default stack size
FileThread, // Thread function
myFolders.data[i], // Pass the string at index i
0, // Default creation flags
NULL // No thread ID needed
);
if (threads[i] == NULL) {
wprintf(L"Failed to create thread for index %d\n", i);
// Handle error (e.g., clean up and exit)
}
}
// Wait for all threads to complete
WaitForMultipleObjects(myFolders.size, threads, TRUE, INFINITE);
// ****************************Singe thread to Search for the File by file name
HANDLE hThreadSearch = ::CreateThread(NULL, 0, SearchThread, NULL, 0, NULL);
::WaitForSingleObject(hThreadSearch, INFINITE);
// Clean up thread handles
for (int i = 0; i < myFolders.size; i++) {
if (threads[i]) {
CloseHandle(threads[i]);
}
}
// Clean up allocated memory
for (int i = 0; i < myFolders.size; i++) {
free(myFolders.data[i]); // Free each string
}
free(myFolders.data); // Free the array of pointers
free(threads); // Free thread handles array
::CloseHandle(hThreadFolder);
::CloseHandle(hThreadSearch);
::DeleteCriticalSection(&g_queueCS);
freeStringDynamicArray(&myFolders);
freeStringDynamicArray(&myFiles);
return 0;
}
Terminal Output:

std::threadetc.).rootPath) is not added tomyFolders, sosearchFilesnever looks at it; only in its subdirectories. That's whyD:\search\My_search_item.txtis not found.SearchThreadhas an off-by-one error - it proceeds with the search even whenmyFiles.size == 0, using the file name from the previous iteration. This is whyD:\search\test\My_search_item.txtappears twice.