TCHAR Array to a concatenate LPCSTR - c++

I am reading a ini file and want to execute a external program (VBS File) after that. But I am having problems with the string types.
This is my code.
LPCTSTR path = _T(".\\my.ini");
TCHAR fileName[500];
int b = GetPrivateProfileString(_T("Paths"), _T("filename"), _T(""), fileName, 500, path);
// fileName = myscript.vbs
// I need to execute "wscript myscript.vbs arg1"
// Execute script file. Doesnt work.
WinExec("wscript " + fileName + " arg1", MINIMZIED);
// OR. Doesnt work.
system("wscript " + fileName + " arg1");
This doesnt work. WinExec wants a LPCSTR but I have the fileName in a TCHAR[] and want to concatenate with some other string.
How can I convert or concatenate it correctly?

From the WinExec() documentation:
This function is provided only for compatibility with 16-bit Windows. Applications should use the CreateProcess function.
Which is CreateProcessW() in your case.
Alternatively, you can use _wsystem().

You need to concatenate the strings using another buffer, for example:
LPCTSTR path = _T(".\\my.ini");
TCHAR fileName[500];
TCHAR command[520];
int b = GetPrivateProfileString(_T("Paths"), _T("filename"), _T(""), fileName, 500, path);
_stprintf_s(command, 520, _T("wscript %.*s arg1"), b, filename);
Then you can use command as needed, eg:
STARTUPINFO si = {};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_MINIMIZED;
PROCESS_INFORMATION pi = {};
if (CreateProcess(NULL, command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
...
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
Or:
#ifdef UNICODE
#define system_t(cmd) _wsystem(cmd)
#else
#define system_t(cmd) system(cmd)
#endif
system_t(command);

Related

How to trim the space from the lpApplicationName of the CreateProcessW function?

I am using CreateProcessW function to create a process. Into this function, passing the first parameter i.e., lpApplicationName with some spaces like as shown below:
In the below path (pszExePath) there are spaces because of this process is not creating.
pszExePath = L"C:\\Program Files\\Common Files\\Installer\\emrinst.exe";
I tried to trim the space by using the below lines but still i am facing the issue.
pszExePath = L"\"";
pszExePath += L"C:\\Program Files\\Common Files\\Installer\\emrinst.exe";
pszExePath += L"\"";
How to trim the space from the lpApplicationName of the CreateProcessW function?
Below is the updated code:
pszExePath = L"C:\\Program Files\\Common Files\\Installer\\emrinst.exe";
strCommandLine = "C:\\Testfolder\\Program Files\\Common Files\\Apps\\emarmain\\emr-test_folder\\Millinnium Files\\test\\test.inf";
std::wstring strFullPath = L"";
strFullPath += pszExePath;
strFullPath += pszCmdLine;
dwExitCode = ::CreateProcessW(NULL, (LPWSTR)strFullPath.c_str(),
0, 0, FALSE, NORMAL_PRIORITY_CLASS, 0,
pszCurrentDirectory, &si, &pi);
still I am getting the error, I think it is exceeding the size of the second parameter "lpCommandLine" of 32,768 characters. Is there any way to increase the size? And also is my code snippet is correct?
As you have no doubt read in the documentation:
If you are using a long file name that contains a space, use quoted strings to indicate where the file name ends and the arguments begin; otherwise, the file name is ambiguous.
So you've got that part right, but you can't concatenate C strings like that. Instead, you can do:
#include <string>
std::wstring cmdline = L"\"";
cmdline += L"C:\\Program Files\\Common Files\\Installer\\emrinst.exe";
cmdline += L"\"";
CreateProcess (NULL, &cmdline [0], ...);
Alternatively, you can pass the application path as yhe first parameter of CreateProcess without quotes.
Following the MSDN documentation for CreateProcess (Unicode) and the code in the question:
1) Add double quotes to the EXE path and the command line.
2) Add whitespace between the pszExePath and strCommandLine.
3) Note: This solution requires a C++11 since CreateProcess requires a non-read only string as lpCommandLine parameter. For prior versions of C++, use _wcsdup to duplicate the string and call for free to release it.
std::wstring pszExePath = L"C:\\Program Files\\Common Files\\Installer\\emrinst.exe";
std::wstring strCommandLine = L"C:\\Testfolder\\Program Files\\Common Files\\Apps\\emarmain\\emr-test_folder\\Millinnium Files\\test\\test.inf";
std::wstring strFullPath = L"\"" + pszExePath + L"\"" + L" " + L"\"" + strCommandLine + L"\"";
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof si;
BOOL result = ::CreateProcessW(NULL, &strFullPath[0], 0, 0, FALSE, NORMAL_PRIORITY_CLASS, 0, pszCurrentDirectory, &si, &pi);
if (result)
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
Edit:
Alternatively, you could try ShellExecuteW (pay attention to double quotes in both paths).
std::wstring pszExePath = L"\"C:\\Program Files\\Common Files\\Installer\\emrinst.exe\"";
std::wstring strCommandLine = L"\"C:\\Testfolder\\Program Files\\Common Files\\Apps\\emarmain\\emr-test_folder\\Millinnium Files\\test\\test.inf\"";
ShellExecute(0, L"open", pszExePath.c_str(), strCommandLine.c_str(), pszCurrentDirectory, SW_NORMAL);

CreateProcess api failing with error code 122 on windows 10

I am using CreateProcess api to start a batch file. The Code works fine on windows 7 but it is failing on Windows 10.
Below is the snippet of code:
CString param; //it holds the very long string of command line arguments
wstring excFile = L"C:\\program files\\BatchFile.bat";
wstring csExcuPath = L"C:\\program files";
wstring exeWithParam = excFile + _T(" ");
exeWithParam = exeWithParam.append(param);
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
TCHAR lpExeWithParam[8191];
_tcscpy_s(lpExeWithParam, exeWithParam.c_str());
BOOL bStatus = CreateProcess(NULL, lpExeWithParam, NULL, NULL, TRUE, CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB, NULL, csExcuPath.c_str(), &si, &pi);
DWORD err;
if (!bStatus)
{
err = GetLastError();
}
With the above code, it is invoking a batch file which will start an executable with given parameters. This code is not working only Windows 10 in our product.
GetLastError is returning error code 122 which code for error "The data area passed to a system call is too small." How to figure out what is causing this error and how it can be resolved?
However, when using the same code in a sample test application is not giving any error and passing.
Any clue/hint why is causing it to fail on Windows 10.
You need to execute cmd.exe with the .bat file as a parameter, don't try to execute the .bat directly.
Also, you don't need lpExeWithParam, you can pass exeWithParam directly to CreateProcess().
Try something more like this instead:
CString param; //it holds the very long string of command line arguments
...
wstring excFile = L"C:\\program files\\BatchFile.bat";
wstring csExcuPath = L"C:\\program files";
wstring exeWithParam = L"cmd.exe /c \"" + excFile + L"\" ";
exeWithParam.append(param);
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi = {};
BOOL bStatus = CreateProcessW(NULL, &exeWithParam[0]/*or exeWithParam.data() in C++17*/, NULL, NULL, TRUE, CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB, NULL, csExcuPath.c_str(), &si, &pi);
if (!bStatus)
{
DWORD err = GetLastError();
...
}
else
{
...
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
Error 122 equates to ERROR_INSUFFICIENT_BUFFER and I think the clue here is "it holds the very long string of command line arguments".
Just how long is it? The limit may be lower on Windows 10 - I recommend you experiment (binary chop).
Also, the documentation for CreateProcess states that you must launch cmd.exe explicitly to run a batch file, so I guess you should do what it says.
I think to run a batch file you must set lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: /c plus the name of the batch file

How can I embed a bat file in my project?

Ive been using the following code to start a bat script.
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
std::wstring env = GetEnvString();
env += L"myvar=boo";
env.push_back('\0'); // somewhat awkward way to embed a null-terminator
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
wchar_t cmdline[] = L"cmd.exe /C C:\\Users\\jrowler\\Desktop\\test\\startsimulator.bat";
if (!CreateProcess(NULL, cmdline, NULL, NULL, false, CREATE_UNICODE_ENVIRONMENT,
(LPVOID)env.c_str(), NULL, &si, &pi))
{
std::cout << GetLastError();
abort();
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
This actually works perfectly, but I was wondering if it is possible to take that bat file and somehow include it in my project? Eventually this project will be distributed to a few different people, and I would like it to be set up in such a way that it does not require the user to download a .bat seperately and make sure it stays in the correct location.
You can write the file out using WriteFile. Since you're using C++ you can eschew the <strsafe.h> function I used in my code (I'm used to C) and build an std::wstring containing the file path with standard string operations, and then use the c_str() method to pass the first argument to CreateFile.
char batContent[] = "#echo off\r\necho Hello World\r\n";
wchar_t temp[MAX_PATH], path[MAX_PATH];
GetEnvironmentVariableW(L"Temp", temp, MAX_PATH);
int len = strlen(batContent);
DWORD dwWritten;
StringCchPrintfW(path, MAX_PATH, L"%s\\filename.bat", temp);
HANDLE hFile = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
if(hFile == INVALID_HANDLE_VALUE)
{
// handle error
}
WriteFile(hFile, batContent, len, &dwWritten, NULL);
CloseHandle(hFile);
// Call CreateProcess with your existing code

How to convert std::wstring to LPCTSTR in C++?

I have Windows registry key value in wstring format. Now I want to pass it to this code (first argument - path to javaw.exe):
std::wstring somePath(L"....\\bin\\javaw.exe");
if (!CreateProcess("C:\\Program Files\\Java\\jre7\\bin\\javaw.exe", <--- here should be LPCTSTR, but I have a somePath in wstring format..
cmdline, // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
0, // Set handle inheritance to FALSE.
CREATE_NO_WINDOW, // ON VISTA/WIN7, THIS CREATES NO WINDOW
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi)) // Pointer to PROCESS_INFORMATION structure.
{
printf("CreateProcess failed\n");
return 0;
}
How can I do that?
Simply use the c_str function of std::w/string.
See here:
http://www.cplusplus.com/reference/string/string/c_str/
std::wstring somePath(L"....\\bin\\javaw.exe");
if (!CreateProcess(somePath.c_str(),
cmdline, // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
0, // Set handle inheritance to FALSE.
CREATE_NO_WINDOW, // ON VISTA/WIN7, THIS CREATES NO WINDOW
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi)) // Pointer to PROCESS_INFORMATION structure.
{
printf("CreateProcess failed\n");
return 0;
}
LPCTSTR is an old relic. It's a hybrid typedef that either defines char* if you are using multi-byte strings or wchar_t* if you are using Unicode. In Visual Studio, this can be changed in general project's settings under "Character Set".
If you are using Unicode, then:
std::wstring somePath(L"....\\bin\\javaw.exe");
LPCTSTR str = somePath.c_str(); // i.e. std::wstring to wchar_t*
If you are using multi-byte, then use this helper:
// wide char to multi byte:
std::string ws2s(const std::wstring& wstr)
{
int size_needed = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), int(wstr.length() + 1), 0, 0, 0, 0);
std::string strTo(size_needed, 0);
WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), int(wstr.length() + 1), &strTo[0], size_needed, 0, 0);
return strTo;
}
i.e. std::wstring to std::string that will contain multi-byte string and then to char*:
LPCTSTR str = ws2s(somePath).c_str();
The safest way when interacting from stdlib classes with TCHARs is to use std::basic_string<TCHAR> and surround raw strings with the TEXT() macro (since TCHAR can be narrow and wide depending on project settings).
std::basic_string<TCHAR> somePath(TEXT("....\\bin\\javaw.exe"));
Since you won't win style contests doing this ... another correct method is to use explicitly the narrow or wide version of a WinAPI function. E.g. in that particular case:
with std::string use CreateProcessA (which uses LPCSTR which is a typedef of char*)
with std::u16string or std::wstring use CreateProcessW (which uses LPCWSTR which is a typedef of wchar_t*, which is 16-bit in Windows)
In C++17, you could do:
std::filesystem::path app = "my/path/myprogram.exe";
std::string commandcall = app.filename.string() + " -myAwesomeParams";
// define si, pi
CreateProcessA(
const_cast<LPCSTR>(app.string().c_str()),
const_cast<LPSTR>(commandcall.c_str()),
nullptr, nullptr, false, CREATE_DEFAULT_ERROR_MODE, nullptr, nullptr,
&si, &pi)
Finally decided to use CreateProcessW as paulm mentioned with a little corrections - values need to be casted (otherwise I get error):
STARTUPINFOW si;
memset(&si, 0, sizeof (STARTUPINFOW));
si.cb = sizeof (STARTUPINFOW);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = FALSE;
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof (PROCESS_INFORMATION));
std::wstring cmdline(L" -jar install.jar");
if (!CreateProcessW((LPCWSTR)strKeyValue.c_str(),
(LPWSTR)cmdline.c_str(), // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
0, // Set handle inheritance to FALSE.
CREATE_NO_WINDOW, // ON VISTA/WIN7, THIS CREATES NO WINDOW
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi)) // Pointer to PROCESS_INFORMATION structure.
{
printf("CreateProcess failed\n");
return 0;
}

Using wide strings with ifstream::open or multibyte strings with CreateProcess

I have a piece of code in which I need to use a string with both ifstream::open and CreateProcess, something like
//in another file
const char* FILENAME = "C:\\...blah blah\\filename.bat";
// in main app
std::ifstream is;
is.open(FILENAME);
// ...do some writing
is.close();
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
std::string cmdLine = "/c " + FILENAME;
if( !CreateProcess( "c:\\Windows\\system32\\cmd.exe",
cmdLine.c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) )
{
return GetLastError();
}
CreateProcess requires a LPCWSTR, so to use the string with CreateProcess I would need to declare the filename and 'cmdLine' as std::wstring, but ifstream::open doesn't take wide strings...I can't figure a way to get around this. I always seem to run into problems with unicode vs multibyte strings.
Any ideas?
Thanks.
I'm assuming you defined UNICODE. You can change STARTUPINFO to STARTUPINFOA and CreateProcess to CreateProcessA and it should work fine (it did for me).
I don't think it'll like the + operation though. Explicitly convert one char array to a string.
std::string cmdLine = (std::string)"/c " + FILENAME;
Finally, you're going to need quotes around the beginning and end of FILENAME if it has a space.
const char FILENAME = "\"C:\\Program Files\\Company\\Program\\program.exe\"";
^ ^ ^
Edit:
Try putting this below your string declaration:
char charCmdLine [MAX_PATH + 3]; //"/c " is 3 extra chars
strncpy (charCmdLine, cmdLine.c_str(), MAX_PATH + 3);
Then, use charCmdLine in CreateProcess instead of cmdLine.c_str().