Search code examples
cwinapireadfile

ReadFile function from WinApi does not read from CreateProcess with CreatePipe


I'm writing an application to obtain Geolocation on C.

To get the coordinates, I decided to use Popen with PowerShell and this is the code I wrote:

BOOL getLatitude(wchar_t* latitudeBuffer)
{
    enableLocation();
    FILE* latitudePipe;
    if (latitudePipe = _wpopen(L"powershell -c \"Add-Type -AssemblyName System.Device; $GeoCoordinateWatcher = New-Object System.Device.Location.GeoCoordinateWatcher; $GeoCoordinateWatcher.Start(); Start-Sleep -Milliseconds 2500; $GeoCoordinateWatcher.Position.Location.Latitude 2>&1\"", L"r"))
    {
        wchar_t* latitudeResult = fgetws(latitudeBuffer, COORDINATES_BUFFER_SIZE, latitudePipe);
        _pclose(latitudePipe);
        if (latitudeResult)
        {
            if ('0' <= latitudeBuffer[0] && latitudeBuffer[0] <= '9')
            {
                latitudeBuffer[COORDINATES_SIZE] = '\0';
                wchar_t* latitudePoint = wcschr(latitudeBuffer, L'.');
                if (latitudePoint != NULL) *latitudePoint = '-';
                return TRUE;
            }
        }
    }
    wcsncpy_s(latitudeBuffer, COORDINATES_BUFFER_SIZE, L"NULL", COORDINATES_BUFFER_SIZE);
    return FALSE;
}

It works, but I encountered a problem that when Popen is used, the terminal opens for a few seconds. And I need everything to happen in the background.

Then I decided to use CreatePipe and CreateProcess, and then read the output using ReadFile, but it did not put the result in the buffer and ended up in an infinite loop. Here's the code:

#define COORDINATES_BUFFER_SIZE 4096

void getLat(wchar_t* latitudeBuffer)
{
    enableLocation();
    
    HANDLE latitudeWriteOutHandle = NULL;
    HANDLE latitudeReadOutHandle = NULL;

    SECURITY_ATTRIBUTES latitudeSecurityAttributes;
    latitudeSecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
    latitudeSecurityAttributes.bInheritHandle = TRUE;
    latitudeSecurityAttributes.lpSecurityDescriptor = NULL;

    CreatePipe(&latitudeReadOutHandle, &latitudeWriteOutHandle, &latitudeSecurityAttributes, 0);
    SetHandleInformation(&latitudeReadOutHandle, HANDLE_FLAG_INHERIT, 0);

    wchar_t cmd[] = L"powershell.exe -c \"Add-Type -AssemblyName System.Device; $GeoCoordinateWatcher = New-Object System.Device.Location.GeoCoordinateWatcher; $GeoCoordinateWatcher.Start(); Start-Sleep -Milliseconds 2500; $GeoCoordinateWatcher.Position.Location.Latitude 2>&1\"";
    STARTUPINFO latitudeStartUpInfo;
    PROCESS_INFORMATION latitudeProcessInformation;
    ZeroMemory(&latitudeStartUpInfo, sizeof(STARTUPINFO));
    ZeroMemory(&latitudeProcessInformation, sizeof(PROCESS_INFORMATION));
    latitudeStartUpInfo.cb = sizeof(STARTUPINFO);
    latitudeStartUpInfo.hStdOutput = latitudeWriteOutHandle;     // edited
    latitudeStartUpInfo.hStdError = latitudeWriteReadOutHandle;  // edited
    latitudeStartUpInfo.dwFlags |= STARTF_USESTDHANDLES;

    if (CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &latitudeStartUpInfo, &latitudeProcessInformation))
    {
        CloseHandle(latitudeProcessInformation.hThread);
        CloseHandle(latitudeProcessInformation.hProcess);
        CloseHandle(latitudeWriteOutHandle);
        
        DWORD dwRead;
        while (TRUE) if (ReadFile(latitudeReadOutHandle, latitudeBuffer, COORDINATES_BUFFER_SIZE, &dwRead, NULL)) break;
        CloseHandle(latitudeReadOutHandle);
        wprintf(L"%s\n", latitudeBuffer);
    }
}

What am I doing wrong?

P.S. To read the coordinate, one iteration of ReadFile is enough for me.


Solution

  • I finally realized what was wrong in my code. First, I want to thank Ben Voigt for the correct answer. So here is the code that started working for me.

    HANDLE latitudeWriteOutHandle = NULL;
    HANDLE latitudeReadOutHandle = NULL;
    
    SECURITY_ATTRIBUTES latitudeSecurityAttributes;
    latitudeSecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
    latitudeSecurityAttributes.bInheritHandle = TRUE;
    latitudeSecurityAttributes.lpSecurityDescriptor = NULL;
    
    if (CreatePipe(&latitudeReadOutHandle, &latitudeWriteOutHandle, &latitudeSecurityAttributes, 0))
    {
        SetHandleInformation(latitudeReadOutHandle, HANDLE_FLAG_INHERIT, 0);
        //                   ^ - no link needed here
        wchar_t cmd[] = L"powershell.exe -c \"Add-Type -AssemblyName System.Device; $GeoCoordinateWatcher = New-Object System.Device.Location.GeoCoordinateWatcher; $GeoCoordinateWatcher.Start(); Start-Sleep -Milliseconds 2500; $GeoCoordinateWatcher.Position.Location.Latitude 2>&1\"";
        STARTUPINFO latitudeStartUpInfo;
        PROCESS_INFORMATION latitudeProcessInformation;
        ZeroMemory(&latitudeStartUpInfo, sizeof(STARTUPINFO));
        ZeroMemory(&latitudeProcessInformation, sizeof(PROCESS_INFORMATION));
        latitudeStartUpInfo.cb = sizeof(STARTUPINFO);
        latitudeStartUpInfo.hStdOutput = latitudeWriteOutHandle;
        latitudeStartUpInfo.hStdError = latitudeWriteOutHandle;
        latitudeStartUpInfo.dwFlags |= STARTF_USESTDHANDLES;
    
        if (CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &latitudeStartUpInfo, &latitudeProcessInformation))
        {
            CloseHandle(latitudeProcessInformation.hThread);
            CloseHandle(latitudeProcessInformation.hProcess);
            CloseHandle(latitudeWriteOutHandle);
            char latitudeBuffer[COORDINATES_BUFFER_SIZE];
            DWORD dwRead;
            if (ReadFile(latitudeReadOutHandle, latitudeBuffer, COORDINATES_BUFFER_SIZE, &dwRead, NULL)) {
                //       ^ - no link needed here too
                printf("%s\n", latitudeBuffer);
            }
        }
    }
    

    Also for the buffer you need to use char instead of wchar_t.