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
相关产品推荐
相关产品推荐

