Thursday, April 5, 2012

monitor_smartcard_readers implementation

The Win32 API to monitor smart card readers is SCardGetStatusChange. However its usage is sufficiently complex and arcane that a post like this is warranted to show usage.

SCardGetStatusChange blocks until a change takes place at which point it returns. An array containing state for each reader is passed in to indicate the state as understood by the caller and when the function detects a difference between the actual state and the state in the array the function updates the array with the new state and returns. The caller then processes the changes and then calls back with its new understanding.

template<
  typename SetContext, 
  typename ClearContext,
  typename Wait,
  typename Report
>
unique_winerror monitor_smartcard_readers(
  SetContext && setContext,
  ClearContext && clearContext,
  Wait && wait,
  Report &&  report
)
{
  unique_winerror winerror;

These hold the output of SCardListReaders and the input/output for SCardGetStatusChange. In fact the readers elements contain pointers into the readernames array. This is why readernames is first so that its lifetime exceeds the lifetime of readers. There is additional code to maintain this lifetime contract further down.

  std::vector<wchar_t> readernames;
  std::vector<SCARD_READERSTATE> readers;

  while (winerror) {

make sure that the smart card service has started and that the loop has not been cancelled


    if (!std::forward<Wait>(wait)()) {
      return winerror_cast(SCARD_E_CANCELLED);
    }

    monitor_error_contract(
    [&]() {

      unique_close_scardcontext context;
      ON_UNWIND_AUTO(
        [&] {
          std::forward<ClearContext>(clearContext)();
        }
      );

need a fresh context whenever we start over. lots of system changes could have caused this restart including the smart card service stopping.


      winerror.reset(
        SCardEstablishContext(
            SCARD_SCOPE_USER,
            NULL,
            NULL,
            context.replace()
        )
      );
      if (!winerror || !context) {
        return;
      }

      std::forward<SetContext>(setContext)(context.get());

make sure that the loop has not been cancelled. without this second wait there is a race where the new context is not cancelled because the caller cancelled the context at a time when there was no context.


      if (!std::forward<Wait>(wait)()) {
        winerror = winerror_cast(SCARD_E_CANCELLED);
        return;
      }

      if (readers.empty()) {

Windows allows smart card readers to be inserted and removed from the machine. In order to support these Plug-and-Play events a static reader name was defined so that SCardGetStatusChange can report PnP changes. the make function defaults the initial state to unaware which causes SCardGetStatusChange to return immediately with the actual PnP state.

        readers.push_back(
          make(L"\\\\?PnP?\\Notification")
        );
      }

      for (;;) {
        auto readersstaterange = lib::rng::make_range_raw(
          readers
        );

        winerror.reset(
          SCardGetStatusChange(
              context.get(),
              INFINITE,
              readersstaterange.begin(),
              lib::rng::size_cast<DWORD>(
                readersstaterange.size()
              )
          )
        );
        if (!winerror) {
          // exit
          return;
        }

report the smart card reader state

        auto readersrange = lib::rng::make_range_raw(
          readers, 
          0, 
          -1
        );
        if (!readersrange.empty()) {
          std::forward<Report>(report)(readersrange);
        }

record the changes now that they have been reported. setting dwCurrentState = dwEventState is required before calling SCardGetStatusChange again or the function will just return immediately to report the same changes.

        for (auto & state : readers) {
          state.dwCurrentState = state.dwEventState;
        }

        if ((
            readers.back().dwEventState & 
            SCARD_STATE_CHANGED
          ) == SCARD_STATE_CHANGED
        ) {
          // Pnp event - list readers.
          break;
        }
      }

A PnP event occured. must keep the existing allocations for use while building the new list.

      std::vector<wchar_t> oldreadernames(
        std::move(readernames)
      );
      std::vector<SCARD_READERSTATE> oldreaders(
        std::move(readers)
      );

create a range of the existing reader states that excludes the static pnp state

      auto oldreaderssortedrange = lib::rng::make_range(
        oldreaders,
        0, 
        -1
      );

      LPWSTR concatreaderstrings = nullptr;
      ON_UNWIND_AUTO(
        [&] { 
        if (concatreaderstrings) {
          SCardFreeMemory(
            context.get(), 
            concatreaderstrings
          );
        };
      }
      );
      DWORD totallength = SCARD_AUTOALLOCATE;

      winerror.reset(
        SCardListReaders(
            context.get(),
            nullptr,
            reinterpret_cast<LPWSTR>(&concatreaderstrings),
            &totallength
        )
      );

no readers is not an error, just loop around to wait for a reader to be connected

      if (winerror == 
          winerror_cast(SCARD_E_NO_READERS_AVAILABLE)
      ) {
        winerror.suppress().release();
        return;
      } else if (!winerror) {
        return;
      }

save a copy of the reader names because the state array will have pointers into them.

      readernames.assign(
        concatreaderstrings, 
        concatreaderstrings + totallength
      );

      auto readerstateless = [](
        const SCARD_READERSTATE & lhs, 
        const SCARD_READERSTATE & rhs
      ) -> bool {
        return _wcsicmp(lhs.szReader, rhs.szReader) < 0;
      };

all the reader names are concatenated in this array with embedded nulls for each and two nulls to mark the end

      auto cursorreadernames = lib::rng::make_range_raw(
        readernames
      );
      while (
        !cursorreadernames.empty() && 
        cursorreadernames.front() != L'\0'
      ) {
        // access the current name
        auto namerange = lib::rng::make_range(
          cursorreadernames,
          0,
          wcslen(
            cursorreadernames.begin()
          ) - cursorreadernames.size()
        );
        // skip to the next name
        cursorreadernames = lib::rng::make_range(
          namerange, 
          namerange.size() + 1, 
          0
        );

find this reader in the old list of readers. This list is kept sorted so that std::equal_range can be used.

        auto oldreader = std::equal_range(
          oldreaderssortedrange.begin(),
          oldreaderssortedrange.end(),
          make(namerange.begin()),
          readerstateless
        );
        if (oldreader.first != oldreader.second) {
          // keep the old state for this reader
          readers.push_back(*oldreader.first);

          // must use the new string allocation,
          // the old one will be gone soon
          readers.back().szReader = namerange.begin();
        } else {
          // this reader has not been seen yet.
          // make defaults to setting the state to 
          // unaware, so that the next call to
          // SCardGetStatusChange will return 
          // immediately with the actual state.
          readers.push_back(make(namerange.begin()));
        }
      }

keeping the reader states sorted makes the updates more stable and allows the std::equal_range above instead of a linear find.

      std::sort(
        readers.begin(), 
        readers.end(), 
        readerstateless
      );

add PnP state query to the new list. keep the existing state, and keep it at the end of the list, out of the sorted area.

      readers.push_back(oldreaders.back());
    }
    );
  }
  return winerror;
}

Tracking the readers state is only the first step. Future posts will explore tracking cards, certificates and subjects/users

Tuesday, April 3, 2012

monitor_smartcard_readers usage

Building a loop to monitor smart cards on windows turns out to be tricky. I have written a function to do this and allow code to be inserted in the necessary places.

template<
  typename SetContext, 
  typename ClearContext,
  typename Wait,
  typename Report
>
unique_winerror monitor_smartcard_readers(
  SetContext && setContext,
  ClearContext && clearContext,
  Wait && wait,
  Report &&  report
);

By itself the data produced by this function isn't too interesting, but I have a project in mind that I hope will use it to advantage.

To use monitor_smartcard_readers you call the function passing in lambdas for the code to insert.

  • setContext when the function establishes a context it calls this lambda to allow the caller to store the context and use it later to call SCardCancel
  • clearContext when the function closes the context it calls this function to inform the caller that it is no longer valid.
  • wait when the function needs to wait for the service to become available or check to see if it should exit it calls this lambda. this lambda must return a bool, true for continue and false for exit.
  • report when the function has an update to the status it calls this lambda with a range of reader states.

A quick example should show how this all stitches together.

void monitor()
{

First need to get the win32 event handle that is set when the smart card service is running.

  HANDLE waitfor[1] = {SCardAccessStartedEvent()};
  ON_UNWIND_AUTO([] {SCardReleaseStartedEvent();});

  unique_winerror winerror;
  SCARDCONTEXT context = NULL;

  while (!winerror) {
    winerror = monitor_smartcard_readers(

when setContext is called, store the context in the local stack variable.

    [&](SCARDCONTEXT context) {
      context = context;
    },

when clearContext is called, set the local stack variable to NULL

    [&]() {
      context = NULL;
    },

when wait is called, wait for the smart card service to start. If additional threads were in play this might include other events to signal that monitor_smartcard_readers should exit.

    [&]() -> bool {
      if (WAIT_OBJECT_0 != WaitForMultipleObjects(
          lib::rng::size(waitfor), 
          waitfor, 
          FALSE, 
          INFINITE
        )
      ) {
        // monitor_smardcard_readers will 
        // return SCARD_E_CANCELLED
        return false;
      }
      return true;
    },

when report is called, print out the state changes.

    [&](
      lib::rng::range<SCARD_READERSTATE*> readersrange
    ) {
      std::wcout << L"---status update---" << std::endl;
      for (auto & state : readersrange) {
        auto stateChanges = (
          state.dwCurrentState ^ state.dwEventState
        ) & std::numeric_limits<unsigned short>::max();
        
        std::wcout << L"reader: " << state.szReader 
        << L" changes: " << 
           std::hex << std::showbase << stateChanges
        << L" state: "
        << ((state.dwEventState == SCARD_STATE_UNAWARE) ? 
           L" SCARD_STATE_UNAWARE" : L"")
        << ((state.dwEventState & SCARD_STATE_PRESENT) ? 
           L" SCARD_STATE_PRESENT" : L"")
        << ((state.dwEventState & SCARD_STATE_ATRMATCH) ? 
           L" SCARD_STATE_ATRMATCH" : L"")
        << ((state.dwEventState & SCARD_STATE_CHANGED) ? 
           L" SCARD_STATE_CHANGED" : L"")
        << ((state.dwEventState & SCARD_STATE_EMPTY) ? 
           L" SCARD_STATE_EMPTY" : L"")
        << ((state.dwEventState & SCARD_STATE_EXCLUSIVE) ? 
           L" SCARD_STATE_EXCLUSIVE" : L"")
        << ((state.dwEventState & SCARD_STATE_IGNORE) ? 
           L" SCARD_STATE_IGNORE" : L"")
        << ((state.dwEventState & SCARD_STATE_INUSE) ? 
           L" SCARD_STATE_INUSE" : L"")
        << ((state.dwEventState & SCARD_STATE_MUTE) ? 
           L" SCARD_STATE_MUTE" : L"")
        << ((state.dwEventState & SCARD_STATE_UNAVAILABLE) ? 
           L" SCARD_STATE_UNAVAILABLE" : L"")
        << ((state.dwEventState & SCARD_STATE_UNKNOWN) ? 
           L" SCARD_STATE_UNKNOWN" : L"")
        << ((state.dwEventState & SCARD_STATE_UNPOWERED) ? 
           L" SCARD_STATE_UNPOWERED" : L"")
        << std::endl; 
      }
      std::wcout << L"--status update end--" << std::endl;
    }
    );
  }
}

Next post will explore the implementation of monitor_smartcard_readers..

Thursday, December 15, 2011

WINDOW_MESSAGE_DEFINITION

WINDOW_MESSAGE_DEFINITION is used with #include to stamp out code for each window message. One example is when generator is defined.

template<typename WindowClassTag, typename Target>
struct generator {
  typedef
    typename generator_root < WindowClassTag, Target
#   define WINDOW_MESSAGE_DEFINE_BEGIN_GENERATOR
#   include "win32_messages.h"
#   undef WINDOW_MESSAGE_DEFINE_BEGIN_GENERATOR

#   define WINDOW_MESSAGE_DEFINE_END_GENERATOR
#   include "win32_messages.h"
#   undef WINDOW_MESSAGE_DEFINE_END_GENERATOR
    >::type
  type;
};

win32_messages.h contains a #define and #include for each supported window message.

#define WINDOW_MESSAGE_DEFINITION (PAINT,   Paint,   0, ())
#include "win32_message_define.h"

#define WINDOW_MESSAGE_DEFINITION (NCPAINT, NCPaint, 1, (HRGN))
#include "win32_message_define.h"

win32_message_define.h then uses that #define to stamp out code for that message. Here are the salient parts of win32_message_define for the generator definition.

#define WINDOW_MESSAGE_CAPITAL_NAME           TPLT_CALL(TPLT_EXTRACT, TPLT_ARGUMENTS_APPEND_LIST(4, WINDOW_MESSAGE_DEFINITION, 0))
#define WINDOW_MESSAGE_CASED_NAME             TPLT_CALL(TPLT_EXTRACT, TPLT_ARGUMENTS_APPEND_LIST(4, WINDOW_MESSAGE_DEFINITION, 1))
#define WINDOW_MESSAGE_PARAMETER_COUNT        TPLT_CALL(TPLT_EXTRACT, TPLT_ARGUMENTS_APPEND_LIST(4, WINDOW_MESSAGE_DEFINITION, 2))
#define WINDOW_MESSAGE_PARAMETER_LIST         TPLT_CALL(TPLT_EXTRACT, TPLT_ARGUMENTS_APPEND_LIST(4, WINDOW_MESSAGE_DEFINITION, 3))

#if defined(WINDOW_MESSAGE_DEFINE_BEGIN_GENERATOR)

#define WINDOW_MESSAGE_OPTIONAL TPLT_CALL(MAKE_IDENTIFIER_EXPLICIT, (optional, WINDOW_MESSAGE_CASED_NAME))

  , generator<WindowClassTag, Target, decltype(WINDOW_MESSAGE_OPTIONAL(instance_of<Target>::value, instance_of<Context<WindowClassTag>*>::value, 0)) 

#undef WINDOW_MESSAGE_OPTIONAL 

#elif defined(WINDOW_MESSAGE_DEFINE_END_GENERATOR)
  >
#endif

#undef WINDOW_MESSAGE_PARAMETER_LIST
#undef WINDOW_MESSAGE_PARAMETER_COUNT
#undef WINDOW_MESSAGE_CASED_NAME
#undef WINDOW_MESSAGE_CAPITAL_NAME
#undef WINDOW_MESSAGE_DEFINITION

First the arguments are pulled out of the WINDOW_MESSAGE_DEFINITION. Then one of the #if blocks is selected and the parameters are used to fill in the name of a message specific identifier. Now that the macro machinery is exposed, here is the generator definition that is stamped out.

template<typename WindowClassTag, typename Target>
struct generator {
  typedef
    typename generator_root < 
      WindowClassTag, 
      Target
      , generator<
          WindowClassTag, 
          Target, 
          decltype(
            optionalPaint(
              instance_of<Target>::value,
              instance_of<Context<WindowClassTag>*>::value, 
              0)) 

          , generator<
              WindowClassTag, 
              Target, 
              decltype(
                optionalNCPaint(
                  instance_of<Target>::value,
                  instance_of<Context<WindowClassTag>*>::value, 
                  0)) 
         >
       >
    >::type
  type;
};

The 'optional' methods are also generated via the same macros. The only piece missing is another #if block in win32_message_define.h That will be next up..

Thursday, December 8, 2011

window message dispatch generator

One goal for this dispatcher was to achieve pay-for-play performance characteristics. The generator builds a recursive type with a recursive dispatch function that only includes the message handling supported by the target. This produces a pretty concise if-else construct.

namespace detail {
template<typename WindowClassTag, typename Target>
struct generator_end
{
  typedef
    base<WindowClassTag, Target>
  type;
};

generator_end is used to terminate the recursive type being generated. base is defined previously but will be covered later.

template <
  typename WindowClassTag,
  typename Target,
  typename MessageChoice,
  typename Base = generator_end<WindowClassTag, Target >>
struct generator;

This is the prototype for generator, 2 specializations of generator are used to either add or omit a type from the recursive type.

template <
  typename WindowClassTag,
  typename Target,
  template <
    typename A,
    typename B,
    typename C > class MessageChoice,
  typename Base >
struct generator <
  WindowClassTag,
  Target,
  MessageChoice <
    WindowClassTag,
    Target,
    base<WindowClassTag, Target >> ,
  Base >
  : public Base
{
  typedef
    MessageChoice <
      WindowClassTag,
      Target,
      typename Base::type >
  type;
};

this specialization of generator matches when the target does support a message. It creates a type result using the supplied MessageChoice which is chained into the recursive type result (by changing the last template parameter to the result of the next recursed generator type result).

template <
  typename WindowClassTag,
  typename Target,
  typename Base >
struct generator <
  WindowClassTag,
  Target,
  nohandler,
  Base >
  : public Base
{
};

This specialization of generator matches when the target does not support the message (the nohandler class is already defined. nohandler will be described later.) There is no type defined here which ensures that no code will be generated for this message.

template <
  typename WindowClassTag,
  typename Target,
  typename Base >
struct generator_root
  : public Base
{
};
}

generator_root provides a container for the generator that allows the type to be stamped out by some macros.

template<typename WindowClassTag, typename Target>
struct generator {
  typedef
    typename detail::generator_root < WindowClassTag, Target
#   define WINDOW_MESSAGE_DEFINE_BEGIN_GENERATOR
#   include "win32_messages.h"
#   undef WINDOW_MESSAGE_DEFINE_BEGIN_GENERATOR

#   define WINDOW_MESSAGE_DEFINE_END_GENERATOR
#   include "win32_messages.h"
#   undef WINDOW_MESSAGE_DEFINE_END_GENERATOR
    >::type
  type;
};

This is how the type for a set of messages is created. This marco technique is complicated in order to allow the debugger to step through the dispatch code with source lines. WINDOW_MESSAGE_DEFINE_BEGIN_GENERATOR causes win32_messages.h to resolve to a recursive type for each message, while WINDOW_MESSAGE_DEFINE_END_GENERATOR causes win32_messages.h to resolve to a < for each type, this is required to finish the recursive generator definition.

Next is a look at the macro machinery..

Thursday, December 1, 2011

window message dispatcher

Time to look at the window message dispatcher. This is the third major implementation. so far.

Callers of dispatch should call DefWindowProc if this returns result.first == false.

One goal was to eliminate manual maintenance of a message-map from message id to function. Other goals included pay-for-use performance characteristics, code driven instead of data driven design, no virtual function calls and allow source debugging even in the macros (which are unfortunately required until C++ supports 'templated' names).

The current approach inspects the specified target class instance for a function matching each supported windows message. The parameters for the function are determined using the HANDLE_MSG_... macros from windowsx.h in the SDK. So if the target class has a function matching LRESULT OnPaint(const lib::wnd::Context<Tag>& context) then code to forward the WM_PAINT message to the function is included, otherwise the code is omitted.

template<typename WindowClassTag, typename Target>
std::pair<bool, LRESULT>
dispatch(
  Target target,
  const Context<WindowClassTag>& context)
{
  BOOL handled = FALSE;
  LRESULT result = 0;
  std::tie(handled, result) =
    generator<WindowClassTag, Target>::type(
        base<WindowClassTag, Target>(
            target, &context)).dispatch();

  if (!handled) {
    std::tie(handled, result) =
      optionalUnhandled(target, context);
  }

  return std::make_pair(
      handled ? true : false, result);
}

dispatch looks simple, it constructs a type stitched together in the generator detail class and calls the dispatch method on the class instance then if that function specifies that the message was not handled it calls optionalUnhandled. When the guts of the generator are exposed later that facade of simplicity will disintegrate. Until that time enjoy the appearance of simplicity.

struct UnhandledTag {};

The tag is used to increase the flexibility of the window_message_error_contract trait function.

template<typename WindowClassTag, typename Target>
std::pair<bool, LRESULT> optionalUnhandled(
  Target target,
  const Context<WindowClassTag>& context,
  decltype(
    cmn::instance_of<Target>::value->OnUnhandled(
      cmn::instance_of<Context<WindowClassTag>>::value)))
{
  LRESULT result = 0;
  window_message_error_contract(
    [&] {
      result = target->OnUnhandled(context);
    },
    context,
    UnhandledTag(),
    WindowClassTag()
  );
  return std::make_pair(true, result);
}

This optionalUnhandled overload is only included in the overload set if the decltype parameter can resolve the OnUnhandled(context) function on the target.

The window_message_error_contract function that must be provided by the target implementor can have many overloads in multiple namespaces. The two tag parameters allow this function to be provided once for the whole class or individually for each message type. The purpose of the error contract is for targets that enable exceptions to provide an overload that handles the exceptions.

Finally, the OnUnhandled method on target is called.

inline std::pair<bool, LRESULT> optionalUnhandled(...)
{
  return std::make_pair(false, 0);
}

If the first overload is not valid then this one is used to report that the message is still unhandled.

This introduced the optional method construct, this is the mechanism that is used to detect whether the target supports each potential message. In this case it detects wether the target is interested in the unhanded messages. Providing this function on the target allows the target to chain the message to another handler.

Next up is the generator..

Tuesday, November 29, 2011

WindowCallback

Time for a look at WindowCallback which implements a WNDPROC.

template<typename WindowClassTag>
LRESULT CALLBACK WindowCallback(
  HWND hWnd,
  UINT message,
  WPARAM wParam,
  LPARAM lParam) {
  __try {
    return detail::WindowCallbackSafe<WindowClassTag>(
        hWnd,
        message,
        wParam,
        lParam);
  } __except (FailFastFilter(GetExceptionInformation())) {
  }
  return 0;
}

Not much here, just set up an SEH exception filter that will prevent any exceptions from passing across the boundary from this module to the calling module.

namespace detail {

template<typename WindowClassTag>
LRESULT CALLBACK WindowCallbackSafe(
  HWND hWnd,
  UINT message,
  WPARAM wParam,
  LPARAM lParam) {

  bool handled = false;
  LRESULT result = 0;

  if (message == WM_NCCREATE) {
    auto existingType = optional_window_class_find(
      hWnd,
      WindowClassTag(),
      0);
    if (!existingType) {
      if (!optional_window_class_insert(
        hWnd,
        optional_window_class_construct(
          hWnd,
          reinterpret_cast<LPCREATESTRUCT>(lParam),
          WindowClassTag(),
          0),
        WindowClassTag(),
        0)) {
        return FALSE;
      }
    }
  }

window_class_find, window_class_insert, window_class_construct, window_class_erase and window_class_destroy are used to manage the lifetime of the implementation. The optional_ prefix refers to functions that provide a default behavior when the implementation does not choose to provide overloads of the functions. The defaults use new and delete for allocation and SetWindowLongPtr to store and retrieve the implementation. Each implementation can provide overloads of these functions to control allocation, construction and storage of that implementation.

This code will create and store the implementation in response to WM_NCCREATE. The attempt to find is here to guard against multiple WM_NCCREATE messages.

  auto type = optional_window_class_find(hWnd, WindowClassTag(), 0);

  ON_UNWIND_AUTO(
  [&] {
    if (type && message == WM_NCDESTROY) {
      optional_window_class_erase(hWnd, type, WindowClassTag(), 0);
      optional_window_class_destroy(hWnd, type, WindowClassTag(), 0);
    }
  }
  );

Here the code attempts to retrieve the implementation and setup a lambda function to destroy the implementation when WindowCallback exits. Using ON_UNWIND means that even a C++ exception will run the lambda.

  if (type) {
    Context<WindowClassTag> context = {hWnd, message, wParam, lParam};

    std::tie(handled, result) = msg::dispatch(type, context);
    if (handled) {
      return result;
    }
  }

  return DefWindowProc(hWnd, message, wParam, lParam);
}
}

The rest just calls the dispatch mechanism to forward the message to the correct function on the implementation - if it exists - and calls DefWindowProc otherwise.

template<typename WindowClassTag>
decltype(
  window_class_find(
    cmn::instance_of<HWND>::value,
    WindowClassTag()))
optional_window_class_find(HWND hwnd, WindowClassTag && , int)
{
  return window_class_find(hwnd, WindowClassTag());
}

template<typename WindowClassTag>
typename window_class<WindowClassTag>::traits::type*
optional_window_class_find(HWND hwnd, WindowClassTag && , ...)
{
  typedef
    typename window_class<WindowClassTag>::traits::type
  type;
  return reinterpret_cast<type*>(
    GetWindowLongPtr(hwnd, GWLP_USERDATA));
}

Using optional_window_class_find as an example of the optional_ pattern. The two functions both take the same parameters up until the last one. The last one is used so that when both functions are included in the overload set the compiler can break the tie. The compiler will choose to send 0 literal to an int parameter rather than ... but if the overload set only contains the function with the last parameter ends in ... then the compiler will select it without an error.

In this case the first function takes precedence only if the decltype can find a windows_class_find overload that takes these parameters. If it cannot the second function will be used to provide default functionality.

Next we will look at the magical msg dispatcher..

Thursday, November 24, 2011

window_class::Register

Continuing the look at window_class. here are the Register methods. Each one will initialize defaults for WNDCLASSEX then allow the window implementation to override the defaults by calling window_class_register via ADL. Then it will force the WNDPROC to WindowCallback and call Register.

template<typename WindowClassTag>
//static
ATOM window_class<WindowClassTag>::Register()
{
  WNDCLASSEX wcex = {};
  wcex.cbSize = sizeof(WNDCLASSEX);

  // defaults that can be overriden
  wcex.style = CS_HREDRAW | CS_VREDRAW;
  wcex.hInstance = GetCurrentInstance();
  wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

  window_class_register(&wcex, tag());

  // not overridable
  wcex.lpfnWndProc = WindowCallback<WindowClassTag>;

  return RegisterClassEx(&wcex);
}

The default overload does the bare minimum

template<typename WindowClassTag>
template<typename T>
//static
ATOM window_class<WindowClassTag>::Register(T && t)
{
  WNDCLASSEX wcex = {};
  wcex.cbSize = sizeof(WNDCLASSEX);

  // defaults that can be overriden
  wcex.style = CS_HREDRAW | CS_VREDRAW;
  wcex.hInstance = GetCurrentInstance();
  wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

  window_class_register(std::forward<T>(t), &wcex, tag());

  // not overridable
  wcex.lpfnWndProc = WindowCallback<WindowClassTag>;

  return RegisterClassEx(&wcex);
}

This overload will accept a parameter, any parameter that the implementation desires to require, it could even be a fully filled out WNDCLASSEX that is just copied over the defaults.