0

I currently have this batch file:

@echo off

setlocal enabledelayedexpansion

set Times=0

for /f "skip=1" %%p in ('wmic cpu get loadpercentage') do (

set Cpusage!Times!=%%p

set /A Times=!Times! + 1

)

echo %Cpusage0%

And I would like to run it in a standard C++ Qt windows application and store the percentage in a variable. I can see you can run batch files using QProcess, but I'm not sure how to get the result.

Thanks in advance for any help.

BUY
  • 705
  • 1
  • 11
  • 30
Ben Stobbs
  • 422
  • 6
  • 14
  • 2
    Not sure why you need the batch file if you can get the CPU usage *directly* in C++ with [native code](http://www.tidytutorials.com/2009/09/windows-c-get-cpu-and-memory.html). – wOxxOm Nov 21 '15 at 15:50
  • The idea of @wOxxOm is definitly a better approach then using a batch. However, if you need a batch for some reason: `QProcess` inherits `QIODevice`, meaning you can use it just like a file and read or write data from/to the process. – Felix Nov 21 '15 at 16:47
  • Is this a question about programming in Qt or about setting up Qt Creator? – MrEricSir Nov 21 '15 at 19:06

1 Answers1

4

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:

  1. 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.

  2. 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.

  3. 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).

1. Standard C++ - std::system(...)

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.
    }

2. Qt - QProcess

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

3. WinAPI - CreateProcess(...)

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;
}
Community
  • 1
  • 1
Pregnor
  • 311
  • 1
  • 5
  • 8
  • seems like these return an int from the batch file. how to return the output from the batch file? – user3217883 Mar 15 '18 at 22:46
  • Batch scripts can only return integer [exit codes](https://ss64.com/nt/exit.html) that is the mechanism I was using in my answer. But there are some workarounds to returning arbitrary result from a batch script, like writing to stdout or a file or [using environment variables](https://stackoverflow.com/a/11486159/3981670). – Pregnor Mar 17 '18 at 08:37