From e4c5e14188091a4bb57c757f8d86e940fe771a20 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Tue, 18 Aug 2026 03:40:37 +0800 Subject: [PATCH] feat: add compat.wil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WIL(Windows Implementation Library)—— Win32 句柄、COM 指针与 HRESULT 的 header-only RAII 封装。来自 SpinningMomo(Sunrisepeak/SpinningMomo#2)的 vcpkg → mcpp 迁移。 不是「带 Windows 后端的可移植库」,而是一个**关于** Win32 的库 —— 没有别的平台段 可声明,所以 `platform_versions_diverge = true` 是事实陈述而非豁免。消费者用 `[target.'cfg(windows)'.dependencies.compat]` 门控,测试成员在非 Windows 上编成 空 `main()`,与 compat.x11 / gui-stack 成员正好互为镜像。 WIL 的行为开关 —— `WIL_ENABLE_EXCEPTIONS`、`RESULT_DIAGNOSTICS_LEVEL`、 `WIL_SUPPRESS_EXCEPTIONS`、`WIL_USE_STL` —— 全都是**消费者**在 include 之前定义的 宏。header-only 包没有可以把这个选择烤进去的产物,预设任何一个都等于替消费者选定 错误模型。 header-only 包有两种「链接测试」看不见的失败:头文件在本工具链下压根编不过 (WIL 里 `__declspec`、SAL 标注与 intrinsic 密集,而本索引的 Windows 腿跑的是 clang 而不是 cl.exe),以及 RAII 类型并没有真的释放它拥有的东西。所以断言观察的是 副作用: - `wil::unique_handle` 析构后用 `GetHandleInformation` 断言句柄**真的关了**; `release()` 之后断言它**没关**;move 之后断言所有权真的转移且中途没关。 - `wil::com_ptr` 对真实 COM 对象(CLSID_ShellLink)做 `query()`, 并用一对裸 AddRef/Release 断言引用计数真的在动。 - `RETURN_IF_FAILED` 断言在第一个失败的 HRESULT 处返回;`THROW_IF_FAILED` 断言 抛出的 `wil::ResultException` 携带原始错误码,且成功路径上**不**抛。 CN 镜像已建:gitcode `mcpp-res/wil`,验证过与 GLOBAL 字节一致。 --- README.md | 2 +- README.zh-CN.md | 2 +- mcpp.toml | 1 + pkgs/c/compat.wil.lua | 60 +++++++++++++++ tests/examples/wil/mcpp.toml | 13 ++++ tests/examples/wil/tests/raii.cpp | 124 ++++++++++++++++++++++++++++++ 6 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 pkgs/c/compat.wil.lua create mode 100644 tests/examples/wil/mcpp.toml create mode 100644 tests/examples/wil/tests/raii.cpp diff --git a/README.md b/README.md index 8a43185..00af042 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Two kinds of packages live here: | Database client + the driver manager it needs, built from source | [`compat.nanodbc`](pkgs/c/compat.nanodbc.lua) (nanodbc 2.14.0, frozen upstream — one TU over the platform ODBC driver manager. Two fixes make the four-year-old source compile and RUN here: a force-included `char_traits` shim for libc++ (the standard's own customization point, guarded on `_LIBCPP_VERSION` so libstdc++/MSVC are untouched; and note `-include` reaches C++ TUs only through `cxxflags`, never `cflags`), and a per-platform answer to the manager itself — windows links the SDK's odbc32, macOS the OS's iODBC, while linux takes `compat.unixodbc` because mcpp's runtime closure rejects a NEEDED `libodbc.so.2` that only the host has. The test asserts the manager's own diagnostics surface through the wrapper — including nanodbc's frozen off-by-one that drops the last SQL-state character) · [`compat.unixodbc`](pkgs/c/compat.unixodbc.lua) (unixODBC 2.3.14, Shape E over A — DM + odbcinst + ini/log/lst + libltdl compiled statically into one `odbc` target, exactly upstream's libodbc.a symbol set, so the consumer carries no `libodbc.so.2` NEEDED at all. The one non-obvious piece is libtool-free ltdl wiring: `-DLTDLOPEN=libltdlc` plus a generated `lt_libltdlc_LTX_preloaded_symbols` table (reconstructed from the libtool object's relocations) registers the dlopen loader. The frozen `config.h` merges ltdl's own configure output into the top-level one — ltdl sources never read the clashing identification macros, and the merge sidesteps a quoted `-DLT_CONFIG_H` that does not survive the pipeline. Verified against the libtool build of the same tarball: identical IM002 error path and identical `lt_dlopen` behaviour) | | C-source compat, an ISA tier turned off through the GENERATED config | [`compat.libwebp`](pkgs/c/compat.libwebp.lua) (117 TUs as five directory globs rather than a transcribed file list, and one real decision. libwebp's SSE4.1 gate is `(__SSE4_1__ || WEBP_MSC_SSE41) && (!HAVE_CONFIG_H || WEBP_HAVE_SSE41)`, and `WEBP_MSC_SSE41` keys off `_MSC_VER` **alone** — which every MSVC-ABI compiler defines, including clang, while only cl.exe lets an intrinsic be used without a target flag. Under clang the SSE4.1 sources fail on `always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3'`. Upstream answers with a PER-FILE `-msse4.1`, which no descriptor field expresses; adding it package-wide would let clang emit SSE4.1 in the BASELINE TUs too, past libwebp's own runtime dispatch — a SIGILL rather than a fallback. So the package uses the other half of upstream's own mechanism: `HAVE_CONFIG_H` plus a generated `src/webp/config.h` naming SSE2 and NEON and not SSE4.1, which turns `dec_sse41.c` and friends into upstream's `WEBP_DSP_INIT_STUB` and removes the matching `VP8DspInitSSE41()` call. `src/demux` and `src/mux` are separate upstream libraries with their own public headers and stay out until something asks for them) | | header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | -| header-only, nothing to gate | [`compat.CLI11`](pkgs/c/compat.CLI11.lua) (a command line parser whose every definition is `CLI11_INLINE`, so the package is `*/include` plus an anchor TU. Upstream's two extras stay out: `src/Precompile.cpp` only means anything when `CLI11_COMPILE` also reaches the CONSUMER's translation units — an interface define, not a sources-only gate — and `src/modules/CLI11.cppm` is a module layer, which is a package shape of its own rather than a feature of the compat package) · [`compat.gtl`](pkgs/c/compat.gtl.lua) (Greg's Template Library — the Swiss-table `flat_hash_map` family plus btrees and a bit_vector. `*/include` exactly, not the tarball root: `tests/` and `examples/` carry headers of their own, and naming `include/` is what upstream's INTERFACE target exposes, so a consumer cannot accidentally resolve into test code) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua) (the reference implementation of the proposed `std::hive`; the whole library is one file at the tarball root, so `*` plus an anchor TU is the entire package. Untagged upstream, so the version is a DATE over a commit archive — the compat.khrplatform precedent) | +| header-only, nothing to gate | [`compat.CLI11`](pkgs/c/compat.CLI11.lua) (a command line parser whose every definition is `CLI11_INLINE`, so the package is `*/include` plus an anchor TU. Upstream's two extras stay out: `src/Precompile.cpp` only means anything when `CLI11_COMPILE` also reaches the CONSUMER's translation units — an interface define, not a sources-only gate — and `src/modules/CLI11.cppm` is a module layer, which is a package shape of its own rather than a feature of the compat package) · [`compat.gtl`](pkgs/c/compat.gtl.lua) (Greg's Template Library — the Swiss-table `flat_hash_map` family plus btrees and a bit_vector. `*/include` exactly, not the tarball root: `tests/` and `examples/` carry headers of their own, and naming `include/` is what upstream's INTERFACE target exposes, so a consumer cannot accidentally resolve into test code) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua) (the reference implementation of the proposed `std::hive`; the whole library is one file at the tarball root, so `*` plus an anchor TU is the entire package. Untagged upstream, so the version is a DATE over a commit archive — the compat.khrplatform precedent) · [`compat.wil`](pkgs/c/compat.wil.lua) (the Windows Implementation Library — RAII over Win32 handles, COM pointers and HRESULT. Windows-ONLY in an unusual sense: not a portable library with a Windows backend, but a library ABOUT Win32, so there are no other platform sections to declare and consumers gate the dependency with `[target.'cfg(windows)'.dependencies]` — compat.x11 and the gui-stack member in the other direction. Nothing is pre-configured: WIL's knobs (`WIL_ENABLE_EXCEPTIONS`, `RESULT_DIAGNOSTICS_LEVEL`, `WIL_USE_STL`) are macros the CONSUMER defines before including, and a header-only package has no compiled artifact for such a choice to be baked into anyway — pre-setting one would pick an error model on its consumers' behalf) | | single-header library + a GENERATED implementation TU | [`compat.nanosvg`](pkgs/c/compat.nanosvg.lua) (two stb-style headers where the implementation hides behind `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION`. Upstream ships no `.c` — its examples define the macros inline — so the package generates one that instantiates BOTH halves once. That is what turns a header drop into something linkable, and it moves the duplicate-symbol hazard from every consumer to a single place: consumers must NOT define those macros again, and the test links `nsvgParse` and `nsvgRasterize` together precisely so a package that instantiated only one half fails here rather than downstream) · [`compat.vulkan-memory-allocator`](pkgs/c/compat.vulkan-memory-allocator.lua) (VMA 3.4.0, same shape but the generated TU also has to make a POLICY choice. VMA defaults to `VMA_STATIC_VULKAN_FUNCTIONS 1`, which references `vkBindBufferMemory2` and seven siblings by name — eight undefined symbols against a headers-only dep. Pulling `compat.vulkan` to satisfy them would force a Vulkan loader on every consumer of a memory allocator and fight anyone dispatching through volk, so the generated TU selects the dynamic path instead and VMA resolves everything through `VmaVulkanFunctions`. Note the implementation is C++ despite the C-shaped API, so the generated file is `.cpp`) | | Runtime loader compat (pure sources, sidestepping upstream codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua) (the Khronos loader: `loader/generated/` is checked in, and the assembly path degrades to plain C through `UNKNOWN_FUNCTIONS_SUPPORTED`, so no CMake/Python/assembler is needed; windows deferred) · [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) | | Whole-source direct build + generated config (only where a platform lacks one) | [`compat.curl`](pkgs/c/compat.curl.lua) (win32 uses upstream's checked-in config, unix generates one) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua) (win/mac use upstream's checked-in config; linux generates one and enables X11 by hand) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua) (91 TUs; the release tarball already ships `ares_build.h` and a Windows config, so only `ares_config.h` is snapshotted per OS) · [`compat.msdfgen`](pkgs/c/compat.msdfgen.lua) (msdfgen 1.13 — the config is not optional here: `core/base.h` opens with `#include `, so without generating it nothing compiles, not even `core/`. Generating it rather than passing `-D` flags is also what makes the library and its consumers agree BY CONSTRUCTION — `base.h` is reached from every public header, so the file is the single place that says which of SVG/PNG/Skia exist. Of the four `ext/` units only `import-font.cpp` is built; the other three each need a library this index does not carry, and their declarations disappear through the same generated config. `MSDFGEN_USE_CPP11` is left off on purpose: it adds move constructors to `Bitmap`, so it changes the layout of a type that crosses the library boundary, and a package cannot guarantee every consumer defines it identically) | diff --git a/README.zh-CN.md b/README.zh-CN.md index ef7b89a..5a369f9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -42,7 +42,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 数据库客户端 + 源码构建的驱动管理器 | [`compat.nanodbc`](pkgs/c/compat.nanodbc.lua)(nanodbc 2.14.0,上游已冻结 —— 单 TU 封装平台 ODBC 驱动管理器。两处修复让这份四年前的源码在此可编译、可运行:一个 force-include 的 `char_traits` 补丁头(标准留给用户的定制点,以 `_LIBCPP_VERSION` 为界,不影响 libstdc++/MSVC;注意 `-include` 只能经 `cxxflags` 到达 C++ TU,`cflags` 够不着),以及对驱动管理器本身的分平台答案 —— windows 链 SDK 的 odbc32、macOS 链系统自带的 iODBC,linux 则依赖 `compat.unixodbc`,因为 mcpp 的运行时闭包检查不接受只有宿主才有的 `libodbc.so.2`。测试断言管理器自身的诊断能穿过封装层 —— 包括 nanodbc 已冻结的、会把 SQL state 末字符截掉的 off-by-one)· [`compat.unixodbc`](pkgs/c/compat.unixodbc.lua)(unixODBC 2.3.14,E 叠 A 形态 —— DM + odbcinst + ini/log/lst + libltdl 静态编入单一 `odbc` 目标,与上游 libodbc.a 符号集一致,消费者不带任何 `libodbc.so.2` NEEDED。唯一非常规之处是无 libtool 的 ltdl 接线:`-DLTDLOPEN=libltdlc` 加一张生成的 `lt_libltdlc_LTX_preloaded_symbols` 表(从 libtool 目标文件的重定位记录还原)注册 dlopen loader。冻结的 `config.h` 把 ltdl 自己的 configure 输出合并进顶层(冲突宏 ltdl 源码并不读),绕开了无法在管道中幸存的带引号 `-DLT_CONFIG_H`。已与同 tarball 的 libtool 构建对比验证:IM002 错误路径与 `lt_dlopen` 行为完全一致) | | C 源码 compat(用**生成的 config** 关掉一档 ISA) | [`compat.libwebp`](pkgs/c/compat.libwebp.lua)(117 个 TU 用五条目录通配写完,外加一个真实的取舍。libwebp 的 SSE4.1 门是 `(__SSE4_1__ || WEBP_MSC_SSE41) && (!HAVE_CONFIG_H || WEBP_HAVE_SSE41)`,而 `WEBP_MSC_SSE41` **只**看 `_MSC_VER` —— 每个 MSVC ABI 编译器都定义它,clang 也不例外,但只有 cl.exe 允许不带 target flag 使用任意 intrinsic。于是 clang 下 SSE4.1 那批源码报 `always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3'`。上游用 **per-file** `-msse4.1` 解决,而描述符没有这个字段;整包加上去,clang 就会在**基线** TU 里也发 SSE4.1,绕过 libwebp 自己的运行期分发 —— 那是 SIGILL 而不是回退。所以本包用上游同一套机制的另一半:`HAVE_CONFIG_H` + 生成的 `src/webp/config.h`,只声明 SSE2 与 NEON、不声明 SSE4.1,`dec_sse41.c` 等随之变成上游的 `WEBP_DSP_INIT_STUB`,对应的 `VP8DspInitSSE41()` 调用点也一并消失。`src/demux` / `src/mux` 是上游各自独立、各带公开头文件的库,在有人需要之前不收) | | header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | -| header-only(无可门控组件) | [`compat.CLI11`](pkgs/c/compat.CLI11.lua)(命令行解析器,全部定义都是 `CLI11_INLINE`,故整包就是 `*/include` 加一个 anchor TU。上游两个额外件都不收:`src/Precompile.cpp` 只有在 `CLI11_COMPILE` 同时到达**消费者** TU 时才有意义 —— 那是 interface define,不是 sources 门控;`src/modules/CLI11.cppm` 属于模块层,是另一种包形态,而非 compat 包的 feature) · [`compat.gtl`](pkgs/c/compat.gtl.lua)(Greg's Template Library —— Swiss-table 的 `flat_hash_map` 家族,外加 btree 与 bit_vector。只取 `*/include` 而非 tarball 根:`tests/` 与 `examples/` 各自带头文件,而 `include/` 正是上游 INTERFACE target 暴露的范围,消费者不会误解析到测试代码) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua)(提案中 `std::hive` 的参考实现;整库就是 tarball 根下一个文件,故全包即 `*` 加一个 anchor TU。上游不打 tag,于是版本用 commit 归档上的日期 —— 沿用 compat.khrplatform 的先例) | +| header-only(无可门控组件) | [`compat.CLI11`](pkgs/c/compat.CLI11.lua)(命令行解析器,全部定义都是 `CLI11_INLINE`,故整包就是 `*/include` 加一个 anchor TU。上游两个额外件都不收:`src/Precompile.cpp` 只有在 `CLI11_COMPILE` 同时到达**消费者** TU 时才有意义 —— 那是 interface define,不是 sources 门控;`src/modules/CLI11.cppm` 属于模块层,是另一种包形态,而非 compat 包的 feature) · [`compat.gtl`](pkgs/c/compat.gtl.lua)(Greg's Template Library —— Swiss-table 的 `flat_hash_map` 家族,外加 btree 与 bit_vector。只取 `*/include` 而非 tarball 根:`tests/` 与 `examples/` 各自带头文件,而 `include/` 正是上游 INTERFACE target 暴露的范围,消费者不会误解析到测试代码) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua)(提案中 `std::hive` 的参考实现;整库就是 tarball 根下一个文件,故全包即 `*` 加一个 anchor TU。上游不打 tag,于是版本用 commit 归档上的日期 —— 沿用 compat.khrplatform 的先例) · [`compat.wil`](pkgs/c/compat.wil.lua)(Windows Implementation Library —— Win32 句柄、COM 指针与 HRESULT 的 RAII 封装。它的「仅 Windows」性质与众不同:不是带 Windows 后端的可移植库,而是一个**关于** Win32 的库,因此没有别的平台段可声明,消费者用 `[target.'cfg(windows)'.dependencies]` 门控 —— 与 compat.x11 及 gui-stack 成员正好互为镜像。什么都不预设:WIL 的开关(`WIL_ENABLE_EXCEPTIONS`、`RESULT_DIAGNOSTICS_LEVEL`、`WIL_USE_STL`)都是**消费者**在 include 之前定义的宏,而 header-only 包根本没有可以把这个选择烤进去的产物 ——预设任何一个都等于替消费者选定了错误模型) | | 单头库 + **生成**实现 TU | [`compat.nanosvg`](pkgs/c/compat.nanosvg.lua)(两个 stb 风格头文件,实现藏在 `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION` 之后。上游不提供 `.c` —— 其示例是就地 define 宏 —— 故本包生成一个,把两半各实例化一次。这才让「一堆头文件」变成可链接的包,并把重复符号的风险从每个消费者收敛到唯一一处:消费者不得再次定义这两个宏。测试刻意同时链接 `nsvgParse` 与 `nsvgRasterize`,使「只实例化了一半」的包在此处就失败,而不是流到下游)· [`compat.vulkan-memory-allocator`](pkgs/c/compat.vulkan-memory-allocator.lua)(VMA 3.4.0,同一形态,但生成的 TU 还得做一个**策略**选择。VMA 默认 `VMA_STATIC_VULKAN_FUNCTIONS 1`,会按名字引用 `vkBindBufferMemory2` 等八个符号 —— 对「只依赖头文件」而言就是八个未定义引用。为此拉入 `compat.vulkan` 是错的:那会逼所有用内存分配器的消费者都链上 Vulkan loader,并与通过 volk 自行分发的项目冲突。故生成的 TU 改走动态路径,VMA 一律经 `VmaVulkanFunctions` 解析。注意其 API 形如 C 但实现是 C++,故生成文件为 `.cpp`)| | 运行时 loader compat(纯源码,绕开上游 codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua)(Khronos loader:`loader/generated/` 已签入,汇编路径经 `UNKNOWN_FUNCTIONS_SUPPORTED` 降级为纯 C,故无需 CMake/Python/汇编器;windows 延后)· [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) | | 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua)(91 TU;release tarball 已自带 `ares_build.h` 与 Windows 配置,故只需按 OS 冻结 `ares_config.h`) · [`compat.msdfgen`](pkgs/c/compat.msdfgen.lua)(msdfgen 1.13 —— 这里的 config 不是可选项:`core/base.h` 开头就是 `#include `,不生成它连 `core/` 都编不了。选择生成它而非传 `-D`,还使库与消费者**天然一致** —— `base.h` 被每个公开头间接包含,于是该文件成为「SVG/PNG/Skia 哪些存在」的唯一出处。`ext/` 四个单元只编 `import-font.cpp`,其余三个各需一个本索引没有的库,其声明经由同一份生成 config 一并消失。`MSDFGEN_USE_CPP11` 刻意不开:它给 `Bitmap` 增加移动构造,即改变了跨库边界类型的布局,而包无法保证每个消费者都同样定义它) | diff --git a/mcpp.toml b/mcpp.toml index e04e737..5ca32be 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -82,6 +82,7 @@ members = [ "tests/examples/vulkan", "tests/examples/websocket", "tests/examples/uwebsockets", + "tests/examples/wil", "tests/examples/websocket-features", "tests/examples/tray", "tests/examples/xxhash", diff --git a/pkgs/c/compat.wil.lua b/pkgs/c/compat.wil.lua new file mode 100644 index 0000000..91597cb --- /dev/null +++ b/pkgs/c/compat.wil.lua @@ -0,0 +1,60 @@ +-- compat.wil — the Windows Implementation Library: header-only RAII wrappers +-- over Win32 handles, COM pointers and HRESULT-based error handling. +-- A consumer writes `#include `, ``, ``. +-- +-- Shape B (header-only + anchor TU): `*/include` is the whole library, and a +-- trivial anchor gives mcpp a buildable lib target — the compat.eigen / +-- compat.opengl shape. +-- +-- WINDOWS-ONLY, and unusually so: this is not a portable library with a Windows +-- backend, it is a library ABOUT Win32. There is no `linux`/`macosx` section to +-- declare, so `check_platform_version_parity` is satisfied by there being only +-- one platform section to compare. Consumers gate the dependency with +-- `[target.'cfg(windows)'.dependencies.compat]`, which is what the test member +-- here does — the same shape compat.x11 and friends use in the other direction. +-- +-- NOTHING IS CONFIGURED, on purpose. WIL's behaviour knobs are all macros the +-- CONSUMER defines before including a header — WIL_ENABLE_EXCEPTIONS (on by +-- default when /EHsc is in effect), RESULT_DIAGNOSTICS_LEVEL, +-- WIL_SUPPRESS_EXCEPTIONS, WIL_USE_STL. A package that pre-set any of them would +-- be choosing an error model on its consumers' behalf, and since the library is +-- header-only there is no compiled artifact for the choice to be baked into +-- anyway. +package = { + spec = "1", + namespace = "compat", + name = "wil", + description = "Windows Implementation Library: header-only RAII for Win32 handles, COM and HRESULT", + licenses = {"MIT"}, + repo = "https://github.com/microsoft/wil", + type = "package", + + xpm = { + windows = { + ["1.0.260126.7"] = { + url = { + GLOBAL = "https://github.com/microsoft/wil/archive/refs/tags/v1.0.260126.7.tar.gz", + CN = "https://gitcode.com/mcpp-res/wil/releases/download/1.0.260126.7/wil-1.0.260126.7.tar.gz", + }, + sha256 = "de9e03b38ff0ff8d22048f00b111cb631d21c550328f12530ccba71c05c9e361", + }, + }, + }, + + -- Windows is the only platform this library exists for; the other two + -- sections are absent rather than empty. + platform_versions_diverge = true, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*/include" }, + generated_files = { + ["mcpp_generated/wil_anchor.c"] = "int mcpp_compat_wil_anchor(void) { return 0; }\n", + }, + sources = { "mcpp_generated/wil_anchor.c" }, + targets = { ["wil"] = { kind = "lib" } }, + deps = { }, + }, +} diff --git a/tests/examples/wil/mcpp.toml b/tests/examples/wil/mcpp.toml new file mode 100644 index 0000000..24ae385 --- /dev/null +++ b/tests/examples/wil/mcpp.toml @@ -0,0 +1,13 @@ +# WIL test project. The library is Windows-only — not a portable library with a +# Windows backend, but a library ABOUT Win32 — so the dependency is gated and +# the test compiles to a no-op main() elsewhere, the same shape gui-stack uses +# in the other direction. +[package] +name = "wil-tests" +version = "0.1.0" + +[target.'cfg(windows)'.dependencies.compat] +wil = "1.0.260126.7" + +[target.'cfg(windows)'.build] +ldflags = ["-lole32", "-loleaut32"] diff --git a/tests/examples/wil/tests/raii.cpp b/tests/examples/wil/tests/raii.cpp new file mode 100644 index 0000000..ebdf5b4 --- /dev/null +++ b/tests/examples/wil/tests/raii.cpp @@ -0,0 +1,124 @@ +// Behavioral test: WIL's three families, each doing real work. +// +// A header-only package can fail in two ways a "does it link" test would miss: +// the headers might not compile under this toolchain at all (WIL is dense in +// __declspec, SAL annotations and intrinsics, and this index builds Windows +// with clang rather than cl.exe), and the RAII types might not actually release +// what they own. So the assertions observe SIDE EFFECTS — a handle that really +// closed, a COM refcount that really dropped — rather than just naming symbols. +#ifdef _WIN32 + +#include +#include +#include + +#include +#include + +#include +#include + +namespace { + +// Returns whether the handle value still names a live kernel object in this +// process. This is how "the wrapper really closed it" gets observed. +bool handle_is_live(HANDLE h) { + DWORD flags = 0; + return ::GetHandleInformation(h, &flags) != FALSE; +} + +HRESULT succeeds() { return S_OK; } +HRESULT fails() { return E_ACCESSDENIED; } + +HRESULT propagates_failure() { + RETURN_IF_FAILED(succeeds()); + RETURN_IF_FAILED(fails()); // must return here + return S_FALSE; // unreachable +} + +} // namespace + +int main() { + // ── wil::unique_handle: ownership, move, and actual closure ────────── + HANDLE raw = ::CreateEventW(nullptr, TRUE, FALSE, nullptr); + assert(raw != nullptr && handle_is_live(raw)); + { + wil::unique_handle owner(raw); + assert(owner.get() == raw); + + wil::unique_handle moved = std::move(owner); + assert(!owner && moved.get() == raw); // move really transferred + assert(handle_is_live(raw)); // and did not close on the way + } + assert(!handle_is_live(raw) && "unique_handle did not close the handle"); + + // release() must hand ownership BACK, i.e. not close it + HANDLE raw2 = ::CreateEventW(nullptr, TRUE, FALSE, nullptr); + assert(raw2 != nullptr); + { + wil::unique_handle owner(raw2); + HANDLE released = owner.release(); + assert(released == raw2 && !owner); + } + assert(handle_is_live(raw2) && "release() must not close"); + ::CloseHandle(raw2); + + // ── wil::unique_hlocal_string over a LocalAlloc'd buffer ───────────── + { + auto* buffer = static_cast(::LocalAlloc(LPTR, 8 * sizeof(wchar_t))); + assert(buffer != nullptr); + wcscpy_s(buffer, 8, L"wil"); + wil::unique_hlocal_string owned(buffer); + assert(owned && wcscmp(owned.get(), L"wil") == 0); + } // LocalFree runs here; nothing observable, but it must compile and not crash + + // ── wil::com_ptr against a real COM object ─────────────────────────── + { + const auto uninit = wil::CoInitializeEx(COINIT_APARTMENTTHREADED); + + wil::com_ptr link; + const HRESULT hr = ::CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&link)); + assert(SUCCEEDED(hr) && link); + + // query() crosses an interface — the classic com_ptr operation. + auto persist = link.query(); + assert(persist); + + // A raw AddRef/Release pair around a copy proves the smart pointer is + // actually counting, not just holding. + link->AddRef(); + const ULONG after_addref = link->AddRef(); + const ULONG after_release = link->Release(); + assert(after_release == after_addref - 1); + link->Release(); + + wil::com_ptr copy = link; + assert(copy.get() == link.get()); + copy.reset(); + assert(!copy && link); + } + + // ── HRESULT macros: the error model, not just the spelling ─────────── + assert(propagates_failure() == E_ACCESSDENIED); + + bool threw = false; + try { + THROW_IF_FAILED(fails()); + } catch (const wil::ResultException& e) { + threw = true; + assert(e.GetErrorCode() == E_ACCESSDENIED); + } + assert(threw && "THROW_IF_FAILED did not throw on a failed HRESULT"); + + // …and must NOT throw on success. + THROW_IF_FAILED(succeeds()); + + return 0; +} + +#else + +int main() { return 0; } // WIL is Windows-only; nothing to assert here. + +#endif