QProcess start process (blender.exe) with multiple arguments - c++

I try to start blender.exe from inside my program (FaceModifier.exe) using QProcess (on Windows). The command follows this structure:
'path-to-blender' --background 'path-to-blend-file' --python 'path-to-python-script' -- 'additional-arg-for-python-script'
A full example (that works if I type it into cmd.exe) would be
"C:\Program Files\Blender Foundation\Blender\blender.exe" --background "C:\Program Files(x86)\FaceModifier\Resources\GenericHeadMesh.blend" --python "C:\Program Files(x86)\FaceModifier\python\local.py" -- "C:\Users\Gunnar\Documents\FaceModifier\Output\"
Now, inside my program I escape the paths and surround them with quotes so I have something like this
std::string blenderPath := "\"C:\\Program Files\\Blender Foundation\\Blender\\blender.exe\""
For QProcess I feed all my arguments into a QStringList with a leading /c so it is treated as a single command and pass it to cmd.exe.
My problem is that I can't get this to be executed. If I type the command (that I pass to QProcess, not the one from above) into cmd by hand neither.
My function that starts the process looks like this:
void PythonConnector::createSubprocess(const QStringList &args)
{
QProcess *process = new Process();
process->start("cmd.exe", args);
process->waitForFinished(-1);
QString result(process->readAll());
qDebug() << "result: " << result; // this gives just an empty string
process->close();
}
I call it like this:
// for the documents path
CoInitialize(NULL);
TCHAR *myDocuments = 0;
SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &myDocuments);
CString temp(myDocuments);
CT2CA tempConv(temp);
std::string outputFolderPath(tempConv);
outputFolderPath += "\\FaceModifier\\Output\\";
outputFolderPath = pH.ExcapeString(outputFolderPath);
CoTaskMemFree(myDocuments);
blenderPath = pH.EscapeString(blenderPath);
std::string meshGlobalPath = "\"" + pH.GetResourcesPath() + "GenericHeadMesh.blend" + "\"";
std::string pythonGlobalPath = "\"" + pH.GetPythonPath() + "global.py" + "\"";
QStringList args;
args << "/c" << QString::fromStdString(blenderPath) << "--background" << QString::fromStdString(meshGlobalPath) << "--python" << QString::fromStdString(pythonGlobalPath) << "--" << QString::fromStdString("\"" + outputFolderPath "\"");
pC.createSubprocess(args);
blenderPath is passed to this function and read from registry in another function.
These are my other helper functions:
std::string PathHandler::GetResourcesPath()
{
if(resourcesPath.empty())
resourcesPath = GetFullPath("Resources\\");
return resourcesPath;
}
std::string PathHandler::GetPythonPath()
{
if(pythonPath.empty())
pythonPath = GetFullPath("python\\");
return pythonPath;
}
std::string PathHandler::GetFullPath(const std::string &relPath)
{
char full[_MAX_PATH];
_fullpath(full, relPath.c_str(), _MAX_PATH);
return EscapeString(std::string(full));
}
std::string PathHandler::EscapeString(const std::string &input)
{
std::regex toReplace("\\\\");
std::string output(input.begin(), input.end());
output = std::regex_replace(output, toReplace, "\\\\");
return output;
}
Debugging the args list with qDebug results in these outputs (these are actually from debugging in Visual Studio 2013, therefore the different paths):
"/c"
""C:\\Program Files\\Blender Foundation\\Blender\\blender.exe""
"--background"
""C:\\Users\\Gunnar\\documents\\visual studio 2013\\Projects\\FaceModifier\\x64\\Release\\Resources\\GenericHeadMesh.blend""
"--python"
""C:\\Users\\Gunnar\\documents\\visual studio 2013\\Projects\\FaceModifier\\x64\\Release\\python\\global.py""
"--"
""C:\\Users\\Gunnar\\Documents\\FaceModifier\\Output\\""
and the result debug from createSubprocess just gives "".
If I type this command into cmd by hand like this:
cmd /c "C:\Program Files\Blender Foundation\Blender\blender.exe" --background "C:\Users\Gunnar\documents\visual studio 2013\Projects\FaceModifier\x64\Release\Resources\GenericHeadMesh.blend" --python "C:\Users\Gunnar\documents\visual studio 2013\Projects\FaceModifier\x64\Release\python\global.py" -- "C:\Users\Gunnar\Documents\FaceModifier\Output\"
I get The command "C:\\Program" is either missspelled or couldn't be found.
Same for various different quotings with and without escaping like this
cmd "/c \"C:\Program Files\Blender Foundation\Blender\blender.exe\" --background \"C:\Users\Gunnar\documents\visual studio 2013\Projects\FaceModifier\x64\Release\Resources\GenericHeadMesh.blend\" --python \"C:\Users\Gunnar\documents\visual studio 2013\Projects\FaceModifier\x64\Release\python\global.py\" -- \"C:\Users\Gunnar\Documents\FaceModifier\Output\\""
Just typing
cmd /c "C:\Program Files\Blender Foundation\Blender\blender.exe"
works fine, but that is obviously not what I need.
I can't wrap my head around how I have to build this so it works. Can somebody tell me what my mistake is?
UPDATE:
Ok, in theory it works now (thanks to ahmed), it really depands on the paths though. If I have the .blend and the .py in ...\Documents\FaceModifier it works, if they are in C:\Program Files (x86)\FaceModifier\ (where my program is installed) it doesn't work. How can I achieve that? I would prefer that so I don't have to copy those files over there.

You don't need to quote or double quote file paths, QProccess already handle that,
change this part of your code to:
std::string meshGlobalPath = pH.GetResourcesPath() + "GenericHeadMesh.blend" ;
std::string pythonGlobalPath = pH.GetPythonPath() + "global.py" ;
QStringList args;
args << "/c" << QString::fromStdString(blenderPath) << "--background"
<< QString::fromStdString(meshGlobalPath) << "--python"
<< QString::fromStdString(pythonGlobalPath) << "--"
<< QString::fromStdString(outputFolderPath);

Related

QProcess does not work if the path in argument list has a whitespace

I want to execute following command using QProcess, that works in cmd already if executed:
C:\\myApplication\\tools\\dcmodify.exe -v -ie -gin -nb -ma (0010,0010)=TestedData "C:\Users\user.name\Documents\My Tools Data\Temp\Demo Phantom Skin\dicom\*"
The first argument, which gives the path of the executable is defined as a QString:
QString srcToolPath = QDir::toNativeSeparators(QDir::cleanPath(qApp->applicationDirPath() + Constants::TOOLS_PATH + QDir::separator() + toQString("dcmodify.exe")));
The argument list and path, where the executable should be executed, is defined as a QStringList:
QString dstDicomPath = QDir::cleanPath(Utilities::getTempPath() + QDir::separator() + toQString("Anon_") + QDateTime::currentDateTime().toString(Constants::DETAILED_DATE_TIME_FORMAT)) + QDir::separator();
QStringList argumentList;
argumentList <<
toQString(" -v") <<
toQString(" -ie") <<
toQString(" -gin") <<
toQString(" -nb") <<
toQString(" -ma (0010,0010)=TestedData") <<
toQString(" \"") + QDir::toNativeSeparators(dstDicomPath) + toQString("*\"");
and the process is started:
QProcess anonymizerProcess;
anonymizerProcess.start(srcToolPath, argumentList);
Since the dstDicomPath contains some whitespaces, I added quotes around it. Although the command is executed, somehow I don't get the result like in cmd. What I am doing wrong with dstDicomPath string?
OK, there are multiple unknowns, for example what toQString() does in your code, but nevermind. Let's start with the assumption that this command is correct and would work if you called it from the command line:
C:\myApplication\tools\dcmodify.exe -v -ie -gin -nb -ma (0010,0010)=TestedData "C:\Users\user.name\Documents\My Tools Data\Temp\Demo Phantom Skin\dicom\*"
now lets have a look at what QProcess::splitCommand() returns (note that I escaped the backslashes and quotes in the command by prepending backslash):
QString cmd= "C:\\myApplication\\tools\\dcmodify.exe -v -ie -gin -nb -ma (0010,0010)=TestedData \"C:\\Users\\user.name\\Documents\\My Tools Data\\Temp\\Demo Phantom Skin\\dicom\\*\"";
qDebug() << QProcess::splitCommand(cmd);
It displays
("C:\\myApplication\\tools\\dcmodify.exe", "-v", "-ie", "-gin", "-nb", "-ma", "(0010,0010)=TestedData", "C:\\Users\\user.name\\Documents\\My Tools Data\\Temp\\Demo Phantom Skin\\dicom\\*")
... and this is exactly what you need to pass to the program and arguments for calling QProcess::start(). Hence:
auto program = QString("C:\\myApplication\\tools\\dcmodify.exe");
auto arguments = QStringList() << "-v" << "-ie" << "-gin" << "-nb"
<< "-ma" << "(0010,0010)=TestedData"
<< "C:\\Users\\user.name\\Documents\\My Tools Data\\Temp\\Demo Phantom Skin\\dicom\\*";
QProcess process;
process.start(program, arguments);
So the message here is that you do not need to try to fix the quotes about whitespaced strings yourself. Qt does it for you automatically. All you need is to correctly split the program and arguments so that Qt knows where to put quotes.

Add files to the existing archive in XZip, C++, Windows

I am creating a windows service that periodically copies the contents of a folder to an archive. I've searched for archiving libraries and found suggestions to use XZip.
Now I can create a new archive using code like this:
HZIP arch = CreateZip((void*)Archive.c_str(), 0, ZIP_FILENAME);
if (arch == nullptr)
return addLogMessage("Unable to open archive.");
for (const auto& file : std::filesystem::directory_iterator(Directory)) {
const std::wstring filePath = file.path().wstring();
const std::wstring nameInArchive = file.path().filename().wstring();
if (ZipAdd(arch, (TCHAR*)nameInArchive.c_str(), (TCHAR*)filePath.c_str(), 0, ZIP_FILENAME) != ZR_OK) {
std::string what = "Error: Adding '" + std::string(filePath.begin(), filePath.end()) + "' to the archive.";
addLogMessage(what.c_str());
}
}
CloseZip(arch);
I also want to add files to an existing archive. When I run the code below, I get a ZR_ZMODE error from the ZipAdd function, which means tried to mix create / open zip archive:
if (std::filesystem::exists(Archive)) {
HZIP arch = OpenZip((void*)Archive.c_str(), 0, ZIP_FILENAME);
if (arch == nullptr)
return addLogMessage("Unable to re-open existing archive.");
for (const auto& file : std::filesystem::directory_iterator(Directory)) {
const std::wstring filePath = file.path().wstring();
const std::wstring nameInArchive = file.path().filename().wstring();
if (ZipAdd(arch, (TCHAR*)nameInArchive.c_str(), (TCHAR*)filePath.c_str(), 0, ZIP_FILENAME) != ZR_OK) {
std::string what = "Error: Adding '" + std::string(filePath.begin(), filePath.end()) + "' to the archive.";
addLogMessage(what.c_str());
}
}
CloseZip(arch);
}
I assume that in XZip library, I'm not able to add files to the existing archive. Is it true?
EDIT: I've solved the problem with different library, libzip. Now I can add new files to the old archive, and everything works fine. I've posted source code and compiling/linking instruction here.
I am still interested, if there is an option for adding files to the existing archive in XZip library, so I will not close the question for now.

QProcess::execute Environment Variables Expanded Strings

How do I get this to work:
QProcess::execute("%windir%\system32\SnippingTool.exe")
I assume expanded environment variables strings are being ignored by QProcess.
I guess I'll need to parse the string and see if % exists and then get the environment variable to complete the full string path. Sounds like hassle and something that should be handled by QProcess. Am I missing something?
Thanks in advance! :)
If you want to use %windir% directly, you can do something like this:
QProcess::execute("cmd.exe /c start /WAIT "" %windir%\\system32\\SnippingTool.exe");
Else, you can use for example qgetenv("windir") or qEnvironmentVariable("windir") to get the windows folder path.
Hope it helps you.
Thanks to #TomKim answer for handling expanded strings in his answer, I got that problem solved. But unfortunately white spaces caused other issues for me that made me come up with this solution that hopefully will help others. Not the prettiest solution though, but it does exactly I needed for multiple platforms:
void QuickCut::executeProcess(const std::string & szProc, const std::string & szArgs)
{
// QProc won't expand environment variable strings.
// Invoking using the user console will allow for expanded string to work as expected.
#ifdef Q_OS_WIN
QString szCommand = "cmd /c start \"\" \"" + QString::fromStdString(szProc) + "\"";
QString szExt = ".cmd";
#elif Q_OS_UNIX
QString szCommand = "sh -c '" + QString::fromStdString(szProc) + "'";
QString szExt = ".sh";
#endif
QStringList qArgsTmp = QString::fromStdString(szArgs).trimmed().split(",");
for (auto && arg : qArgsTmp)
{
QString argTrimmed = arg.trimmed();
if (argTrimmed.isEmpty()) continue;
szCommand += " " + argTrimmed;
}
qDebug() << "[QuickCut::executeProcess] - Execute Command: " << szCommand;
QString szFilePath = applicationDirPath() + "/tempCmd" + szExt;
QFile file(szFilePath);
file.open(QIODevice::ReadWrite);
QTextStream ts(&file);
ts << szCommand;
file.close();
QProcess::execute(szFilePath);
file.remove();
}

C++ Qt get return value of sub-python process

I'm trying to get the return value of the external python script.
I tried this to get return value but it doesn't work.
QStringList arguments { "example.py", "--argv",path_of_image };
QProcess p;
p.start("python", arguments);
return_value=p.readLine();//also I tried p.readAllStandardOutput()
p.waitForFinished();
qDebug()<<return_value;
This prints "" , I can't get the value.
I found it
QProcess process;
QString scriptFile = "example.py";
QString pythonCommand = "python " + scriptFile + " --arg " ;
process.start(pythonCommand);
process.execute(pythonCommand);
process.waitForFinished(1000);
returned=process.readAllStandardOutput();

want it to be trim my TCHAR * from end only

TCHAR* pszBackupPath;
m_Edt_ExportPath.GetWindowText(pszBackupPath, dwcchBackupPath);
StrTrim(pszBackupPath, L" ");
StrTrim(pszBackupPath, L"\\"); //this line has issue
iRet = _tcslen(pszBackupPath);
boRet = PathIsNetworkPath(pszBackupPath);
if (FALSE == boRet)
{
// MessageBox with string "Entered path is network path.
}
boRet = PathIsDirectory(pszBackupPath);
if (FALSE == boRet)
{
// MessageBox with string "Entered path is not a valid directory.
}
This is a part of my code in MFC.
I am passing a network path from UI. But because of StrTrim(pszBackupPath, L"\\") "\\" get trimmed from start and end. But I want it to be trimmed from end only.
I don't know any direct API. Please suggest.
There is a simple function to do that: PathRemoveBackslash (or PathCchRemoveBackslash for Windows 8 and later).