You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WinAPI中CreatePipe+CreateProcess场景下ReadFile无法读取输出问题排查

问题:C语言调用PowerShell获取地理位置时CreatePipe/CreateProcess陷入无限循环

我正在用C语言编写一个获取地理位置的应用程序。为获取坐标,我最初使用_wpopen调用PowerShell,代码可正常运行,但使用_wpopen时会弹出终端窗口,我需要程序在后台运行。于是改用WinAPI的CreatePipe和CreateProcess,再通过ReadFile读取输出,却陷入无限循环且未将结果写入缓冲区。

初始可用代码(使用_wpopen)

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;
}

有问题的代码(使用CreatePipe/CreateProcess)

#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);
    }
}

请问我哪里出错了?注:我只需要一次ReadFile调用就能读取到坐标。


错误分析与修正

你的代码存在以下几个关键问题:

  • 变量名笔误:latitudeStartUpInfo.hStdError = latitudeWriteReadOutHandle; 中的latitudeWriteReadOutHandle是未定义变量,应改为latitudeWriteOutHandle(命令中已用2>&1将错误重定向到标准输出)。

  • 提前关闭进程句柄:调用CreateProcess后立刻关闭latitudeProcessInformation.hProcess,会导致无法等待子进程退出。此时子进程仍持有管道写端,ReadFile会一直等待数据,陷入无限循环。需等待子进程结束后再读取输出。

  • ReadFile参数与逻辑错误:

    • ReadFile的第三个参数是字节数,你传入的是wchar_t数量,需改为COORDINATES_BUFFER_SIZE * sizeof(wchar_t),避免读取不完整或缓冲区溢出;
    • 原循环逻辑错误:若ReadFile返回FALSE(如管道暂时为空)会无限循环,需判断读取结果与管道状态。
  • 未设置后台运行标志:CreateProcess需添加CREATE_NO_WINDOW标志,确保PowerShell不弹出终端窗口。

修正后的代码

#define COORDINATES_BUFFER_SIZE 4096
#define COORDINATES_SIZE 32  // 与原代码保持一致的坐标长度定义

BOOL 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;

    // 创建管道,失败则返回错误
    if (!CreatePipe(&latitudeReadOutHandle, &latitudeWriteOutHandle, &latitudeSecurityAttributes, 0))
    {
        wcsncpy_s(latitudeBuffer, COORDINATES_BUFFER_SIZE, L"NULL", COORDINATES_BUFFER_SIZE);
        return FALSE;
    }
    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;
    latitudeStartUpInfo.hStdError = latitudeWriteOutHandle;  // 修正笔误,错误重定向到标准输出
    latitudeStartUpInfo.dwFlags |= STARTF_USESTDHANDLES;

    // 创建后台进程,隐藏终端窗口
    if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &latitudeStartUpInfo, &latitudeProcessInformation))
    {
        CloseHandle(latitudeReadOutHandle);
        CloseHandle(latitudeWriteOutHandle);
        wcsncpy_s(latitudeBuffer, COORDINATES_BUFFER_SIZE, L"NULL", COORDINATES_BUFFER_SIZE);
        return FALSE;
    }

    // 关闭父进程持有的写句柄,避免管道无法关闭
    CloseHandle(latitudeWriteOutHandle);

    // 等待子进程执行完毕
    WaitForSingleObject(latitudeProcessInformation.hProcess, INFINITE);

    DWORD dwRead;
    BOOL readSuccess = FALSE;
    // 初始化缓冲区,避免垃圾数据
    ZeroMemory(latitudeBuffer, COORDINATES_BUFFER_SIZE * sizeof(wchar_t));
    
    // 读取管道输出(一次读取即可满足需求)
    if (ReadFile(latitudeReadOutHandle, latitudeBuffer, COORDINATES_BUFFER_SIZE * sizeof(wchar_t), &dwRead, NULL) && dwRead > 0)
    {
        // 转换为宽字符字符串并添加终止符
        size_t strLen = dwRead / sizeof(wchar_t);
        if (strLen < COORDINATES_BUFFER_SIZE)
        {
            latitudeBuffer[strLen] = L'\0';
            // 与原代码一致的坐标格式处理
            if ('0' <= latitudeBuffer[0] && latitudeBuffer[0] <= '9')
            {
                latitudeBuffer[COORDINATES_SIZE] = L'\0';
                wchar_t* latitudePoint = wcschr(latitudeBuffer, L'.');
                if (latitudePoint != NULL) *latitudePoint = L'-';
                readSuccess = TRUE;
            }
        }
    }

    // 清理所有句柄
    CloseHandle(latitudeReadOutHandle);
    CloseHandle(latitudeProcessInformation.hThread);
    CloseHandle(latitudeProcessInformation.hProcess);

    if (!readSuccess)
    {
        wcsncpy_s(latitudeBuffer, COORDINATES_BUFFER_SIZE, L"NULL", COORDINATES_BUFFER_SIZE);
        return FALSE;
    }
    wprintf(L"%s\n", latitudeBuffer);
    return TRUE;
}

内容的提问来源于stack exchange,提问作者MiZentUi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 02:05:58