The Boost C++ Libraries

Platform-specific I/O Objects

So far, all of the examples in this chapter have been platform independent. I/O objects such as boost::asio::steady_timer and boost::asio::ip::tcp::socket are supported on all platforms. However, Boost.Asio also provides platform-specific I/O objects because some asynchronous operations are only available on certain platforms, for example, Windows or Linux.

Example 32.8. Using boost::asio::windows::object_handle
#include <boost/asio/io_service.hpp>
#include <boost/asio/windows/object_handle.hpp>
#include <boost/system/error_code.hpp>
#include <iostream>
#include <Windows.h>

using namespace boost::asio;
using namespace boost::system;

int main()
{
  io_service ioservice;

  HANDLE file_handle = CreateFileA(".", FILE_LIST_DIRECTORY,
    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
    OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

  char buffer[1024];
  DWORD transferred;
  OVERLAPPED overlapped;
  ZeroMemory(&overlapped, sizeof(overlapped));
  overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  ReadDirectoryChangesW(file_handle, buffer, sizeof(buffer), FALSE,
    FILE_NOTIFY_CHANGE_FILE_NAME, &transferred, &overlapped, NULL);

  windows::object_handle obj_handle{ioservice, overlapped.hEvent};
  obj_handle.async_wait([&buffer, &overlapped](const error_code &ec) {
    if (!ec)
    {
      DWORD transferred;
      GetOverlappedResult(overlapped.hEvent, &overlapped, &transferred,
        FALSE);
      auto notification = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer);
      std::wcout << notification->Action << '\n';
      std::streamsize size = notification->FileNameLength / sizeof(wchar_t);
      std::wcout.write(notification->FileName, size);
    }
  });

  ioservice.run();
}

Example 32.8 uses the I/O object boost::asio::windows::object_handle, which is only available on Windows. boost::asio::windows::object_handle, which is based on the Windows function RegisterWaitForSingleObject(), lets you start asynchronous operations for object handles. All handles accepted by RegisterWaitForSingleObject() can be used with boost::asio::windows::object_handle. With async_wait(), it is possible to wait asynchronously for an object handle to change.

Example 32.8 initializes the object obj_handle of type boost::asio::windows::object_handle with an object handle created with the Windows function CreateEvent(). The handle is part of an OVERLAPPED structure whose address is passed to the Windows function ReadDirectoryChangesW(). Windows uses OVERLAPPED structures to start asynchronous operations.

ReadDirectoryChangesW() can be used to monitor a directory and wait for changes. To call the function asynchronously, an OVERLAPPED structure must be passed to ReadDirectoryChangesW(). To report the completion of the asynchronous operation through Boost.Asio, an event handler is stored in the OVERLAPPED structure before it is passed to ReadDirectoryChangesW(). This event handler is passed to obj_handle afterwards. When async_wait() is called on obj_handle, the handler is executed when a change is detected in the observed directory.

When you run Example 32.8, create a new file in the directory from which you will run the example. The program will detect the new file and write a message to the standard output stream.

Example 32.9. Using boost::asio::windows::overlapped_ptr
#include <boost/asio/io_service.hpp>
#include <boost/asio/windows/overlapped_ptr.hpp>
#include <boost/system/error_code.hpp>
#include <iostream>
#include <Windows.h>

using namespace boost::asio;
using namespace boost::system;

int main()
{
  io_service ioservice;

  HANDLE file_handle = CreateFileA(".", FILE_LIST_DIRECTORY,
    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
    OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

  error_code ec;
  auto &io_service_impl = use_service<detail::io_service_impl>(ioservice);
  io_service_impl.register_handle(file_handle, ec);

  char buffer[1024];
  auto handler = [&buffer](const error_code &ec, std::size_t) {
    if (!ec)
    {
      auto notification =
        reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer);
      std::wcout << notification->Action << '\n';
      std::streamsize size = notification->FileNameLength / sizeof(wchar_t);
      std::wcout.write(notification->FileName, size);
    }
  };
  windows::overlapped_ptr overlapped{ioservice, handler};
  DWORD transferred;
  BOOL ok = ReadDirectoryChangesW(file_handle, buffer, sizeof(buffer),
    FALSE, FILE_NOTIFY_CHANGE_FILE_NAME, &transferred, overlapped.get(),
    NULL);
  int last_error = GetLastError();
  if (!ok && last_error != ERROR_IO_PENDING)
  {
    error_code ec{last_error, error::get_system_category()};
    overlapped.complete(ec, 0);
  }
  else
  {
    overlapped.release();
  }

  ioservice.run();
}

Example 32.9 uses ReadDirectoryChangesW() like the previous one to monitor a directory. This time, the asynchronous call to ReadDirectoryChangesW() isn’t linked to an event handle. The example uses the class boost::asio::windows::overlapped_ptr, which uses an OVERLAPPED structure internally. get() retrieves a pointer to the internal OVERLAPPED structure. In the example, the pointer is then passed to ReadDirectoryChangesW().

boost::asio::windows::overlapped_ptr is an I/O object that has no member function to start an asynchronous operation. The asynchronous operation is started by passing a pointer to the internal OVERLAPPED variable to a Windows function. In addition to an I/O service object, the constructor of boost::asio::windows::overlapped_ptr expects a handler that will be called when the asynchronous operation completes.

Example 32.9 uses boost::asio::use_service() to get a reference to a service in the I/O service object ioservice. boost::asio::use_service() is a function template. The type of the I/O service you want to fetch has to be passed as a template parameter. In the example, boost::asio::detail::io_service_impl is passed. This type of the I/O service is closest to the operating system. On Windows, boost::asio::detail::io_service_impl uses IOCP, and on Linux it uses epoll(). boost::asio::detail::io_service_impl is a type definition that is set to boost::asio::detail::win_iocp_io_service on Windows and to boost::asio::detail::task_io_service on Linux.

boost::asio::detail::win_iocp_io_service provides the member function register_handle() to link a handle to an IOCP handle. register_handle() calls the Windows function CreateIoCompletionPort(). This call is required for the example to work correctly. The handle returned by CreateFileA() may be passed through overlapped to ReadDirectoryChangesW() only after it is linked to an IOCP handle.

Example 32.9 checks whether ReadDirectoryChangesW() has failed. If ReadDirectoryChangesW() failed, complete() is called on overlapped to complete the asynchronous operation for Boost.Asio. The parameters passed to complete() are forwarded to the handler.

If ReadDirectoryChangesW() succeeds, release() is called. The asynchronous operation is then pending and is only completed after the operation which was initiated with the Windows function ReadDirectoryChangesW() has completed.

Example 32.10. Using boost::asio::posix::stream_descriptor
#include <boost/asio/io_service.hpp>
#include <boost/asio/posix/stream_descriptor.hpp>
#include <boost/asio/write.hpp>
#include <boost/system/error_code.hpp>
#include <iostream>
#include <unistd.h>

using namespace boost::asio;

int main()
{
  io_service ioservice;

  posix::stream_descriptor stream{ioservice, STDOUT_FILENO};
  auto handler = [](const boost::system::error_code&, std::size_t) {
    std::cout << ", world!\n";
  };
  async_write(stream, buffer("Hello"), handler);

  ioservice.run();
}

Example 32.10 introduces an I/O object for POSIX platforms.

boost::asio::posix::stream_descriptor can be initialized with a file descriptor to start an asynchronous operation on that file descriptor. In the example, stream is linked to the file descriptor STDOUT_FILENO to write a string asynchronously to the standard output stream.