Skip to content
Keith Horton edited this page Nov 2, 2025 · 1 revision

WIL network helpers are free-function helpers and classes for working with BSD sockets, Winsock functions, and network address structures. They provide simplified, type-safe access to Windows networking APIs with RAII types and error handling.

Usage

To use WIL network helpers, add wil/network.h to your C++ source file:

Requirements

Some of the network functions have specific requirements:

Requires
Functions that throw Requires C++ exceptions
Functions that are no_throw Do not require or use C++ exceptions
STL string return types Requires C++ exceptions and <string>
Link libs Links with ntdll.lib and ws2_32.lib

Including Headers

/wil/network.h has the below #include set of Winsock and networking headers to be referenced in a deliberate sequence to ensure all types and functions are available. Windows network headers can have intra-header dependencies, creating difficulties when needing access to various functions and types if not included in the correct order.

#include <winsock2.h>
#include <ws2def.h>
#include <ws2ipdef.h>
#include <mswsock.h>
#include <mstcpip.h>
#include <ws2tcpip.h>
#include <windns.h>
#include <iphlpapi.h>

WSAStartup and Cleanup

WSAStartup must be called before using any Winsock functions. WIL provides RAII types to simplify this process. The returned object automatically calls WSACleanup when it goes out of scope. It is recommended to keep the WSAStartup return value in a variable for the lifetime of all your networking operations.

Error Handling Function name Full Signature
Exception wil::network::WSAStartup wil::network::unique_wsacleanup_call WSAStartup()
No-throw wil::network::WSAStartup_nothrow wil::network::unique_wsacleanup_call WSAStartup_nothrow() WI_NOEXCEPT
Fail-fast wil::network::WSAStartup_failfast wil::network::unique_wsacleanup_call WSAStartup_failfast() WI_NOEXCEPT

Returns

Returns a wil::network::unique_wsacleanup_call RAII object that automatically calls WSACleanup when destroyed. The object evaluates to false if WSAStartup failed (no-throw version only).

// handling WSAStartup failure without exceptions
const auto wsaCleanup = wil::network::WSAStartup_nothrow();
if (!wsaCleanup)
{
    // WSAStartup failed, handle error
    return;
}

Socket Address Class

The wil::network::socket_address class provides a type-safe encapsulation around the network structures used with the sockaddr structure.

Commonly used sockaddr* types which the wil::network::socket_address class provides utility in working through type-safe interfaces. Note that the wil::network::socket_address class handles all required byte-order conversions.

Native type Description
sockaddr_storage / SOCKADDR_STORAGE A sockaddr-derived type that is guaranteed to be large enough to hold any possible socket address - not limited to TCPIP related-addresses.
sockaddr_in / SOCKADDR_IN A sockaddr-derived type designed to contain an IPv4 address and port number.
sockaddr_in6 / SOCKADDR_IN6 A sockaddr-derived type designed to contain an IPv6 address, port, scope id, and flow info.
sockaddr_inet / SOCKADDR_INET A union of sockaddr_in and sockaddr_in6. Large enough to contain any TCPIP IPv4 or IPv6 address.
in_addr / IN_ADDR The address field in a sockaddr_in.
in6_addr / IN6_ADDR The address field in a sockaddr_in6.
SOCKET_ADDRESS Not a derived sockaddr* type. A structure with members containing both a sockaddr* and its length fields. Returned from some networking functions

Constructors

Constructor Description
socket_address() Default constructor - sets the famaily to AF_UNSPEC (zero)
socket_address(ADDRESS_FAMILY family) Construct with the specified address family. The family must be AF_UNSPEC, AF_INET, or AF_INET6
socket_address(const SOCKADDR* addr, size_t addr_size) Constructs from the provided sockaddr
socket_address(const SOCKADDR_IN* addr) Constructs from the provided IPv4 sockaddr
socket_address(const SOCKADDR_IN6* addr) Constructs from the provided IPv6 sockaddr
socket_address(const IN_ADDR* addr, unsigned short port = 0) Constructs from the provided IPv4 address and port
socket_address(const IN6_ADDR* addr, unsigned short port = 0) Constructs from the provided IPv6 address and port
socket_address(PCWSTR addr, unsigned short port = 0) Constructs from the provided string and port (requires exceptions)

Address Update Methods

Method Description
void reset() Resets the contained address to the all-zero address with the AF_UNSPEC (zero) family
void reset(ADDRESS_FAMILY family) Resets the contained address to the all-zero address with the specified address family. The family must be AF_UNSPEC, AF_INET, or AF_INET6
void set_address_any() Sets the contained address to the all-zero any address (also called the wildcard address)
void set_address_loopback() Sets the contained address to loopback address
void set_port(USHORT port) Sets the port number
void set_scope_id(ULONG scopeId) Sets the IPv6 scope ID
void set_flow_info(ULONG flowInfo) Sets the IPv6 flow info

Address Property Accessors

All values are returned in host byte order (they are stored in network byte order internally).

Method Description
ADDRESS_FAMILY family() Returns the address family
USHORT port() Returns the port number
ULONG scope_id() Returns the IPv6 scope ID
ULONG flow_info() Returns the IPv6 flow info
bool is_address_loopback() Checks if loopback address
bool is_address_linklocal() Checks if link-local address
NL_ADDRESS_TYPE address_type() Get address type classification

String Formatting

Method Description
HRESULT format_address_nothrow(socket_address_wstring& address) Formats the IP address to the provided string object (no-throw)
HRESULT format_complete_address_nothrow(socket_address_wstring& address) Formats the complete IP address with port to the provided string object (no-throw)
std::wstring format_address() Formats the IP address to the returned std::wstring object (requires exceptions)
std::wstring format_complete_address() Formats the complete address with port to the returned std::wstring object (requires exceptions)

SOCKADDR Structure Access

Method Description
SOCKADDR* sockaddr() Returns the encapsulated address as a generic sockaddr*
SOCKADDR_IN* sockaddr_in() Returns the encapsulated address as IPv4 sockaddr_in*
SOCKADDR_IN6* sockaddr_in6() Returns the encapsulated address as IPv6 sockaddr_in6*
IN_ADDR* in_addr() Returns the encapsulated IPv4 address in_addr*
IN6_ADDR* in6_addr() Returns the encapsulated IPv6 address in6_addr*
SOCKADDR_STORAGE sockaddr_storage() Returns the encapsulated address into a returned sockaddr_storage structure

Address Resolution

Functions and classes are provided to simplify resolving hostnames to addresses, including managing the memory of the returned ADDRINFO structures.

Address Iterator

The addr_info_iterator class provides STL-compatible iteration over ADDRINFO structures. Three variants are provided for ANSI, Unicode, and extended ADDRINFOEX structures. This iterator can be used in range-based for loops and STL algorithms. This iterator works with the related RAII object wil::unique_addrinfo which manages the memory of the returned ADDRINFO* list.

Iterator Type Description
addr_info_ansi_iterator Iterates over ADDRINFOA* structures
addr_info_iterator Iterates over ADDRINFOW* structures
addr_infoex_iterator Iterates over ADDRINFOEXW* structures
#include "wil/network.h"

// the example shows resolving a name when C++ exceptions are enabled
// the code assumes WSAStartup has already been called
// e.g.:
//     auto wsaCleanup = wil::network::WSAStartup();
//
// It is generally recommended that WSAStartup should be scoped for the lifetime of all networking operations
// not calling WSAStartup/WSACleanup per-function
wil::unique_socket connect_to_hostname(PCWSTR hostname)
{
    // Resolve a hostname
    auto addresses = wil::network::resolve_name(hostname);

    // Iterate through all resolved addresses
    for (const auto& addr : wil::network::addr_info_iterator{addresses.get()})
    {
        // Try to connect to each address
        wil::unique_socket sock{::socket(addr.family(), SOCK_STREAM, IPPROTO_TCP)};
        if (::connect(sock.get(), addr.sockaddr(), addr.size()) == 0)
        {
            // Connected successfully
            return sock;
        }
    }

    // If we reach here, no connection was successful
    return {};
}

Extension Function Tables

WIL provides RAII types for loading Winsock extension functions. These returned extension function table objects automatically maintain a WSAStartup reference and load the function pointers on construction. The function tables handle the complexity of using WSAIoctls to dynamically load these extension functions.

Available Extension Function Tables

Type Description
winsock_extension_function_table Standard Winsock extension functions (AcceptEx, ConnectEx, etc.)
rio_extension_function_table Registered I/O (RIO) extension functions
process_socket_notification_table Socket notification functions (Windows 10 version 2004+)

Winsock Extension Function Table

The winsock_extension_function_table provides access to the standard Winsock extension functions that have been available since Windows Vista.

Extension Functions

Function Description
AcceptEx Accepts a new connection, returns the local and remote address, and receives the first block of data sent by the client application
ConnectEx Establishes a connection to a specified socket, and optionally sends data after the connection is established
DisconnectEx Closes a connection and optionally allows the socket to be reused
GetAcceptExSockaddrs Parses the data obtained from AcceptEx and passes the local and remote addresses
TransmitFile Transmits file data over a connected socket
TransmitPackets Transmits in-memory data or file data over a connected socket
WSARecvMsg Can be used to receive data and optional control information. Can only be used with datagram and raw sockets.
WSASendMsg Can be used to send data and optional control information. Can only be used with datagram and raw sockets.

RIO Extension Function Table

The rio_extension_function_table provides access to Registered I/O (RIO) extension functions for high-performance networking. RIO is designed for applications that require extremely high throughput and low CPU overhead.

Available Functions

Function Description
RIOReceive Receives network data on a connected RIO socket
RIOReceiveEx Receives network data with additional control information
RIOSend Sends network data on a connected RIO socket
RIOSendEx Sends network data with additional control information
RIOCloseCompletionQueue Closes a completion queue
RIOCreateCompletionQueue Creates a completion queue for RIO sockets
RIOCreateRequestQueue Creates a request queue for sending and receiving data
RIODequeueCompletion Removes entries from a completion queue
RIODeregisterBuffer Deregisters a buffer used with RIO
RIONotify Registers a method to use for notification
RIORegisterBuffer Registers a buffer for use with RIO
RIOResizeCompletionQueue Resizes a completion queue
RIOResizeRequestQueue Resizes a request queue

Socket Notification Function Table

The process_socket_notification_table provides access to socket notification functions available in Windows 10 version 2004 and later. These functions enable efficient event-driven socket programming.

Available Functions

Function Description
ProcessSocketNotifications Associates a set of sockets with a completion port, and retrieves any notifications that are already pending on that port.

See ProcessSocketNotifications on Microsoft Learn for detailed documentation.

Examples

Basic Usage

// Default construction (AF_UNSPEC)
wil::network::socket_address default_addr;

// Construct with address family
wil::network::socket_address v4_addr{AF_INET};
wil::network::socket_address v6_addr{AF_INET6};

// From raw address structures
SOCKADDR_IN v4_sockaddr{};
v4_sockaddr.sin_family = AF_INET;
v4_sockaddr.sin_port = htons(8080); // the raw structure requires network byte order
wil::network::socket_address addr_from_v4{&v4_sockaddr};

// From IN_ADDR/IN6_ADDR with optional port
in_addr ipv4_address{};
wil::network::socket_address addr{&ipv4_address, 8080};

// From string (requires exceptions)
wil::network::socket_address addr_from_string{L"192.168.1.1", 8080};

// Set to wildcard/any address
wil::network::socket_address addr;
addr.set_address_any(AF_INET); // 0.0.0.0
addr.set_port(8080); // assinging ports does not require conversion to network byte order

// Set to loopback address
addr.set_address_loopback(AF_INET6); // ::1
addr.set_port(443);

// Set IPv6-specific properties
addr.set_scope_id(10000);
addr.set_flow_info(123456);

// Set specific address from string
addr.reset_address_nothrow(L"127.0.0.1");
addr.set_port(80);

// Get address family
ADDRESS_FAMILY family = addr.family(); // AF_INET

// Get port (returned in host byte order)
USHORT port = addr.port(); // 80

// Check address characteristics
bool isLoopback = addr.is_address_loopback(); // true
bool isLinkLocal = addr.is_address_linklocal(); // false
NL_ADDRESS_TYPE type = addr.address_type(); // NlatUnicast

String Formatting

wil::network::socket_address addr{L"2001::1", 8080};

// Format just the IP address (nothrow version)
wil::network::socket_address_wstring address_str;
HRESULT hr = addr.format_address_nothrow(address_str);
// Returns "2001::1"

// Format complete address with port and scope (nothrow version)
wil::network::socket_address_wstring complete_str;
hr = addr.format_complete_address_nothrow(complete_str);
// Returns "[2001::1]:8080"

// Exception-based versions
std::wstring addr_str = addr.format_address();
std::wstring complete = addr.format_complete_address();

// ANSI string versions also available
wil::network::socket_address_string ansi_str;
hr = addr.format_address_nothrow(ansi_str);

Example: Address Comparison and Ordering

wil::network::socket_address addr1{L"192.168.1.1", 80};
wil::network::socket_address addr2{L"192.168.1.2", 80};

// Equality comparison
if (addr1 == addr2)
{
    // Addresses are identical
}

// Ordering (useful for std::map, std::set)
if (addr1 < addr2)
{
    // addr1 sorts before addr2
}

// Manual comparison (-1, 0, 1 like memcmp)
int result = addr1.compare(addr2);

Example: Convert IPv4 to IPv4-mapped IPv6 address

// Convert IPv4 address to IPv4-mapped IPv6 for dual-mode sockets
wil::network::socket_address ipv4_addr{L"192.168.1.1", 8080};

// mapped is an IPv6 address, containing the IPv4-mapped address
wil::network::socket_address mapped = wil::network::map_dual_mode_4to6(ipv4_addr);

// Can now connect dual-mode IPv6 socket to IPv4 address
wil::unique_socket dualModeSocket{::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)};

// Set the IPV6_V6ONLY option to 0 (dual-mode)
DWORD opt = 0; // 0 to disable IPV6_V6ONLY
if (setsockopt(dualModeSocket.get(), IPPROTO_IPV6, IPV6_V6ONLY, (char *)&opt, sizeof(opt)) == SOCKET_ERROR) {
    return ::WSAGetLastError();
}

// Connect to the mapped IPv6 address
if (::connect(dualModeSocket.get(), mapped.sockaddr(), mapped.size()) == SOCKET_ERROR) {
    return ::WSAGetLastError();
}

Example: Using Extension Functions (AcceptEx)

#include "wil/network.h"

// Assumes WSAStartup has already been called
// create a listening socket and post an AcceptEx on it
int accept_a_connection_over_loopback(uint16_t port)
{
    wil::network::winsock_extension_function_table extension_functions;

    wil::unique_socket listeningSocket{::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)};
    if (!listeningSocket)
    {
        return ::WSAGetLastError();
    }

    wil::network::socket_address listenAddress{AF_INET6};
    listenAddress.set_address_loopback();
    listenAddress.set_port(port);
    const auto bind_error = ::bind(listeningSocket.get(), listenAddress.sockaddr(), listenAddress.size());
    if (bind_error != 0)
    {
        return ::WSAGetLastError();
    }

    const auto listen_error = ::listen(listeningSocket.get(), 1);
    if (listen_error != 0)
    {
        return ::WSAGetLastError();
    }

    // the buffer to supply to AcceptEx to capture the address information
    static constexpr size_t singleAddressOutputBufferSize = listenAddress.size() + 16;
    char acceptex_output_buffer[singleAddressOutputBufferSize * 2]{};
    wil::unique_socket acceptSocket{::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)};
    if (!acceptSocket)
    {
        return ::WSAGetLastError();
    }

    DWORD acceptex_bytes_received{};
    wil::unique_event_nothrow acceptex_overlapped_event{};
    acceptex_overlapped_event.create(wil::EventOptions::ManualReset);

    OVERLAPPED acceptex_overlapped{};
    acceptex_overlapped.hEvent = acceptex_overlapped_event.get();

    // must pass an OVERLAPPED*, cannot pass null
    auto acceptex_return = extension_functions->AcceptEx(
        listeningSocket.get(),
        acceptSocket.get(),
        acceptex_output_buffer,
        0,
        singleAddressOutputBufferSize,
        singleAddressOutputBufferSize,
        &acceptex_bytes_received,
        &acceptex_overlapped);
    if (!acceptex_return)
    {
        gle = ::WSAGetLastError();
        if (gle != ERROR_IO_PENDING)
        {
            return gle;
        }
    }

    // this scope guard is a guarantee against OVERLAPPED -sourced heap/stack corruption
    // the scope guard ensures that we wait for this async (overlapped) call to complete
    // before we exit this function, regardless of how we exit (return, exception, etc)
    const auto ensure_acceptex_overlapped_completes = wil::scope_exit([&] {
        // close the sockets to cancel any pended IO
        acceptSocket.reset();
        listeningSocket.reset();
        // now wait for our async call
        acceptex_overlapped_event.wait();
    });

    // must waiting for the overlapped AcceptEx to complete
    // i.e., for the acceptex_overlapped_event to be signaled
}
Clone this wiki locally