0

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.

8
  • 1
    What a horrifically convoluted way of calling a native API from native code. I've seen bad, really bad, but this is a new low. Commented Feb 12, 2024 at 20:37
  • One thing I have different in some code of mine is the close of hProcess is late, after the ReadFile is done. You may also want to wait for the process to finish (WaitForSingleObject) after the read. Commented Feb 12, 2024 at 20:48
  • 1
    Windows is not linux where you call a command line tool to do the work for you and you pipe in and out. Use ILocation. Commented Feb 12, 2024 at 21:22
  • I see a lack of adequate error handling. You are not verifying if CreatePipe() is successful, but more importantly you are not breaking the reading loop if ReadFile() fails. "To read the coordinate, one iteration of ReadFile is enough for me" - then there is no point in using a loop at all. Ether you get the data or you don't. That said, why are you not simply using the GeoLocation object directly? You shouldn't need to shell out to PowerShell if the object is accessible to native languages via WinRT or COM. Commented Feb 12, 2024 at 21:26
  • Remy Lebeau can you tell me how to use object GeoLocation in C? Commented Feb 13, 2024 at 6:20

2 Answers 2

2

You are passing the wrong end of the pipe to the child.

latitudeStartUpInfo.hStdOutput = latitudeReadOutHandle;
latitudeStartUpInfo.hStdError = latitudeReadOutHandle;

should instead be

latitudeStartUpInfo.hStdOutput = latitudeWriteOutHandle;
latitudeStartUpInfo.hStdError = latitudeWriteOutHandle;
Sign up to request clarification or add additional context in comments.

1 Comment

I fixed it, but still ReadFile doesn't read anything
-1

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.

1 Comment

Please explain what the problem is, so that the next person with this issue knows what to fix. I did a diff of the question and the answer, and found the only changes were to error checking. So where was the actual problem?

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.