Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Extensions

Structure
Simple extensions
Handler Types
Asynchronous Functionality
Error handling
Executor Overloading

To extend the library, the header extend is provided.

It only provides the explicit style for custom properties, but no implicit style.

What this means is, that a custom initializer can be implemented, a reference which can be passed to one of the launching functions. If a class inherits boost::process::extend::handler it will be regarded as an initializer and thus directly put into the sequence the executor gets passed.

The executor calls different handlers of the initializers during the process launch. The basic structure consists of three functions, as given below:

Additionally posix provides three more handlers, listed below:

For more information see the reference of posix_executor.

The simplest extension just takes a single handler, which can be done in a functional style. So let's start with a simple hello-world example, while we use a C++14 generic lambda.

using namespace boost::process;
namespace ex = bp::extend;

child c("foo", ex::on_success=[](auto & exec) {std::cout << "hello world" << std::endl;});

Considering that lambdas can also capture values, data can easily be shared between handlers.

To see which members the executor has, refer to windows_executor and posix_executor.

[Note] Note

Combined with on_exit this can also handle the process exit.

[Caution] Caution

The posix handler symbols are not defined on windows.

Since the previous example is in a functional style, it is not very reusable. To solve that problem, the handler has an alias in the boost::process::extend namespace, to be inherited. So let's implement the hello world example in a class.

struct hello_world : handler
{
    template<typename Executor>
    void ex::on_success(Executor & exec) const
    {
        std::cout << "hello world" << std::endl;
    }
};

//in our function
child c("foo", hello_world());

[Note] Note

The implementation is done via overloading, not overriding.

Every handler not implemented defaults to handler, where an empty handler is defined for each event.

Since boost.process provides an interface for boost.asio, this functionality is also available for extensions. If the class needs the boost::asio::io_context for some reason, the following code will do that.

struct async_foo : handler, ex::require_io_context
{
    template<typename Executor>
    void on_setup(Executor & exec)
    {
        boost::asio::io_context & ios = ex::get_io_context(exec.seq); //gives us a reference and a compiler error if not present.
        //do something with ios
    }
};

[Note] Note

Inheriting require_io_context is necessary, so system provides one.

Additionally the handler can provide a function that is invoked when the child process exits. This is done through ex::async_handler.

[Note] Note

async_handler implies require_io_context .

struct async_bar : __handler, ex::async_handler
{
    template<typename Executor>
    std::function<void(int, const std::error_code&)> on_exit_handler(Executor & exec)
    {
        auto handler_ = this->handler;
        return [handler_](int exit_code, const std::error_code & ec)
               {
                   std::cout << "hello world, I exited with " << exit_code << std::endl;
               };

    }
};

[Caution] Caution

on_exit_handler does not default and is always required when async_handler is inherited.

[Caution] Caution

on_exit_handler uses boost::asio::signal_set to listen for SIGCHLD on posix. The application must not also register a signal handler for SIGCHLD using functions such as signal() or sigaction() (but using boost::asio::signal_set is fine).

If an error occurs in the initializers it shall be told to the executor and not handled directly. This is because the behaviour can be changed through arguments passed to the launching function. Hence the executor has the function set_error, which takes an std::error_code and a string. Depending on the configuration of the executor, this may either throw, set an internal error_code, or do nothing.

So let's take a simple example, where we set a randomly chosen error_code.

auto set_error = [](auto & exec)
        {
            std::error_code ec{42, std::system_category()};
            exec.set_error(ec, "a fake error");

        };
child c("foo", on_setup=set_error);

Since we do not specify the error-handling mode in this example, this will throw process_error.

Now that we have a custom initializer, let's consider how we can handle differences between different executors. The distinction is between posix and windows and char and wchar_t on windows. One solution is to use the BOOST_WINDOWS_API and BOOST_POSIX_API macros, which are automatically available as soon as any process-header is included.

Another variant are the type aliases ex::posix_executor and ex::windows_executor, where the executor, not on the current system is a forward-declaration. This works fine, because the function will never get invoked. So let's implement another example, which prints the executable name ex::on_success.

struct hello_exe : handler
{
    template<typename Sequence>
    void ex::on_success(ex::posix_executor<Sequence> & exec)
    {
        std::cout << "posix-exe: " << exec.exe << std::endl;
    }

    template<typename Sequence>
    void ex::on_success(ex::windows_executor<char, Sequence> & exec)
    {
        //note: exe might be a nullptr on windows.
        if (exec.exe != nullptr)
            std::cout << "windows-exe: " << exec.exe << std::endl;
        else
            std::cout << "windows didn't use exe" << std::endl;
    }

    template<typename Sequence>
    void ex::on_success(ex::windows_executor<wchar_t, Sequence> & exec)
    {
        //note: exe might be a nullptr on windows.
        if (exec.exe != nullptr)
            std::wcout << L"windows-exe: " << exec.exe << std::endl;
        else
            std::cout << "windows didn't use exe" << std::endl;
    }

};

So given our example, the definitions with the non-native executor are still a template so that they will not be evaluated if not used. Hence this provides a way to implement system-specific code without using the preprocessor.

[Note] Note

If you only write a partial implementation, e.g. only for ex::posix_executor, the other variants will default to handler

.


PrevUpHomeNext