Skip to content

Compile the public headers after a consumer's <windows.h> - #217

Open
chrisuthe wants to merge 3 commits into
mainfrom
windows-public-header-macros
Open

chrisuthe wants to merge 3 commits into
mainfrom
windows-public-header-macros

Conversation

@chrisuthe

Copy link
Copy Markdown
Member

A Windows consumer that includes <windows.h> before the sendspin headers, as most Win32 code does, fails to compile them. The library's NOMINMAX / WIN32_LEAN_AND_MEAN / NOGDI from #210 are private, so they never covered the public headers.

Summary

  • LogLevel::ERROR. wingdi.h defines ERROR as 0, which turned the enumerator into 0 = 1. client.h now sets the macro aside around LogLevel with #pragma push_macro("ERROR") / #undef ERROR / #pragma pop_macro("ERROR"), so the consumer's own ERROR is back after it. The pragmas are supported by GCC, Clang and MSVC, so the header needs no #ifdef _WIN32, and they are no-ops where ERROR is not defined.
  • std::numeric_limits<uint16_t>::max() in PlayerRoleConfig::pipeline_lead_time_ms() was expanded by windows.h's function-like max() macro. It is now (std::numeric_limits<uint16_t>::max)(), with a comment so the parentheses are not tidied away.

Notes

  • Consumer code that itself names LogLevel::ERROR after <windows.h> still needs NOGDI or its own #undef ERROR; the comment above LogLevel says so. Renaming the enumerator would remove that, but it would break the API for every consumer, so it is left alone.
  • There is no unit test: on Linux, simulating windows.h's max() macro breaks the standard library headers before it reaches ours. The natural guard is a Windows CI job that compiles one translation unit including <windows.h> first and then every public header; that is planned as a follow-up along with building the library and basic_client on Windows in CI.

Testing

  • Windows 11, clang-cl 22.1.3 (Visual Studio 2026), /W4, every role enabled, one translation unit including every public header:
    • <windows.h> first: failed with 3 errors before this change, compiles cleanly after it.
    • <windows.h> after the sendspin headers, with NOMINMAX + WIN32_LEAN_AND_MEAN, and without <windows.h>: all compile cleanly.
    • static_assert(ERROR == 0) after the headers holds, so pop_macro restores wingdi's macro.
    • With NOGDI, consumer code can name LogLevel::ERROR after <windows.h>; without it, it fails in the consumer's own line, as the header comment says.
  • Windows 11, clang-cl: the sendspin target builds with no warnings from the library's own sources.
  • Linux (WSL), GCC 13, -Wall -Wextra -Wpedantic: both headers compile, a consumer's own ERROR macro survives the include, and pipeline_lead_time_ms(0xFFFF) still saturates at 0xFFFF. The rest of Linux is this PR's CI.
  • clang-format: no changes.

A Windows consumer that includes <windows.h> before the sendspin headers, as most Win32 code
does, failed in two places:

- wingdi.h defines ERROR as 0, which turned the LogLevel::ERROR enumerator into `0 = 1`.
  client.h now sets the macro aside around LogLevel with push_macro/pop_macro, so the
  consumer's ERROR is restored after it. Code that names LogLevel::ERROR after <windows.h>
  still needs NOGDI or its own #undef ERROR.
- windows.h's max() macro expanded std::numeric_limits<uint16_t>::max() in
  PlayerRoleConfig::pipeline_lead_time_ms(). It is now parenthesized.

The library's own NOMINMAX/WIN32_LEAN_AND_MEAN/NOGDI stay private, so they never covered
this.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The macro workarounds lack automated regression coverage, so either could be removed without failing the existing host tests.

1 open issue
What changed in this PR

This PR extends the Windows build support from #210 by making public headers tolerate a consumer including <windows.h> first, without changing the public API or the consumer’s macro settings.

Changes:

  • Temporarily saves and undefines ERROR around LogLevel, then restores it.
  • Protects numeric_limits::max() from macro expansion while preserving lead-time saturation.
File Description
include/​sendspin/​config.h Parenthesizes max to prevent Windows macro expansion.
include/​sendspin/​client.h Shields the enum declaration from ERROR and documents the remaining consumer-side limitation.

Review effort: Balanced

Comment thread include/sendspin/client.h Resolved
A compile-only translation unit includes the standard headers the public headers use, then
defines ERROR, min() and max() as <windows.h> does, then includes client.h and
persistence_codec.h, and asserts ERROR is restored after them. Dropping the parentheses on
numeric_limits<uint16_t>::max() in config.h, the push_macro/#undef in client.h, or its
pop_macro each fails the build. It stands in for a Windows CI job compiling against the real
<windows.h>.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The Windows macro collision test omits the seven public role headers, allowing collisions in those headers to pass CI.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread tests/test_windows_header_macros.cpp Resolved
client.h only forward-declares the roles, so the check never compiled any of the seven public
role headers under ERROR, min() and max(). Include each enabled role's header, guarded by its
role option as a consumer would, and correct the comment that said client.h brought them in.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants