Skip to content
Ben Stolovitz edited this page May 6, 2019 · 10 revisions

The Windows Implementation Libraries (WIL) are a collection of header-only libraries used across Windows source code to make life easier for developers on Windows through readable type-safe C++ interfaces for common Windows coding patterns. For example, the WIL resource wrappers include "unique_ptr-like" wrappers for internal types, like HANDLEs and events.

Features

For the coolest features, see:

Supported error handling styles

WIL intends on being equally usable from both exception-based and error-code based code whenever possible. WIL also supports and encourages the use of fail-fast.

The names of functions and classes reflect the error handling style. Error-code based functions and classes always end with a _nothrow suffix. Fail-fast based functions and classes always end with a _failfast suffix. The best names (without a suffix) are reserved for exception-based routines and routines that do not produce errors.

Neutral (no errors)

When possible, WIL prefers error-handling–neutral code. Specifically, this refers to classes and functions that will never propagate an error (either through exceptions or the return of error codes). For example, take the following WIL call:

wil::detach_to_opt_param(outParam, smartPtr);

This detaches the raw resource from a smart pointer if the given optional out param is non-null. The routine cannot fail at runtime; if you were trying to use an unsupported smart pointer type, it would simply static_assert and fail at compile time.

Exceptions

Prefer exception-based error handling over error code-based handling when possible; it both enables the use of modern libraries and reduces error handling clutter. Exceptions also enable some patterns that aren't possible using error codes:

THROW_IF_FAILED(wil::com_query<IPersistFile>(collection)->Save(name, FALSE));

Notice that the use of WIL's exception-based com_query helper allows acquisition of the interface and the actual call to the method can be folded together on the same line.

Exception-based code can cleanly interop with error-code based code (including ABIs or system callbacks) through the use of exception guards defined within WIL's Error Handling Helpers.

WIL's exception-based classes and routines are hidden from code that does not have exceptions enabled through the _CPPUNWIND define which is set based on your compiler options.

Error codes

WIL normalizes on HRESULT as its error code currency. Nearly all WIL classes that can produce an error from one or more methods have a version of the class named with a _nothrow suffix whose methods return HRESULT on failure:

wil::unique_event_nothrow taskReadyEvent;
RETURN_IF_FAILED(taskReadyEvent.open(m_taskReadyEventName, EVENT_ALL_ACCESS));

Global functions that can produce an error will also have a version of the function with a _nothrow suffix that returns an HRESULT on failure:

RETURN_IF_FAILED(wil::com_query_to_nothrow(m_agileReference, IID_PPV_ARGS(listener)));

Some global functions only produce an error due to an allocation failure. The error-code based version of those functions are also named with a _nothrow suffix, but expect the caller to validate that the returned object is not null:

auto undo = wil::make_unique_nothrow<CApplyPropertiesUndo>();
RETURN_IF_NULL_ALLOC(undo);

Fail fast

Nearly all WIL classes that can produce an error from one or more methods have a version of the class named with a _failfast suffix whose methods will fail fast on failure. In the following example, the create() call will fail fast if unable to create the event:

wil::unique_event_failfast pong_received;
pong_received.create();

Similarly, global functions that can produce an error will also have a version of the function with a _failfast suffix that will terminate the process on failure:

auto serializer = wil::make_unique_failfast<JSONSerializer>();

Note that WIL internally will utilize fail fast to prevent callers from making some programming errors and to terminate a process when an unrecoverable invariant has been violated (for example, failure of a synchronization primitive).

Namespaces and naming conventions

WIL has a small handful of namespaces it uses to isolate code and uses specific naming conventions for macros and error handling style.

Note that WIL's namespaces are specifically designed to be short and typed with each use. Reference functions and typenames directly with the namespace qualification: wil::unique_event. Avoid removing these namespaces (avoid using using namespace wil; or using namespace wistd;, for example) as the namespaces easily collide with STL and global Windows functions making your code more susceptible to naming conflicts and making it more difficult make WIL or STL changes.

wil::

All WIL classes and functions are, by default, available behind the wil namespace.

wistd::

Some aspects of the STL are core language concepts that should be used from all C++ code, regardless of whether exceptions are enabled in the component. WIL selectively pulls in necessary exception-neutral functionality from STL with almost no modification. This functionality is available behind the wistd namespace and exactly mirrors the same functionality behind the std namespace.

Note that callers able to use exceptions should always prefer the std counterparts over wistd.

MACROS

WIL prefixes macros with the WI_ prefix to help ensure that it's macros don't have name collisions with other similarly named constructs. Example:

constexpr GattCharacteristicConfigurationFlags c_validGattCharacteristicConfigurationFlags =
    WI_COMPILETIME_COMBINE_FLAGS(GattCharacteristicConfigurationFlags::Notification, GattCharacteristicConfigurationFlags::Indication);

Macros will normally be in WI_ALL_CAPS to signify that they are macros and may not behave quite like a normal function call. WIL prefixes macros that emulate function calls in every way with the same WI_ prefix, but uses PascalCase for their naming. These can be used as though they were simple functions. Example:

if (WI_IsFeatureEnabled(Feature_PenWorkSpace))
{
    // Code ...
}

Note that the error handling helpers library chooses to stick with a non-prefixed, but longer and unique macro names like THROW_IF_WIN32_BOOL_FALSE to maximize readability given the frequency with which those macros are expected to be used.

Error handling style

WIL reserves the best names for error-code neutral code and for exception-based code. To distinguish functions and classes that instead return error codes on failure or fail fast on failure, WIL uses the _nothrow and _failfast suffixes.

For example, the unique_event class also has unique_event_failfast with the exact same contract (fail fast replacing exceptions) and unique_event_nothrow with the contract altered for methods that can fail to return HRESULTs. Class methods are not suffixed, only the class names are.

Global methods are similarly suffixed. For example, the make_event_watcher routine also has make_event_watcher_nothrow and make_event_watcher_failfast.

WIL supports multiple error handling policies in order to accommodate a wide variety of clients. Consumers will typically decide whether the consumer's overall policy is error-code-based or exception-based, and use that policy throughout (perhaps with some fail-fast sprinkled in where applicable). Consumers are not expected to support all error handling styles; support just the style you need.

File and library organization

WIL headers are created based upon two key factors: dependencies and/or functionality.

You can think of dependencies pivot as the area being covered. Specifically, these are headers like:

  • stl.h
  • winrt.h
  • com.h
  • filesystem.h

The goals of these headers are really to create a set of wrappers; functions and classes that operate all with a common set of dependencies (nothing added to any of these locations should ever add a new dependency). Most code within these headers offer simple usability wrappers or interoperability wrappers across specific system or library constructs.

The other pivot is by functionality. These are headers like:

  • result.h
  • staging.h
  • WrlEvent.h

This is where we have a large piece of independent functionality (either a single class or a collection of routines that together define something interesting); new dependencies are unlikely, but could be added to handle the key functionality being delivered. These generally are not simple wrappers around existing system constructs, but provide something more.

Kernel mode

WIL is primarily consumed in user-mode C++, but some libraries have been enlightened for use from Kernel components:

Deprecation

WIL includes a mechanism for deprecating things that should no longer be used. If a component is deprecated, it will be marked clearly with "deprecated" comments. There is a system of ifdefs to identify deprecated features (and when they were deprecated) and enable clients to opt into only using the non-deprecated parts of WIL.