I'm trying to use devcon.exe to check the status of various pieces of hardware. In the example I'm trying to check my SATA HBA status but devcon is whining about it. Here's the code:
int main(int argc, char** argv) {
std::string cmdLine("\"C:\\Users\\afalanga\\Documents\\Visual Studio 2010\\Projects\\PlayGround\\Debug\\devcon.exe\" status PCI\\VEN_8086^&DEV_3A22^&SUBSYS_75201462^&REV_00");
char* pCmdLine(new char[cmdLine.length() + 10]);
memset(pCmdLine, 0, cmdLine.length() + 10);
for(int i(0); i < cmdLine.length(); i++)
pCmdLine[i] = cmdLine.at(i);
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = {0};
if(!CreateProcess(NULL, pCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
std::cout << "Create child process failed. Error code: "
<< GetLastError() << std::endl;
return 1;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}
The problem is that when the above executes, devcon complains that, "No matching devices were found." However, if I copy/paste that command line from the debugger into my command prompt and hit the enter key (or course deleting the all encompassing quotes that the debugger puts around it), the command executes perfectly as expected.
What am I getting wrong in my handing of the string? What's above is the result of reading the CreateProcess() docs on MSDN (found out that the first argument isn't necessarily needed and the cmd args shouldn't go there at all). The reason I'm allocating 10 extra bytes of memory to copy the string into is so that "whatever" may change down in the guts of the CreateProcess() function can do so without stomping on other memory. At least, that was my thought when I did that.
Command line metacharacters are parsed by the command processor. In particular you are using the ^ to prevent CMD.EXE from breaking the command at the ampersand. But you are executing the program directly, bypassing CMD.EXE. Therefore, the ^ passes through to devcon.exe who gets confused by them.
Solution: Remove the ^ characters.
Your problem is actually the opposite of your title. The command line you passed to CreateProcess is being passed directly to the application exactly as you specified it.
std::string cmdLine("\"C:\\Users\\afalanga\\Documents\\Visual Studio 2010\\Projects\\PlayGround\\Debug\\devcon.exe\" status PCI\\VEN_8086^&DEV_3A22^&SUBSYS_75201462^&REV_00
Presumably the ^ carets in there are residues from a command entered in the command line interpreter, where they serve to turn off the special meaning of &.
Simply remove the carets.
Also note that your current code leaks memory.
To avoid that, do e.g.
string commandLineArg = cmdLine + '\0';
... CreateProcess( 0, &commandLineArg[0], ... )
Can you try like this:
CreateProcess(NULL, pCmdLine.c_str(), ...);
I used:
TCHAR var[] = _T(" C:\\filepathe\\foo");
CreateProcess(NULL, var,...);
Related
This question already has answers here:
CreateProcess doesn't pass command line arguments
(8 answers)
C++ Winapi CreateProcess issue with command line
(1 answer)
Closed 3 months ago.
Quoting the whole argument with an extra quotes, does not work.
char command[256] = R"(""D:\src has space" "E:\dest has space" /I /Y /E")";
auto process = CreateProcessA(
//"Project1.exe", //my test program below
R"(C:\Windows\system32\xcopy.exe)",
command,
nullptr,
nullptr,
true,
CREATE_NO_WINDOW,
nullptr,
".",
&si, &pi
);
I got Invalid number of parameters.
I wrote a little program to print out the arguments, one by one and then showing in a messagebox.
#include <windows.h>
#include <string>
int main(int argc, char** argv)
{
std::string s = std::string{"argc = "} + std::to_string(argc);
s += '\n';
for (int i = 0; i<argc; ++i)
{
(s += argv[i]) += '\n';
}
MessageBoxA(NULL, s.data(), "Caption", 0);
}
This is obviously not correct.
Removing the extra quotes does not work either
char command[256] = R"("D:\src has space" "E:\dest has space" /I /Y /E)";
I got 0 File(s) copied
I've forked a child process which then calls a bash script using execv, the way i'm passing command line arguments to the script, It does not print first argument on doing echo $1 inside the script.
std::string s = std::to_string(c_no);
char *args[] = {(char *)s.c_str(), NULL};
pid_t pid = fork();
if(pid == 0){
execv("./ckpnt.sh", &args[0]);
}
consider c_no to be any integer.
What is the correct way to do this?
I've already refrenced this link How to pass command line arguments from C program to the bash script? but this answer uses system system call and i try to not use that.
First argument passed to a program is its name so currently your number ends up in $0. args should be:
char *args[] = {"./ckpnt.sh", (char *)s.c_str(), NULL};
I am aware that this may well be a duplicate. However, I am struggling to actually get a working answer.
What I am trying to do is list all of the folders in the working directory. Below is some code that I have adapted from the MS website (http://msdn.microsoft.com/en-us/library/windows/desktop/aa365200(v=vs.85).aspx)
This gives the output:
Filname:52428
I have checked the folder - and there are three folders that I am wanting to list 'Vidoe' 'John' 'David' I am not sure as to why it is printing out the result above.
I do not want to use Boost - nor to download any third party plugings.
int main(int argc, char** argv)
{
HANDLE hFind = INVALID_HANDLE_VALUE;
WIN32_FIND_DATA ffd;
//The Directory where the .exe is run from.
hFind = FindFirstFile(TEXT(".\\Players\\*"), &ffd);
do
{
Sleep(1000);
bool isDirectory = ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
if(isDirectory)
{
cout << "DirectoryName: " << *ffd.cFileName << endl;
}
else
{
cout << "FileName: " << *ffd.cFileName << endl;
}
}while(FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
}
EDIT:
I do not have a specific way that I want to do this, all I am wanting to do is output the Folders in the directory - I do not care how it is done.
In …
*ffd.cFileName
remove the *.
Also remove the call to Sleep.
Also remove the silly TEXT macro call, use wide string literals like L"blah".
Oh I forgot, also replace the do loop with a while loop (or for loop), because it's not sure that the FindFirstFile call will succeed.
Oh, and important, for the debug output use wcout, not cout. The latter doesn't know anything about output of Unicode strings. But wcout can handle them.
The output you're getting,
52428
appears to be wchar_t value 0xCCCC, treated as an integer by cout, which value indicates uninitialized storage, which implies that the FindFirstFile call failed.
So, also be sure about the current directory when you run the program. A good idea is to run it from the command line. Then you're sure.
I'm working on some path-parsing C++ code and I've been experimenting with a lot of the Windows APIs for this. Is there a difference between PathGetArgs/PathRemoveArgs and a slightly-massaged CommandLineToArgvW?
In other words, aside from length/cleanness, is this:
std::wstring StripFileArguments(std::wstring filePath)
{
WCHAR tempPath[MAX_PATH];
wcscpy(tempPath, filePath.c_str());
PathRemoveArgs(tempPath);
return tempPath;
}
different from this:
std::wstring StripFileArguments(std::wstring filePath)
{
LPWSTR* argList;
int argCount;
std::wstring tempPath;
argList = CommandLineToArgvW(filePath.c_str(), &argCount);
if (argCount > 0)
{
tempPath = argList[0]; //ignore any elements after the first because those are args, not the base app
LocalFree(argList);
return tempPath;
}
return filePath;
}
and is this
std::wstring GetFileArguments(std::wstring filePath)
{
WCHAR tempArgs[MAX_PATH];
wcscpy(tempArgs, filePath.c_str());
wcscpy(tempArgs, PathGetArgs(tempArgs));
return tempArgs;
}
different from
std::wstring GetFileArguments(std::wstring filePath)
{
LPWSTR* argList;
int argCount;
std::wstring tempArgs;
argList = CommandLineToArgvW(filePath.c_str(), &argCount);
for (int counter = 1; counter < argCount; counter++) //ignore the first element (counter = 0) because that's the base app, not args
{
tempArgs = tempArgs + TEXT(" ") + argList[counter];
}
LocalFree(argList);
return tempArgs;
}
? It looks to me like PathGetArgs/PathRemoveArgs just provide a cleaner, simpler special-case implementation of the CommandLineToArgvW parsing, but I'd like to know if there are any corner cases in which the APIs will behave differently.
The functions are similar but not exactly the same - mostly relating to how quoted strings are handled.
PathGetArgs returns a pointer to the first character following the first space in the input string. If a quote character is encountered before the first space, another quote is required before the function will start looking for spaces again. If no space is found the function returns a pointer to the end of the string.
PathRemoveArgs calls PathGetArgs and then uses the returned pointer to terminate the string. It will also strip a trailing space if the first space encountered happened to be at the end of the line.
CommandLineToArgvW takes the supplied string and splits it into an array. It uses spaces to delineate each item in the array. The first item in the array can be quoted to allow spaces. The second and subsequent items can also be quoted, but they support slightly more complex processing - arguments can also include embedded quotes by prepending them with a backslash. For example:
"c:\program files\my app\my app.exe" arg1 "argument 2" "arg \"number\" 3"
This would produce an array with four entries:
argv[0] - c:\program files\my app\my app.exe
argv[1] - arg1
argv[2] - argument 2
argv[3] - arg "number" 3
See the CommandLineToArgVW docs for a full description of the parsing rules, including how you can have embedded backslashes as well as quotes in the arguments.
Yes I've observed a different behaviour with the current SDK (VS2015 Update 3 + Windows 1607 Anniversary SDK with SDK version set to 8.1):
Calling CommandLineToArgvW with an empty lpCmdLine (what you get from wWinMain when no arguments were passed) returns the program path and filename, which will be split-up on every space. But this was not specified in the parameter, it must have done that itself but failed to think about ignoring spacing that path itself:
lpCmdLine = ""
argv[0] = C:\Program
argv[1] = Files\Vendor\MyProgram.exe
Calling CommandLineToArgvW with lpCmdLine containing parameters, does not include the program path and name, so works as expected (so long as there are no further spaces in the parameters...):
lpCmdLine = "One=1 Two=\"2\""
argv[0] = One=1
argv[1] = Two=2
Note it also strips any other quotes inside the parameters when passed.
CommandLineToArgvW doesn't like the first parameter in the format Text=\"Quoted spaces\" so if you try to pass lpCmdLine to it directly it incorrectly splits the key=value pairs if they have spaces:
lpCmdLine = "One=\"Number One\" Two=\"Number Two\""
argv[0] = One=\"Number
argv[1] = One\"
argv[2] = Two=\"Number
argv[3] = Two\"
It's kind of documented here:
https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx
But this kind of behaviour with spaces in the program path was not expected. It seems like a bug to me. I'd prefer the same data to be processed in both situations. Because if I really want the path to the executable I'd call GetCommandLineW() instead.
The only sensible consistent solution in my opinion is to totally ignore lpCmdLine and call GetCommandLineW(), pass the results to CommandLineToArgvW() then skip the first parameter if you are not interested in the program path. That way, all combinations are supported, i.e. path with and without spaces, parameters with nested quotes with and without spaces.
int argumentCount;
LPWSTR commandLine = GetCommandLineW();
LPWSTR *arguments = CommandLineToArgvW(commandLine, &argumentCount);
I wrote the program of finding file using FindFirstFile(...) function. But when I try to print the output of this function, there is a several strings of unknown characters printed in console windows.
I read some posts, there was written to try using wcout instead of cout. I try it, but it doesn't help. I think, that the problem is in difference between ANSI and UNICODE encodings. Can somebody help me? I will be very thankful for any help!
Here is my code:
#include "FindFile.h"
#include <iostream>
using namespace std;
void FindFileCl::Execute(Input * input, Response * response )
{
WIN32_FIND_DATAA FindFileData;
HANDLE h = FindFirstFileA((input->FileName).c_str(), // name of the file
&FindFileData);
if (h)
{
cout << "Search Results:\n";
cout<<(FindFileData.cFileName);
CloseHandle(h);
}
else
{
cerr << "File is NOT found:" << GetLastError() << "\n";
}
}
If FindFirstFile() fails it returns INVALID_HANDLE_VALUE, not NULL:
If the function fails or fails to locate files from the search string in the lpFileName parameter, the return value is INVALID_HANDLE_VALUE and the contents of lpFindFileData are indeterminate. To get extended error information, call the GetLastError function.
and INVALID_HANDLE_VALUE is #defined as -1 (following macro located in WinBase.h):
#define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)
Meaning the if (h) will be entered in either success or failure. In the event of failure, cFileName will not be modified resulting in junk being printed as it is not initialized. Change the if condition to explicitly check for INVALID_HANDLE_VALUE:
if (h != INVALID_HANDLE_VALUE)
{
}
One of the "least bad" ways would be to convert the Unicode name to the Console's encoding.
For this, I suggest compiling in Unicode (There's a project option for that in Visual Studio >=8; otherwise you have to define both UNICODE and _UNICODE manually), using the TCHAR version of FindFirstFile(), and then using CharToOem() or CharToOemBuff() (neither is perfect). -- Or alternately, use the W version followed by WideCharToMultiByte(CP_OEMCP).