MSVC下调用带EXTENDED_STARTUPINFO_PRESENT的CreateProcessW返回错误码87
解决MSVC下CreateProcessW带EXTENDED_STARTUPINFO_PRESENT时的错误码87问题
错误根源分析
错误码87(ERROR_INVALID_PARAMETER)来自以下几个关键参数错误:
STARTUPINFOEXW的cb成员设置时机错误:必须在初始化结构体后立即设置startup_info.StartupInfo.cb = sizeof(STARTUPINFOEXW),否则CreateProcessW会认为结构体大小不合法。- 继承句柄列表包含无效项:你已经通过
SetHandleInformation取消了m_hChildStd_OUT_Rd的继承属性,却仍将它加入PROC_THREAD_ATTRIBUTE_HANDLE_LIST,导致参数冲突。该读句柄不需要被子进程继承,应从列表中移除。 bInheritHandles参数值错误:使用PROC_THREAD_ATTRIBUTE_HANDLE_LIST时,此参数必须设为TRUE,否则子进程无法继承指定的句柄。- 命令行参数传递不安全:直接强制转换
std::wstring::c_str()得到的const wchar_t*传入CreateProcessW会触发未定义行为,因为函数会修改传入的命令行缓冲区。 - 资源未正确清理:虽然不是直接引发错误的原因,但管道句柄、进程/线程句柄和属性列表需要在结束时释放,避免资源泄漏。
修复后的完整代码
#include <windows.h> #include <processthreadsapi.h> #include <WinBase.h> #include <iostream> #include <vector> using namespace std; void TestFunctionW() { STARTUPINFOEXW startup_info = { 0 }; PROCESS_INFORMATION process_info = { 0 }; // 必须先设置cb成员,这是使用STARTUPINFOEX的核心要求 startup_info.StartupInfo.cb = sizeof(STARTUPINFOEXW); SECURITY_ATTRIBUTES saAttr; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; HANDLE m_hChildStd_OUT_Rd = NULL; HANDLE m_hChildStd_OUT_Wr = NULL; if (!CreatePipe(&m_hChildStd_OUT_Rd, &m_hChildStd_OUT_Wr, &saAttr, 0)) { cerr << "CreatePipe Failed" << endl; goto Cleanup; } // 取消读句柄的继承属性,无需加入继承列表 if (!SetHandleInformation(m_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { cerr << "SetHandleInformation Failed" << endl; goto Cleanup; } // 重定向标准输出和错误输出到管道,标准输入沿用父进程的输入 startup_info.StartupInfo.hStdError = m_hChildStd_OUT_Wr; startup_info.StartupInfo.hStdOutput = m_hChildStd_OUT_Wr; startup_info.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup_info.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; BOOL fSuccess = TRUE; SIZE_T size = 0; LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL; // 仅将需要继承的管道写句柄加入列表 int cHandlesToInherit = 1; HANDLE rgHandlesToInherit[] = { m_hChildStd_OUT_Wr }; // 第一次调用获取属性列表所需内存大小 if (!InitializeProcThreadAttributeList(NULL, 1, 0, &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { fSuccess = FALSE; cerr << "InitializeProcThreadAttributeList (size query) Failed" << endl; goto Cleanup; } // 分配属性列表内存 lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size)); if (!lpAttributeList) { fSuccess = FALSE; cerr << "HeapAlloc Failed" << endl; goto Cleanup; } // 初始化属性列表 if (!InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &size)) { fSuccess = FALSE; cerr << "InitializeProcThreadAttributeList Failed" << endl; goto Cleanup; } // 更新属性列表,添加继承句柄 if (!UpdateProcThreadAttribute(lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, rgHandlesToInherit, cHandlesToInherit * sizeof(HANDLE), NULL, NULL)) { fSuccess = FALSE; cerr << "UpdateProcThreadAttribute Failed" << endl; goto Cleanup; } startup_info.lpAttributeList = lpAttributeList; cerr << "Handle stuff succeeded.." << endl; DWORD flags = EXTENDED_STARTUPINFO_PRESENT; // 使用vector创建可修改的命令行缓冲区,避免const指针问题 vector<wchar_t> cmd_buf(L"ping.exe", L"ping.exe" + 8); BOOL ret = CreateProcessW(NULL, cmd_buf.data(), NULL, NULL, TRUE, flags, NULL, NULL, (LPSTARTUPINFOW)&startup_info, &process_info); if (!ret) { DWORD err = GetLastError(); cout << "Failed: code " << err << endl; } else { // 等待子进程完成,读取管道输出 WaitForSingleObject(process_info.hProcess, INFINITE); char buffer[4096]; DWORD bytesRead; while (ReadFile(m_hChildStd_OUT_Rd, buffer, sizeof(buffer)-1, &bytesRead, NULL) && bytesRead > 0) { buffer[bytesRead] = '\0'; cout << buffer; } } Cleanup: // 释放所有资源 if (lpAttributeList) { DeleteProcThreadAttributeList(lpAttributeList); HeapFree(GetProcessHeap(), 0, lpAttributeList); } if (m_hChildStd_OUT_Rd) CloseHandle(m_hChildStd_OUT_Rd); if (m_hChildStd_OUT_Wr) CloseHandle(m_hChildStd_OUT_Wr); if (process_info.hProcess) CloseHandle(process_info.hProcess); if (process_info.hThread) CloseHandle(process_info.hThread); } int main(int argc, char* argv[]) { TestFunctionW(); return 0; }
关键修复说明
- 提前设置
cb成员:确保CreateProcessW正确识别扩展启动信息结构体,这是使用EXTENDED_STARTUPINFO_PRESENT的必要前提。 - 精简继承句柄列表:只保留需要被子进程继承的管道写句柄,避免无效句柄导致的参数错误。
- 修正
bInheritHandles参数:设为TRUE以允许子进程继承属性列表中指定的句柄。 - 安全传递命令行:使用
std::vector<wchar_t>创建可修改的缓冲区,符合CreateProcessW对命令行参数的要求。 - 完善资源清理:通过
goto Cleanup统一处理资源释放,避免内存和句柄泄漏。 - 修复
main函数参数:将错误的char* argv修正为标准的char* argv[]。
内容的提问来源于stack exchange,提问作者Derek Magee
相关产品推荐
相关产品推荐

