Die Boost C++ Bibliotheken

Plattformspezifische I/O Objekte

Alle bisherigen Beispiele in diesem Kapitel sind plattformunabhängig. So stehen I/O Objekte wie boost::asio::steady_timer oder boost::asio::ip::tcp::socket auf allen Plattformen zur Verfügung. Boost.Asio bietet jedoch auch plattformspezifische I/O Objekte an, über die Sie asynchrone Operationen anstoßen können, wie sie beispielsweise nur unter Windows oder nur unter POSIX-Betriebssystemen möglich sind.

Beispiel 32.8. boost::asio::windows::object_handle in Aktion
#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();
}

Im Beispiel 32.8 wird das I/O Objekt boost::asio::windows::object_handle verwendet, das ausschließlich unter Windows zur Verfügung steht. boost::asio::windows::object_handle ermöglicht es, asynchrone Operationen für Objekt-Handles anzustoßen, die an die Windows-Funktion RegisterWaitForSingleObject() übergeben werden können. boost::asio::windows::object_handle basiert auf RegisterWaitForSingleObject(). Mit async_wait() bietet das I/O Objekt die Möglichkeit an, asynchron auf eine Veränderung eines Objekt-Handles zu warten.

Im obigen Beispiel wird obj_handle vom Typ boost::asio::windows::object_handle mit einem Handle initialisiert, der mit der Windows-Funktion CreateEvent() erstellt wurde. Der Handle ist Teil einer OVERLAPPED-Struktur, deren Adresse an die Windows-Funktion ReadDirectoryChangesW() übergeben wird. OVERLAPPED-Strukturen werden unter Windows verwendet, um asynchrone Operationen anzustoßen.

Mit ReadDirectoryChangesW() kann ein Verzeichnis auf Veränderungen überwacht werden. Damit die Funktion als asynchrone Operation ausgeführt wird, muss eine OVERLAPPED-Struktur übergeben werden. Damit der Abschluß der asynchronen Operation über Boost.Asio gemeldet wird, wird ein Event-Handle in der OVERLAPPED-Struktur gesetzt, bevor diese an ReadDirectoryChangesW() übergeben wird. Indem dieser Handle anschließend an obj_handle übergeben und async_wait() aufgerufen wird, kann im Handler auf eine Veränderung im Verzeichnis reagiert werden.

Wenn Sie obiges Beispiel ausführen und im gleichen Verzeichnis, in dem Sie das Beispiel ausführen, eine neue Datei erstellen, wird für diese Veränderung im Verzeichnis eine entsprechende Meldung auf die Standardausgabe ausgegeben.

Beispiel 32.9. boost::asio::windows::overlapped_ptr in Aktion
#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();
}

Im Beispiel 32.9 wird wie im vorherigen mit ReadDirectoryChangesW() ein Verzeichnis überwacht. Diesmal ist der asynchrone Aufruf von ReadDirectoryChangesW() nicht mit einem Event-Handle verknüpft. Das Beispiel verwendet eine Klasse boost::asio::windows::overlapped_ptr, die intern eine OVERLAPPED-Struktur verwendet. Über get wird ein Zeiger auf die interne OVERLAPPED-Struktur erhalten. Dieser wird im Beispiel an ReadDirectoryChangesW() übergeben.

Bei boost::asio::windows::overlapped_ptr handelt es sich um ein I/O Objekt, das keine Methode anbietet, um eine asynchrone Operation zu starten. Die asynchrone Operation wird gestartet, indem ein Zeiger auf die interne OVERLAPPED-Struktur an eine entsprechende Windows-Funktion übergeben wird. Der Konstruktor von boost::asio::windows::overlapped_ptr erwartet neben einer Referenz auf ein I/O Serviceobjekt einen Handler, der aufgerufen wird, wenn die asynchrone Operation beendet wurde.

Beachten Sie, dass im Beispiel mit boost::asio::use_service() eine Referenz auf einen Service im I/O Serviceobjekt ioservice erhalten wird. boost::asio::use_service() ist eine Template-Funktion, der der Typ des Services als Template-Parameter übergeben werden muss. Im Beispiel wird über den Typ boost::asio::detail::io_service_impl Zugriff auf den I/O Service erhalten, der die betriebssystemnächste Schnittstelle im I/O Serviceobjekt ist. Unter Windows greift boost::asio::detail::io_service_impl auf IOCP zu, unter Linux zum Beispiel auf epoll(). boost::asio::detail::io_service_impl ist eine Typdefinition, die unter Windows auf boost::asio::detail::win_iocp_io_service und unter Linux auf boost::asio::detail::task_io_service gesetzt sein kann.

boost::asio::detail::win_iocp_io_service bietet eine Methode register_handle() an, um einen Handle mit dem IOCP-Handle zu verknüpfen. register_handle() ruft die Windows-Funktion CreateIoCompletionPort() auf. Dies ist nötig, damit das Beispiel richtig funktioniert. Der Handle, der von CreateFileA() zurückgegeben wird, darf erst nach einer derartigen Verknüpfung über overlapped an ReadDirectoryChangesW() übergeben werden.

Beachten Sie außerdem, dass für overlapped nach dem Aufruf von ReadDirectoryChangesW() complete() oder release() aufgerufen werden muss. Im Beispiel wird überprüft, ob ReadDirectoryChangesW() fehlgeschlagen ist und die asynchrone Operation womöglich schon beendet ist. In diesem Fall wird complete() aufgerufen, um die asynchrone Operation für Boost.Asio zu beenden. Die Parameter, die an complete() übergeben werden, werden an den Handler weitergereicht.

War der Aufruf von ReadDirectoryChangesW() erfolgreich, wird release() aufgerufen. In diesem Fall gilt die asynchrone Operation als schwebend und wird erst beendet, wenn die asynchrone Windows-Operation, die mit ReadDirectoryChangesW() eingeleitet wurde, beendet ist.

Beispiel 32.10. boost::asio::posix::stream_descriptor in Aktion
#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();
}

Beispiel 32.10 stellt ein I/O Objekt für POSIX-Betriebssysteme vor. boost::asio::posix::stream_descriptor kann mit einem File Descriptor initialisiert werden, um eine asynchrone Operation für diesen zu starten. Im Beispiel wird stream mit dem File Descriptor STDOUT_FILENO verknüpft, um einen String asynchron in die Standardausgabe zu schreiben.