0
votes

I've been given a task to create 2 processes. First one opens a file "log.txt" and adds the input given by user to it. The second process is meant to be a "monitor" of that file. It checks if it exists, gives its size and gives the number of characters entered by user since the start of the second process. I'm using the GetFileSize() function to it so it's not a problem.

I'm slighty confused by the CreateProcess() and CreateFile() functions as I'm not sure how to connect it with one another.

I've read that the CreateFile() function can be used as a mutex by changing its flags. I've come up with something like this:

HANDLE hFile = CreateFile(
                    "log.txt",
                    FILE_APPEND_DATA,
                    FILE_SHARE_WRITE | FILE_SHARE_READ,
                    0,
                    OPEN_ALWAYS,
                    FILE_ATTRIBUTE_NORMAL,
                    0);

Now I'm not really sure how to connect it to processes and where to start the processes from. And also I have no idea how to check how many characters were given since the start of the second process.

Can someone explain to me when to start those 2 processes and how to connect the CreateFile() function to them?

3
"I'm slighty confused by the CreateProcess() and CreateFile() functions as I'm not sure how to connect it with one another" - you don't "connect" them. If needed, simply have the 1st process pass the file path as a command-line parameter to the 2nd process when calling CreateProcess(). Have the 2nd process open its own handle to the file. "I've read that the CreateFile() function can be used as a mutex by changing its flags" - where did you read that? If you need a mutex, why not just use a real named mutex via CreateMutex()? Processes can share mutexes. - Remy Lebeau
"I have no idea how to check how many characters were given since the start of the second process" - yes, you do, since you already stated it - GetFileSize(). Have the 2nd process open the file, and then periodically query its size, such as in a timer or Find(First|Next)ChangeNotification or ReadDirectoryChangesW callback, and react accordingly whenever the size changes. - Remy Lebeau

3 Answers

1
votes

FILE_SHARE_WRITE | FILE_SHARE_READ will allow other processes to open and share the read and write access. You need to open the file exclusively (with dwShareMode = 0), but this requires process2 to try to open the file exclusively in a loop. Instead, use CreateMutex to create a mutex, then process1 uses WaitForSingleObject to take up the mutex, do something and then ReleaseMutex, process2 uses WaitForSingleObject waits for released mutex, and then reads the file. (One synchronization is completed)

process 2:

#include <windows.h>
#include <iostream>
 
int wmain(int argc, wchar_t* argv[])
{
    HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, false, L"MyMutex");

    DWORD dwWaitResult = WaitForSingleObject(hMutex, INFINITE);
    if (dwWaitResult == WAIT_OBJECT_0)
    {
        HANDLE hFile = CreateFile(L"log.txt", FILE_READ_ATTRIBUTES, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
        if (hFile == INVALID_HANDLE_VALUE)
        {
            std::cout << "CreateFile error " << GetLastError() << std::endl;
            ReleaseMutex(hMutex);
        }
        else
        {
            DWORD size = GetFileSize(hFile, NULL);
            std::cout << "File Size: " << size << std::endl;
            CloseHandle(hFile);
            ReleaseMutex(hMutex);
        }
    }
    return 0;
}

process 1:

#include <windows.h>
#include <iostream>
int wmain(int argc, wchar_t* argv[])
{
    HANDLE hMutex = CreateMutex(NULL, false, L"MyMutex");
    DWORD dwWaitResult = WaitForSingleObject(hMutex, INFINITE);
    if (dwWaitResult == WAIT_OBJECT_0)
    {
        STARTUPINFO si = { 0 };
        si.cb = sizeof(si);
        PROCESS_INFORMATION pi = { 0 };
        CreateProcess(L"process2.exe",
            NULL,
            NULL,
            NULL,
            false,
            CREATE_NEW_CONSOLE,
            NULL,
            NULL,
            &si,
            &pi);
        std::string buffer;
        std::cin >> buffer;
        HANDLE hFile = CreateFile(L"log.txt", FILE_APPEND_DATA, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        DWORD written = 0;
        WriteFile(hFile, buffer.c_str(), buffer.size(), &written, NULL);
        CloseHandle(hFile);
        ReleaseMutex(hMutex);
    }
    return 0;
}

But this is more troublesome, because you need to synchronize the two processes every time. As @Remy Lebeau said, use ReadDirectoryChangesW in process2:

#include <windows.h>
#include <iostream>
 
int wmain(int argc, wchar_t* argv[])
{
    FILE_NOTIFY_INFORMATION* pInfo = NULL;

    HANDLE hFile = CreateFile(L"Directory of log.txt", GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, 0);

    while (1)
    {
        DWORD returned = 0;
        DWORD dwOffset = 0;
        BYTE szBuffer[1024] = { 0 };
        
        ReadDirectoryChangesW(hFile, szBuffer, sizeof(szBuffer), false, FILE_NOTIFY_CHANGE_SIZE, &returned, NULL, NULL);
        do
        {
            pInfo = (FILE_NOTIFY_INFORMATION*)&szBuffer[dwOffset];
            if (wcscmp(pInfo->FileName, L"log.txt") == 0)
            {
                HANDLE hFile = CreateFile(L"path\\log.txt", FILE_APPEND_DATA, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
                DWORD size = GetFileSize(hFile, NULL);
                std::cout << "File Size: " << size << std::endl;
                CloseHandle(hFile);
            }
            dwOffset += pInfo->NextEntryOffset;
        } while (pInfo->NextEntryOffset != 0);
    }
    return 0;
}

And process 1 only need to get user input and write to the file:

#include <windows.h>
#include <iostream>
int wmain(int argc, wchar_t* argv[])
{
    std::string buffer;
    while (1)
    {
        std::cin >> buffer;
        HANDLE hFile = CreateFile(L"log.txt", FILE_APPEND_DATA, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        DWORD written = 0;
        WriteFile(hFile, buffer.c_str(), buffer.size(), &written, NULL);
        CloseHandle(hFile);
    }
    return 0;
}
0
votes

It appears that you want to synchronize these two proccesses so that the second one waits for the first one to complete writing to "log.txt".

For that, you would need to open that file in the first process with exclusive access (no FILE_SHARE_WRITE | FILE_SHARE_READ), and close it when it's done writing.

The second process would try to open that same file, also with exclusive access. CreateFile() would fail with "access denied" error if that file is still in use by the first process. This is an essence of "mutually exclusive" concept of mutex. You would then wait a little and try again.

Contrary to synchronization objects, I am not aware of the way to wait for the file to become available (easily done with WaitForSingleObject for mutex).

0
votes

So I've managed to make something out of your precious comments. I'm not sure if it is a right way to solve this task. It would be nice if someone could review my code and give me some additional tips.

Here is my code

int main(int argc, char *argv[])
{

    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(si));
    ZeroMemory(&pi, sizeof(pi));

    si.cb = sizeof(si);
    CreateProcess("process2.exe",
        NULL,
        NULL,
        NULL,
        false,
        CREATE_NEW_CONSOLE,
        NULL,
        NULL,
        &si,
        &pi);

        std::string buffer;
        std::cout << "Enter your text:" << std::endl;
        getline(std::cin, buffer);
        HANDLE hFile = CreateFile("log.txt", FILE_APPEND_DATA, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        DWORD written = 0;
        WriteFile(hFile, buffer.c_str(), buffer.size(), &written, NULL);



        hFile = CreateFile("log.txt", FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
        if (hFile == INVALID_HANDLE_VALUE)
        {
            std::cout << "CreateFile error " << GetLastError() << std::endl;
        }
        else
        {
            DWORD size = GetFileSize(hFile, NULL);
            std::cout << "\nCurrent file size: " << size << std::endl;
            CloseHandle(hFile);
        }

        int stringLenght = 0;
        for(int i=0; buffer[i]; i++)
            stringLenght++;

            std::cout << "\nCharacters given since last startup: " << stringLenght << std::endl;

 return 0;
 }

I'm not sure if that was the point in this task or should it check the file size and ask user for input in a while loop and if it's possible to do without a mutex.