关于SetEnvironmentStringsW在空环境进程中调用返回ERROR_INVALID_PARAMETER的技术问询
SetEnvironmentStringsW 在空环境进程中调用返回 ERROR_INVALID_PARAMETER 的问题
大家好,我最近调试Windows环境变量相关逻辑时,碰到了SetEnvironmentStringsW函数的一个奇怪问题,想请教下有没有人遇到过或者知道原因。
正常来说,SetEnvironmentStringsW应该能用来恢复之前通过GetEnvironmentStringsW备份的环境变量块,这本来是个很方便的方式来临时修改然后恢复环境。但我发现,如果一个进程是用空环境创建的——也就是调用CreateProcessW时,lpEnvironment参数传L"\0",同时指定CREATE_UNICODE_ENVIRONMENT标志——之后再调用SetEnvironmentStringsW去恢复之前获取的环境块,就会直接失败:返回0,调用GetLastError()得到ERROR_INVALID_PARAMETER。这个问题我在Win7、Win10、Win11的x64版本上都复现了。
我写了个小工具passenv来复现这个问题,它支持三种调用方式:
passenv 1:给子进程传递只有一个变量的环境passenv 0:给子进程传递空环境- 直接运行
passenv:当它作为子进程被递归调用时,会输出最关键的测试结果
测试程序源码
文件名:passenv.c
// Demonstrate an issue in `SetEnvironmentStringsW` kernel32 function // Compile: // $ gcc passenv.c -municode -o passenv #include <stdio.h> #include <windows.h> void ShowGetEnvironmentStringsW() { printf("--- get environment ---\n"); WCHAR* env_block = GetEnvironmentStringsW(); if (env_block) { for (WCHAR* p = env_block; *p; ++p) { wprintf(L"%ls\n", p); while (*p) ++p; } FreeEnvironmentStringsW(env_block); } printf("-----------------------\n"); } void ShowSetEnvironmentStringsW(WCHAR* env) { printf("SetEnvironmentStringsW ... "); DWORD result = SetEnvironmentStringsW(env); if (result) { printf("OK\n"); } else { printf("Error %u\n", GetLastError()); } } void ShowGetEnvironmentVariableW(WCHAR* name) { WCHAR value[1023+1] = L"NOP"; DWORD size = sizeof(value); SetLastError(ERROR_SUCCESS); DWORD written = GetEnvironmentVariableW(name, value, size); if (written && (written < size)) { wprintf(L"%ls: '%ls' (%u)\n", name, value, written); } else { DWORD error_code = GetLastError(); if (error_code == ERROR_ENVVAR_NOT_FOUND) { wprintf(L"Variable '%ls' was not found.\n", name); } else { wprintf(L"Error reading %ls: %u\n", name, error_code); } wprintf(L"%ls: '%ls' (%u)\n", name, value, written); } } void ShowSetEnvironmentVariableW(WCHAR* name, WCHAR* value) { wprintf(L"Set var '%ls' to value '%ls' ... ", name, value); DWORD result = SetEnvironmentVariableW(name, value); if (result) { printf("OK\n"); } else { DWORD error_code = GetLastError(); if (error_code == ERROR_INVALID_PARAMETER) { printf("Error: %u (ERROR_INVALID_PARAMETER)\n", error_code); } else { printf("Error: %u\n", error_code); } } } int restart_self(WCHAR* cmd, WCHAR* env) { STARTUPINFO si; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); PROCESS_INFORMATION pi; ZeroMemory( &pi, sizeof(pi) ); if (!CreateProcessW( NULL, cmd, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, env, NULL, &si, &pi )) { printf( "CreateProcess failed (%u).\n", GetLastError() ); return 1; } // Wait until child process exits. WaitForSingleObject( pi.hProcess, INFINITE ); DWORD code; GetExitCodeProcess(pi.hProcess, &code); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return code; } int wmain(int argc, wchar_t* argv[]) { if ((argc == 2) && (0==wcscmp(argv[1], L"0"))) { printf("arg 0\n"); return restart_self(argv[0], L"\0"); } else if ((argc == 2) && (0==wcscmp(argv[1], L"1"))) { printf("arg 1\n"); return restart_self(argv[0], L"a=1\0"); } else { WCHAR* old_env = GetEnvironmentStringsW(); ShowGetEnvironmentStringsW(); ShowSetEnvironmentVariableW(L"TEST", L"test"); ShowGetEnvironmentVariableW(L"TEST"); ShowGetEnvironmentStringsW(); ShowSetEnvironmentVariableW(L"TEST", NULL); ShowGetEnvironmentVariableW(L"TEST"); ShowGetEnvironmentStringsW(); ShowSetEnvironmentStringsW(old_env); ShowGetEnvironmentStringsW(); FreeEnvironmentStringsW(old_env); } return 0; }
编译命令
$ gcc passenv.c -municode -o passenv
测试结果
情况1:子进程有正常环境(passenv 1)
这种情况下,SetEnvironmentStringsW能正常工作,输出如下:
$> passenv 1 arg 1 --- get environment --- a=1 ----------------------- Set var 'TEST' to value 'test' ... OK TEST: 'test' (4) --- get environment --- a=1 TEST=test ----------------------- Set var 'TEST' to value '(null)' ... OK Variable 'TEST' was not found. TEST: 'NOP' (0) --- get environment --- a=1 ----------------------- SetEnvironmentStringsW ... OK --- get environment --- a=1 -----------------------
情况2:子进程是空环境(passenv 0)
这种情况下,最后一步调用SetEnvironmentStringsW会失败,返回ERROR_INVALID_PARAMETER(错误码87),输出如下:
$> passenv 0 arg 0 --- get environment --- ----------------------- Set var 'TEST' to value 'test' ... OK TEST: 'test' (4) --- get environment --- TEST=test ----------------------- Set var 'TEST' to value '(null)' ... OK Variable 'TEST' was not found. TEST: 'NOP' (0) --- get environment --- ----------------------- SetEnvironmentStringsW ... Error 87 --- get environment --- -----------------------
我想知道这是SetEnvironmentStringsW的已知限制吗?官方文档里好像没提到这个情况,有没有什么workaround可以在空环境的进程里正常恢复环境变量块?
内容来源于stack exchange
相关产品推荐
相关产品推荐

