I'm working on a Windows C++ project using Visual Studio 2022 with the Unicode build, and I'm encountering an issue with ShellExecuteExA
. It intermittently fails with ERROR_FILE_NOT_FOUND
(error code 2), even though the file path is correct. The issue seems related to the handling of the file path string passed to ShellExecuteExA
.
I added logging to print the file path before and after calling ShellExecuteExA
, but noticed that the "After" log does not get printed. The "Before" log prints the correct file path, but after ShellExecuteExA
is called, the "After" log is not output, suggesting something may be altering or overwriting the string before the second log call.
Here’s the current log output for a test run:
LOG_CONSOLE("Before: %s", _strdup(execinfo.lpFile)); // Print the file path before execution
BOOL bIsExecute = ShellExecuteExA(&execinfo); // This intermittently fails with ERROR_FILE_NOT_FOUND
LOG_CONSOLE("After: %s", _strdup(execinfo.lpFile)); // This log's Path does not appear after execution
Before: C:\Users\<UserName>\Desktop\MyProgram.exe
After: // After log's Path does not appear
The "After" log's path does not appear, even though the path seems valid in the "Before" log. This issue only occurs when using the raw pointer returned by MakeMiddlewarePath()
.
ShellExecuteExA
sporadically returns FALSE
.GetLastError()
gives 2 (ERROR_FILE_NOT_FOUND), but the file definitely exists at the specified path.ShellExecuteExA
, when inspecting execinfo.lpFile
, the string seems to be altered or overwritten, which does not happen when I cache the string using CString
.// Windows C++ (Unicode build, Visual Studio 2022)
SHELLEXECUTEINFOA execinfo;
ZeroMemory(&execinfo, sizeof(execinfo));
execinfo.cbSize = sizeof(execinfo);
execinfo.lpVerb = TEXT("open");
execinfo.lpFile = MakeMiddlewarePath(); // File path
execinfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;
execinfo.nShow = SW_SHOWMINIMIZED;
LOG_CONSOLE("Before: %s", _strdup(execinfo.lpFile));
BOOL bIsExecute = ShellExecuteExA(&execinfo); // <‑‑ occasionally fails
LOG_CONSOLE("After: %s", _strdup(execinfo.lpFile)); // After log's Path does not appear after execution
MakeMiddlewarePath()
implementation:CString MakeMiddlewarePath()
{
char szPath[MAX_PATH] = {};
GetModuleFileName(nullptr, szPath, MAX_PATH);
strcpy(strrchr(szPath, '\\') + 1, "MyProgram.exe");
return CString(szPath);
}
When I cache the string using CString
, the issue is resolved:
CString path = MakeMiddlewarePath(); // Cache the string in a variable
execinfo.lpFile = path; // Pass the variable
BOOL ok = ShellExecuteExA(&execinfo); // Now works 100% of the time
This caching method ensures the string remains valid and prevents the overwrite issue.
MakeMiddlewarePath()
allow ShellExecuteExA
to overwrite that buffer, while caching the string in a CString
prevents it?ShellExecuteExA
that the buffer must remain valid and writable during the call?Any documentation or experience explaining this behavior would be greatly appreciated!
/MTd
, C++20ShellExecuteExA
) intentionally for legacy code.The problem is indeed with the MakeMiddlewarePath()
function.
When you do
return CString(szPath);
you create a temporary object. Its life-time will end as soon as the assignment to execinfo.lpFile
is finished.
That will leave you with a dangling and invalid pointer, and undefined behavior when ShellExecuteExA
tries to dereference it.
This is why it works when you initialize another CString
object, because its life-time is longer than the call to ShellExecuteExA
.