QProcess::execute Environment Variables Expanded Strings - c++

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();
}

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.

How do I add precautions to get around Microsoft.ACE.OLEDB.12.0 not registered?

When I use the following code on my home computer it works fine for me. However, I have to use it on multiple different machines.
"Provider = Microsoft.ACE.OLEDB.12.0;"
I was thinking that there could be a get around to this because I need it in a more universal format so that it works on multiple machines.
I have hoped that there could be a work around, maybe by including an alternative provider in the code (if that is possible) or an catch or if statement that will rerun the code but with an alternate provider if it cannot find the first one.
It would be changed to Microsoft.Jet.OLEDB.4.0
If anyone could scratch up a little bit of code to work around this so that my program could take into account both providers that would be great as it would make my projects more universal.
Background code (ignore unsafe SQL unless you want to fix it):
OleDbDataReader^ openData(String^ fieldEntity, String^ field, String^ tableName)
{
String^ sqlstr = "SELECT * FROM ";
sqlstr += tableName + " WHERE " + field + " = " + fieldEntity;
OleDbConnection^ conn = nullptr;
conn = gcnew OleDbConnection("Provider = Microsoft.ACE.OLEDB.12.0;" +
"Data Source =" + "myDatabaseV3.accdb");
OleDbCommand^ cmd = nullptr;
//fix this so that it will consider both providers.
conn->Open();
cmd = gcnew OleDbCommand(sqlstr, conn);
OleDbDataReader^ reader = cmd->ExecuteReader(System::Data::CommandBehavior::CloseConnection);
return reader;
}
I have solved this problem myself. I realised that I could you another try catch statement in my class to change the connection string if there is an exception
conn = gcnew OleDbConnection("Provider = Microsoft.Jet.OLEDB.4.0;" +
"Data Source =" + "myDatabaseV3.mdb");
pause();
OleDbCommand^ cmd = nullptr;
//fix this so that it will come out of the current directiory
try {
conn->Open();
}
catch (Exception^ ex)
{
conn->ConnectionString = "Provider = Microsoft.ACE.OLEDB.12.0;" +
"Data Source =" + "myDatabaseV3.accdb";
conn->Open();
}
You could switch these around if you wanted the order of these doesn't really matter. Furthermore, if this solution is invalid you could have a nested try catch statement. However, my code in particular has this In a class and the way this is ran anyway is in a try catch in another part of the code so it is unnecessary for me.

Qt PL/SQL - Assignment Operator - Character String Buffer too small

I have been picking my brain for a while trying to figure this one out.
The problem I am having is that the function I am using in Oracle returns a BLOB. It's a list of items that are concatenated together using ||.
From the research I have done,
In the QSQLQuery docs it says "Stored procedures that uses the return statement to return values, or return multiple result sets, are not fully supported. For specific details see SQL Database Drivers." - which leads me to believe I may need to switch to a different codebase if Qt cannot handle it yet.
The documentation for the QOCI driver mentions this "Binary Large Objects (BLOBs) can be read and written, but be aware that this process may require a lot of memory. You should use a forward only query to select LOB fields (see QSqlQuery::setForwardOnly())."
I did set
query.setForwardOnly(true);
Before I prepared or executed the statement.
However, I still get this error
QSqlError("6502", "Unable to execute statement", "ORA-06502: PL/SQL: numeric or value error: character string buffer too small\nORA-06512: at line 55\n")
I had to scrub the code a bit, I hope this is still helpful to give context to what i'm trying to accomplish
temp_clob clob;
name varchar2(183) := ?;
start varchar2(16) := ?;
end varchar2(16) := ?;
count integer := ?;
return_val named_redacted_table_object; -- Sorry had to remove this, it's a table with more Date, Varchar, etc
begin
dbms_lob.createtemporary(temp_clob, true);
return_val := package_name.function_name (
set_name => name,
start_time => to_date(start, 'yyyy-mm-dd hh24:mi'),
end_time => to_date(end, 'yyyy-mm-dd hh24:mi'),
max_count => count);
-- In here was a loop that would break apart the removed table object and make it into strings along the following lines
-- '' || return_val(i).name || return_val(i).value || etc
-- and would store these into the CLOB / temp_clob
? := temp_clob;
end;
I could not get something as simple as this to work
begin
? := 'test123';
end;
With the assumption I could at least read this string in Qt.
Here is my code for Qt
QString name = "test";
QSqlQuery query(db);
query.setForwardOnly(true);
query.prepare(sql);
QString test_sql_text = ui->comboBox_test_text->currentText();
qDebug() << name;
query.bindValue(0, name);
query.bindValue(1, "2003-03-14 00:00");
query.bindValue(2, "2005-03-14 23:00");
query.bindValue(3, 2);
query.bindValue(4, QString(""), QSql::Out);
bool query_executed_ok = query.exec();
qDebug() << "did it execute?" << query_executed_ok;
// qDebug() << query.executedQuery();
qDebug() << query.boundValues();
qDebug() << query.lastError();
QSqlRecord rec = query.record();
qDebug() << rec;
int record_count = rec.count();
qDebug() << "Records: " << record_count;
while (query.next()) {
for(int i=0;i<record_count;i++) {
qDebug() << query.isValid() << " - " << rec.fieldName(i) << " " << query.value(i).toString();
}
}
The error posted appears to be from within the Oracle code; ORA.... You have stripped so much it's hard to see what is actually happening, especially the are where the error apparently occurred. But perhaps using Oracle supplied code that is specifically designed to handle CLOBs. Instead of
'' || return_val(i).name ...
Try
dbms_lob.append(temp_clob, to_clob(return_val(i).name))
begin
? := 'test123';
end;
Bind variables are used to assign values to variables. You define your variable in your pl/sql code and assign a value to it at runtime by using a bind variable. In that case pl/sql code will compile correctly.
In your code the bind variable is used to replace the pl/sql variable, not the value, which will fail. Your pl/sql block cannot be compiled because it cannot resolve the "?".
A valid use of bind variables would be
BEGIN
l_xyz := ?;
END;
where you assign the value test123 at runtime.
It took some fiddling, and I realize I gave fairly cryptic code. So thank you to belayer and Koen for taking a shot at my mess.
What I was able to determine and get working for anyone else running into this:
Let me start off by saying I am not sure if this is a bug, or if i'm doing something in a way that was not intended by the designers of QSqlQuery (The class for handling SQL calls).
The call would work in SQL developer and I would see the intended CLOB with all characters. I was unable to get DBMS_Output to work, however, I saw this post saying to reserve space on the string before binding it to the query.
It solves my issue and shows the result in the debug window. However, it presents a new problem. What if the string becomes larger than my hard coded reserve value?
Here's the updated code for that
query.prepare(sql);
QString name= ui->comboBox_name->currentText();
qDebug() << project;
query.bindValue(":name", project);
query.bindValue(":start_date", "2005-03-14 00:00");
query.bindValue(":end_date", "2006-03-14 23:00");
query.bindValue(":max_count", 3);
QString testStr ="*****";
//testStr.truncate(0); //Maybe this works too?
testStr.reserve( 1000000 ); // This did it for sure
qDebug() << testStr.capacity();
query.bindValue(":result_clob", testStr, QSql::Out);
bool query_executed_ok = query.exec();
qDebug() << "did it execute?" << query_executed_ok;
if (query_executed_ok) {
testStr = query.boundValue(":result_clob").toString();
qDebug() << testStr;
} else {
qDebug() << "string is empty";
}
I got the idea to do this, from THIS post.

QProcess start process (blender.exe) with multiple arguments

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);

how do i change a file extension on qt

I have a piece of code to download a file from server. However, due to server constraint, I can not put .exe file at server. So I rename my XXX.exe file to XXX.alt(just a random extension) and put it on server.
Now my code can download XXX.alt, but how can I change the file name from XXX.alt back to XXX.exe when in QT environment?
Use QFileInfo to get the path without the last extension then append the new extension.
QFileInfo info(fileName);
QString strNewName = info.path() + "/" + info.completeBaseName() + ".exe";
Just use rename function from 'stdio.h'.
char oldname[] ="XXX.alt";
char newname[] ="XXX.exe";
result= rename( oldname , newname );
if ( result == 0 )
puts ( "File successfully renamed" );
else
perror( "Error renaming file" );
One solution is to find the last '.', and replace the substring from that position to the end with the substring you want.
Exactly how to do it, there are many ways using both std::string and QString, as both support finding characters in the string (and doing a search from the end to the beginning as well), and replace substrings.
Prefer to use baseName()
QFileInfo info(fileName);
QString strNewName = info.path() + info.baseName() + ".exe";
QString QFileInfo::completeBaseName () const
Returns file name with shortest extension removed (file.tar.gz -> file.tar)
QString QFileInfo::baseName () const
Returns file name with longest extension removed (file.tar.gz -> file)