diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index dc9e19cdd..637515ac4 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -64,6 +64,9 @@ else() set_property(TARGET ${TARGET_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_options(${TARGET_NAME} PRIVATE "${LINK_ARG}") target_link_libraries(${TARGET_NAME} ${ESPP_EXTERNAL_LIBS}) + if(WIN32) + target_link_libraries(${TARGET_NAME} winmm) + endif() target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20) # install build output and headers diff --git a/lib/espp.cmake b/lib/espp.cmake index 78da945bf..cf79c412d 100644 --- a/lib/espp.cmake +++ b/lib/espp.cmake @@ -89,9 +89,13 @@ if(MSVC) list(APPEND ESPP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/wcswidth.c) endif() -# if we're on Windows, we need to link against ws2_32 +# On Windows link against ws2_32 (sockets) and winmm (timeBeginPeriod, used by +# the TimerResolution helper in espp.hpp). Centralizing these here keeps linkage +# consistent across the static library, tests, and the _espp module, and works +# on all Windows toolchains (MSVC, MinGW, clang) rather than relying on +# MSVC-only #pragma comment(lib, ...). if(WIN32) - set(ESPP_EXTERNAL_LIBS ws2_32) + set(ESPP_EXTERNAL_LIBS ws2_32 winmm) else() set(ESPP_EXTERNAL_LIBS pthread) endif() diff --git a/lib/espp.cpp b/lib/espp.cpp index a8bc32434..5583ab640 100644 --- a/lib/espp.cpp +++ b/lib/espp.cpp @@ -1,5 +1,13 @@ #include "espp.hpp" -#ifdef _MSC_VER -#pragma comment(lib, "Ws2_32.lib") +// The Windows system libraries this needs (Ws2_32, winmm) are linked via CMake +// (see lib/espp.cmake, which sets ESPP_EXTERNAL_LIBS for WIN32), so no +// #pragma comment(lib, ...) is needed here - keeping the link spec in one place +// keeps it consistent across the static library, the tests, and the _espp +// Python module, and works on all Windows toolchains (not just MSVC). + +#ifdef _WIN32 +// Global instance that raises the multimedia timer resolution to 1 ms for the +// lifetime of the program (see TimerResolution in espp.hpp). +TimerResolution timer_resolution{}; #endif diff --git a/lib/include/espp.hpp b/lib/include/espp.hpp index 37eeff76b..c1b3fcd0a 100644 --- a/lib/include/espp.hpp +++ b/lib/include/espp.hpp @@ -3,6 +3,9 @@ #include "socket_msvc.hpp" #ifdef _MSC_VER +// windows.h is a C++ header and must not be wrapped in extern "C"; only the C +// header (wcswidth) needs it. +#include extern "C" { // NOTE: needed for tabulate #include "wcswidth.h" @@ -64,3 +67,27 @@ extern "C" { #include "state_base.hpp" #include + +// The timer-resolution helper uses the Windows multimedia timer API +// (timeBeginPeriod), which is available on all Windows toolchains (MSVC, MinGW, +// clang), so guard on _WIN32 rather than _MSC_VER. winmm is linked via CMake +// (see lib/espp.cmake / pc/CMakeLists.txt). +#ifdef _WIN32 + +#include +#include + +// we want to ensure that the timer resolution is set to 1ms, otherwise the +// timer will not be accurate. To do this we need to call timeBeginPeriod(1) at +// the start of the program and timeEndPeriod(1) at the end of the program. +class TimerResolution { +public: + TimerResolution() { timeBeginPeriod(1); } + ~TimerResolution() { timeEndPeriod(1); } +}; + +// we create a global instance of the TimerResolution class to ensure that the +// timer resolution is set to 1ms for the duration of the program. +extern TimerResolution timer_resolution; + +#endif diff --git a/pc/CMakeLists.txt b/pc/CMakeLists.txt index 494b59bc2..a53dea5d2 100644 --- a/pc/CMakeLists.txt +++ b/pc/CMakeLists.txt @@ -28,6 +28,13 @@ MACRO(GEN_TESTS curdir) PRIVATE espp_pc PRIVATE ${ESPP_EXTERNAL_LIBS} ) + # /WHOLEARCHIVE is an MSVC/link.exe flag (gate on MSVC, not WIN32, so + # MinGW/clang Windows builds don't receive it). It ensures the whole archive + # is linked in, otherwise the Windows timer-period adjustment code (from + # espp.hpp) is stripped and the timer runs at a max of ~64 Hz. + if(MSVC) + target_link_options(${TEST_NAME} PRIVATE "/WHOLEARCHIVE:espp_pc.lib") + endif() ENDFOREACH() ENDMACRO()