0

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:

image

16
  • 3
    A side note: why don't you use the standard Filesystem library ? (as well as other standard classes like std::thread etc.). Commented Sep 2 at 7:09
  • 1
    This code would likely be much simpler using the c++ standard library rather than the raw win32 api Commented Sep 2 at 7:12
  • 1
    @KimKulling so is the win32 api... Commented Sep 2 at 7:16
  • 1
    The root directory (rootPath) is not added to myFolders, so searchFiles never looks at it; only in its subdirectories. That's why D:\search\My_search_item.txt is not found. Commented Sep 2 at 14:41
  • 1
    SearchThread has an off-by-one error - it proceeds with the search even when myFiles.size == 0, using the file name from the previous iteration. This is why D:\search\test\My_search_item.txt appears twice. Commented Sep 2 at 14:44

1 Answer 1

0

Hi there finally I have it working, first of all there were several errors in my code the event objects were using the same name (In my working code example only one event object was necessary). also I had to make several other changes as well. Like addressing this issue of an off-by-one error in SearchFiles - since it proceeds with the search even when myFiles.size == 0, using the file name from the previous iteration. That's why D:\search\test\My_search_item.txt appeared twice. also I included this Line

    pushString(&myFolders, rootPath);//Without this Line it won't search in the current folder

Close to the top of the main function

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h> // For malloc, realloc, free
#include <string.h>
#include <wchar.h> // For wcslen and wcscpy

HANDLE g_NewFileItemEvent;
HANDLE g_NoMoreWorkEvent;
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];
    //::EnterCriticalSection(&g_queueCS);
    //pushString(&myFolders, (LPCWSTR)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) {
            wprintf(L"[DIR]: %ws\n", subPath);
            pushString(&myFolders, (LPCWSTR)subPath);
            searchDirectories(subPath);
        }
        
    } while (FindNextFileW(hFind, &findData) != 0);
    //::LeaveCriticalSection(&g_queueCS);
    // 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
    while (FindNextFileW(hFind, &findData) != 0) {
        // 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) {
            //wprintf(L"[FILE]: %s\n", subPath);
            ::EnterCriticalSection(&g_queueCS);
            //printf("Size: %d\n", myFiles.size);
            pushString(&myFiles, (LPCWSTR)subPath);
            ::LeaveCriticalSection(&g_queueCS);
        }
    }
    printf("File thread: Finished producing items.\n");
    
    // 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);
    ::Sleep(20000);
    ::SetEvent(g_NewFileItemEvent);
    return 42;
}

DWORD WINAPI FileThread(LPVOID lpParam) {
    printf("File thread: waiting for Start signal.\n");
    ::WaitForSingleObject(g_NewFileItemEvent, INFINITE);
    const WCHAR* folderPath = (WCHAR*)lpParam;
    wprintf(L"Processing string: %s\n", folderPath);
    printf("File thread: Starting.\n");
    searchFiles(folderPath);
    printf("File thread: Exiting.\n");
    return 42;
}

DWORD WINAPI SearchThread(PVOID) {
    printf("Search thread started. Waiting for manual reset event...\n");
    // Wait for the manual reset event
    WaitForSingleObject(g_NewFileItemEvent, INFINITE);
    printf("Search thread: Event received! Starting to consume items...\n");
    while (myFiles.size !=0) {
        ::EnterCriticalSection(&g_queueCS);
        WCHAR* filePath = NULL;
        if (myFiles.size != 0)
        {
            filePath = popString(&myFiles);
            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");
            }
        }
        ::LeaveCriticalSection(&g_queueCS);
    }
    printf("Search thread: Thread exiting.\n");
    return 42;
}

int main(){
    ::InitializeCriticalSection(&g_queueCS);
    initStringDynamicArray(&myFolders, 5);
    initStringDynamicArray(&myFiles, 5);
    pushString(&myFolders, rootPath);//Without this Line it won't search in the current folder
    // Create a manual-reset event in a non-signaled state
    g_NewFileItemEvent = ::CreateEvent(
        NULL,               // Default security attributes
        TRUE,               // Manual-reset event
        FALSE,              // Initial state is non-signaled
        L"NewFileItemEvent" // 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]);
        }
    }
    free(threads); // Free thread handles array
    ::CloseHandle(hThreadFolder);
    ::CloseHandle(hThreadSearch);
    ::DeleteCriticalSection(&g_queueCS);
    freeStringDynamicArray(&myFolders);
    freeStringDynamicArray(&myFiles);
    return 0;
}
Sign up to request clarification or add additional context in comments.

1 Comment

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.