There are multiple solutions to this task (standard C++, Qt, WinAPI, etc.), I will list a few of those. I have tested and verified all the listed ones (in case I did not make a mistake, they should work fine).
However all the solutions listed below requires you to change one line in the batch script you provided.
The reason for that is the last line "echo %Cpusage0%
" only prints the result value to the command prompt instead of returning it.
For that reason I changed the last line of the batch file
from echo %Cpusage0%
to exit %Cpusage0%
This returns the value in the variable Cpusage0
from the command prompt.
Solutions:
In general: the batch file needs to be run through the command interpreter (cmd.exe - command prompt on Windows). In addition you need to specify the /C option to the cmd.exe if you wish to run only one command in the interpreter and then terminate it (that's true in your case).
So the command you wish to execute is:
std::string BatchFile = "..."; // Access path to the batch file.
std::string Command = "cmd.exe /C " + BatchFile;
Note: if you change the Command
std::string the previously obtained c_str() pointers become invalid.
So either
- don't change the std::string while the std::system() call is running,
- use a local variable for storing the command (like i did in the examples),
- copy the command into a C-string (
[const] char*
) pointer or
preferably into a smart pointer character array
(std::unique_ptr<char[]>
).
In case of using C-string, don't forget to delete it.
Summary:
Standard C++ - std::system(...)
A, Waiting for std::system(...) to finish. This blocks the calling thread.
B, Running std::system(...) in a different thread with std::thread. This does not block the calling thread.
C, Running std::system(...) in a different thread with std::async. This does not block the calling thread.
Qt - QProcess
A, Waiting for QProcess to finish. This blocks the calling thread.
B, Signaling a slot with QProcess::finished(). This does not block the calling thread.
WinAPI - CreateProcess(...)
A, Waiting for CreateProcess(...) to finish. This blocks the calling thread.
B, Starting a new thread (CreateThread(...)) to wait for CreateProcess(...) to finish. This does not block the calling thread.
Alternative: I would like to mention the same thing @wOxxOm advised in a comment on the question - you can get the CPU usage directly in C++. This has been asked on StackOverflow a couple of times before, here are some examples:
How to determine CPU and memory consumption from inside a process?
Retrieving CPU Load Percent total in Windows with C++
Note: I haven't verified the "cpu usage directly from C++" answers myself, but one is heavily upvoted and the other one is accepted as an answer.
In details:
Note: These are minimalistic solutions with minimal error checking. In case of using a "multi-threaded solution" do not forget to add proper protection for the shared resources (for example using std::atomic<int>
or std::mutex to protect the shared variable).
You can execute the batch file in the current thread and wait for the result by calling std::system(...) with the Command and storing the result in an int variable (the percentage value).
A, Plain blocking std::system(...) call.
This blocks the calling thread.
auto runBatchSTDSystemWaited(const std::string& BatchFile) -> int {
auto Command = std::string("cmd.exe /C " + BatchFile);
return std::system(Command.c_str());
}
You can do the same in another thread (and continue to do other things while waiting for the result) by using either std::thread or std::async(...).
B, std::thread. (std::promise, std::future)
This does not block the calling thread.
auto runBatchSTDSystemThread(const std::string& BatchFile, std::shared_ptr<std::promise<int>> Promise) -> std::future<int> {
// Note: the Promise object must exist until the last call to either the promise or the future objects.
auto Command = std::string("cmd.exe /C " + BatchFile);
auto Future = Promise->get_future();
std::thread Thread([](decltype(Command) STDSystemCommand, decltype(Promise) ResultPromise) -> void {
ResultPromise->set_value_at_thread_exit(std::system(STDSystemCommand.c_str()));
}, Command, Promise);
Thread.detach();
return Future;
// Note: You can access the CPU usage value by calling the std::future::get() function of the returned future object.
}
The following basically wraps the 1/B solution into 1 call.
C, std::async(...)
This does not block the calling thread.
auto runBatchSTDSystemAsync(const std::string& BatchFile) -> std::future<int> {
auto Command = std::string("cmd.exe /C " + BatchFile);
// std::async can be forced to create new thread with std::launch::async launch policy.
// Make sure that the Command string exists until the new thread ends (reason for string copy).
// The lambda-function is required to copy the command string to the new thread.
auto Future = std::future<int>(std::async(std::launch::async, [](decltype(Command) STDSystemCommand) -> int {
return std::system(STDSystemCommand.c_str());
}, Command));
return Future;
// Note: You can access the CPU usage value by calling the std::future::get() function of the returned future object.
}
Similarly to the standard c++ solutions you can wait for the QProcess thread to finish the execution and obtain the result.
A, QProcess::waitForFinished(-1)
This blocks the calling thread.
auto runBatchQtQProcessWaited(const std::string& BatchFile) -> int {
QProcess Process;
auto Command = QString("cmd.exe");
auto Arguments = QStringList{
QString("/C"),
QString::fromStdString(BatchFile)
};
Process.start(Command, Arguments);
Process.waitForFinished(-1);
return Process.exitCode();
}
With Qt another possibility is to signal an appropriate slot function for receiving the result of the QProcess.
B, QProcess::finished()
This does not block the calling thread.
class SlotClass : public QObject {
Q_OBJECT
public:
SlotClass(std::shared_ptr<QProcess> Process);
auto getResult() const -> int;
public slots:
/*auto*/ void onPostFinishQtQProcess(int ExitCode, QProcess::ExitStatus ExitStatus) /*-> void*/;
// Seems like Qt 5.5 moc compiler fails to correctly recognize auto declared slots. (Throws error when compiling at ":".)
private:
std::shared_ptr<QProcess> Process;
int Result;
};
SlotClass::SlotClass(std::shared_ptr<QProcess> Process) :
Process(Process),
Result(-1) {}
auto SlotClass::getResult() const -> int {
return this->Result;
}
/*auto*/ void SlotClass::onPostFinishQtQProcess(int ExitCode, QProcess::ExitStatus ExitStatus) /*-> void*/ {
if (ExitStatus == QProcess::CrashExit)
throw std::runtime_error("Batch process crashed.");
this->Result = ExitCode;
}
auto runBatchQtQProcessSignaled(const std::string& BatchFile, const SlotClass& SlotObject) -> void {
auto Command = QString("cmd.exe");
auto Arguments = QStringList{
QString("/C"),
QString::fromStdString(BatchFile)
};
QObject::connect(SlotObject.getProcess().get(), SIGNAL(finished(int, QProcess::ExitStatus)),
&SlotObject, SLOT(onPostFinishQtQProcess(int, QProcess::ExitStatus)));
SlotObject.getProcess()->start(Command, Arguments);
}
There is a variation for the blocking wait with WinAPI as well.
A, CreateProcess(...)
This blocks the calling thread.
auto runBatchWinAPICreateProcessWaited(const std::string& BatchFile) -> int {
auto Command = "cmd.exe /C " + BatchFile;
// Creates wide string array from the narrow command string.
auto WideStringConverter = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>();
auto WideCommand = WideStringConverter.from_bytes(Command);
auto WideCommandArray = std::make_unique<wchar_t[]>(WideCommand.length() + 1);
std::wcscpy(WideCommandArray.get(), WideCommand.c_str());
// Initializes necessary structures.
STARTUPINFO BatchStartupInformation;
std::memset(&BatchStartupInformation, 0, sizeof(BatchStartupInformation));
BatchStartupInformation.cb = sizeof(BatchStartupInformation);
PROCESS_INFORMATION BatchProcessInformation;
std::memset(&BatchProcessInformation, 0, sizeof(BatchProcessInformation));
// Creates a new command prompt process with no window and executes the given batch file.
if (!CreateProcess(nullptr, WideCommandArray.get(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW,
nullptr, nullptr, &BatchStartupInformation, &BatchProcessInformation))
throw std::exception(("Could not create process for running the batch file. Error code: " + std::to_string(GetLastError())).c_str());
// Waits until the created process has already finished.
auto WaitResult = WaitForSingleObject(BatchProcessInformation.hProcess, INFINITE);
if (WAIT_FAILED == WaitResult)
throw std::runtime_error(("Waiting for batch process failed. Error code: " + std::to_string(GetLastError())).c_str());
//else if (WAIT_TIMEOUT == WaitResult)
// ; //...
auto ProcessResult = 0ul;
if (!GetExitCodeProcess(BatchProcessInformation.hProcess, &ProcessResult))
throw std::exception(("Could not retrieve process exit code after running batch file. Exit code: " + std::to_string(GetLastError())).c_str());
CloseHandle(BatchProcessInformation.hProcess);
CloseHandle(BatchProcessInformation.hThread);
return ProcessResult;
}
Or you can do the same as 3/A, but create a new thread to wait for the batch file to finish.
B, CreateThread(), CreateProcess()
This does not block the calling thread.
auto runBatchWinAPICreateProcessEvent(const std::string& BatchFile) -> void {
auto Command = "cmd.exe /C " + BatchFile;
// Creates wide string array from the narrow command string.
auto WideStringConverter = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>();
auto WideCommand = WideStringConverter.from_bytes(Command);
auto WideCommandArray = std::make_unique<wchar_t[]>(WideCommand.length() + 1);
std::wcscpy(WideCommandArray.get(), WideCommand.c_str());
// Initializes necessary structures.
STARTUPINFO BatchStartupInformation;
std::memset(&BatchStartupInformation, 0, sizeof(BatchStartupInformation));
BatchStartupInformation.cb = sizeof(BatchStartupInformation);
PROCESS_INFORMATION BatchProcessInformation;
std::memset(&BatchProcessInformation, 0, sizeof(BatchProcessInformation));
// Creates a new command prompt process with no window and executes the given batch file.
if (!CreateProcess(nullptr, WideCommandArray.get(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW,
nullptr, nullptr, &BatchStartupInformation, &BatchProcessInformation))
throw std::exception(("Could not create process for running the batch file. Error code: " + std::to_string(GetLastError())).c_str());
if (!CreateThread(nullptr, 0, &waitForWinAPICreateProcessResult, new PROCESS_INFORMATION(BatchProcessInformation), 0, nullptr))
throw std::exception(("Could not create process for retrieving the result of the batch file. Error code: " + std::to_string(GetLastError())).c_str());
}
auto WINAPI waitForWinAPICreateProcessResult(LPVOID ThreadParameter) -> DWORD {
auto BatchProcessInformation = std::unique_ptr<PROCESS_INFORMATION>(reinterpret_cast<PROCESS_INFORMATION*>(ThreadParameter));
// Waits until the created process has already finished.
auto WaitResult = WaitForSingleObject(BatchProcessInformation->hProcess, INFINITE);
if (WAIT_FAILED == WaitResult)
throw std::runtime_error(("Waiting for batch process failed. Error code: " + std::to_string(GetLastError())).c_str());
//else if (WAIT_TIMEOUT == WaitResult)
// ; //...
auto ProcessResult = 0ul;
if (!GetExitCodeProcess(BatchProcessInformation->hProcess, &ProcessResult))
throw std::exception(("Could not retrieve process exit code after running batch file. Exit code: " + std::to_string(GetLastError())).c_str());
// You have the result in the ProcessResult variable.
CloseHandle(BatchProcessInformation->hProcess);
CloseHandle(BatchProcessInformation->hThread);
return 0;
}