From 5f7d2f5c6b98523b6373e9dbeb316a1a6f1324b4 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Wed, 22 Apr 2026 22:38:36 -0700 Subject: [PATCH 01/29] Initial implementation of vs modules based on cppwinrtplus fork --- cppwinrt/code_writers.h | 32 +- cppwinrt/file_writers.h | 398 ++++++++++++++++++- cppwinrt/main.cpp | 157 +++++++- cppwinrt/settings.h | 2 + strings/base_abi.h | 2 +- strings/base_activation.h | 6 +- strings/base_agile_ref.h | 2 +- strings/base_array.h | 2 +- strings/base_collections.h | 2 +- strings/base_collections_base.h | 2 +- strings/base_collections_input_iterable.h | 2 +- strings/base_collections_input_map.h | 2 +- strings/base_collections_input_map_view.h | 2 +- strings/base_collections_input_vector.h | 2 +- strings/base_collections_input_vector_view.h | 2 +- strings/base_collections_map.h | 2 +- strings/base_collections_vector.h | 2 +- strings/base_com_ptr.h | 4 +- strings/base_composable.h | 2 +- strings/base_coroutine_foundation.h | 4 +- strings/base_coroutine_threadpool.h | 4 +- strings/base_delegate.h | 2 +- strings/base_error.h | 4 +- strings/base_events.h | 2 +- strings/base_fast_forward.h | 2 +- strings/base_foundation.h | 2 +- strings/base_identity.h | 2 +- strings/base_implements.h | 6 +- strings/base_iterator.h | 2 +- strings/base_macros.h | 4 +- strings/base_marshaler.h | 2 +- strings/base_meta.h | 2 +- strings/base_natvis.h | 2 +- strings/base_reference_produce.h | 4 +- strings/base_std_hash.h | 2 +- strings/base_string.h | 4 +- strings/base_string_input.h | 2 +- strings/base_string_operators.h | 2 +- strings/base_types.h | 4 +- strings/base_windows.h | 2 +- strings/base_xaml_typename.h | 2 +- 41 files changed, 600 insertions(+), 87 deletions(-) diff --git a/cppwinrt/code_writers.h b/cppwinrt/code_writers.h index fc5081bef..131c157eb 100644 --- a/cppwinrt/code_writers.h +++ b/cppwinrt/code_writers.h @@ -35,6 +35,28 @@ namespace cppwinrt } } + static void write_endif(writer& w) + { + w.write("#endif\n"); + } + + // When modules are enabled, wraps a block of #include directives in + // #ifndef WINRT_MODULE ... #endif so that in module builds (where + // WINRT_MODULE is defined in the global module fragment), textual + // includes are suppressed — dependencies come via import instead. + [[nodiscard]] static finish_with wrap_module_aware_includes_guard(writer& w, bool modules_enabled) + { + if (modules_enabled) + { + w.write("#ifndef WINRT_MODULE\n"); + return { w, write_endif }; + } + else + { + return { w, write_nothing }; + } + } + static void write_version_assert(writer& w) { w.write_root_include("base"); @@ -52,14 +74,6 @@ namespace cppwinrt w.write(format); } - static void write_endif(writer& w) - { - auto format = R"(#endif -)"; - - w.write(format); - } - static void write_close_file_guard(writer& w) { write_endif(w); @@ -166,7 +180,7 @@ namespace cppwinrt [[nodiscard]] static finish_with wrap_impl_namespace(writer& w) { - auto format = R"(namespace winrt::impl + auto format = R"(WINRT_EXPORT namespace winrt::impl { )"; diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index ed9386b4e..6cb5e1778 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -10,7 +10,10 @@ namespace cppwinrt { auto wrap_file_guard = wrap_open_file_guard(w, "BASE"); - w.write(strings::base_includes); + { + auto wrap_includes = wrap_module_aware_includes_guard(w, settings.modules); + w.write(strings::base_includes); + } w.write(strings::base_macros); w.write(strings::base_types); w.write(strings::base_extern); @@ -65,7 +68,15 @@ namespace cppwinrt w.flush_to_file(settings.output_folder + "winrt/fast_forward.h"); } - static void write_namespace_0_h(std::string_view const& ns, cache::namespace_members const& members) + static void collect_writer_deps(writer const& w, std::set& out) + { + for (auto&& [dep_ns, _] : w.depends) + { + out.insert(std::string(dep_ns)); + } + } + + static void write_namespace_0_h(std::string_view const& ns, cache::namespace_members const& members, std::set* out_deps = nullptr) { writer w; w.type_namespace = ns; @@ -118,10 +129,11 @@ namespace cppwinrt w.write_each(depends.second); } + if (out_deps) collect_writer_deps(w, *out_deps); w.save_header('0'); } - static void write_namespace_1_h(std::string_view const& ns, cache::namespace_members const& members) + static void write_namespace_1_h(std::string_view const& ns, cache::namespace_members const& members, std::set* out_deps = nullptr) { writer w; w.type_namespace = ns; @@ -137,16 +149,20 @@ namespace cppwinrt write_preamble(w); write_open_file_guard(w, ns, '1'); - for (auto&& depends : w.depends) { - w.write_depends(depends.first, '0'); - } + auto wrap_includes = wrap_module_aware_includes_guard(w, settings.modules); + for (auto&& depends : w.depends) + { + w.write_depends(depends.first, '0'); + } - w.write_depends(w.type_namespace, '0'); + w.write_depends(w.type_namespace, '0'); + } + if (out_deps) collect_writer_deps(w, *out_deps); w.save_header('1'); } - static void write_namespace_2_h(std::string_view const& ns, cache::namespace_members const& members) + static void write_namespace_2_h(std::string_view const& ns, cache::namespace_members const& members, std::set* out_deps = nullptr) { writer w; w.type_namespace = ns; @@ -167,16 +183,20 @@ namespace cppwinrt char const impl = promote ? '2' : '1'; - for (auto&& depends : w.depends) { - w.write_depends(depends.first, impl); - } + auto wrap_includes = wrap_module_aware_includes_guard(w, settings.modules); + for (auto&& depends : w.depends) + { + w.write_depends(depends.first, impl); + } - w.write_depends(w.type_namespace, '1'); + w.write_depends(w.type_namespace, '1'); + } + if (out_deps) collect_writer_deps(w, *out_deps); w.save_header('2'); } - static void write_namespace_h(cache const& c, std::string_view const& ns, cache::namespace_members const& members) + static void write_namespace_h(cache const& c, std::string_view const& ns, cache::namespace_members const& members, std::set* out_deps = nullptr) { writer w; w.type_namespace = ns; @@ -216,18 +236,30 @@ namespace cppwinrt write_namespace_special(w, ns); write_close_file_guard(w); + if (settings.modules) + { + w.write("#endif\n"); // WINRT_CONSUME_MODULE + } w.swap(); write_preamble(w); write_open_file_guard(w, ns); - write_version_assert(w); - write_parent_depends(w, c, ns); - - for (auto&& depends : w.depends) + if (settings.modules) { - w.write_depends(depends.first, '2'); + w.write("#ifndef WINRT_CONSUME_MODULE\n\n"); } + { + auto wrap_includes = wrap_module_aware_includes_guard(w, settings.modules); + write_version_assert(w); + write_parent_depends(w, c, ns); + + for (auto&& depends : w.depends) + { + w.write_depends(depends.first, '2'); + } - w.write_depends(w.type_namespace, '2'); + w.write_depends(w.type_namespace, '2'); + } + if (out_deps) collect_writer_deps(w, *out_deps); w.save_header(); } @@ -250,11 +282,27 @@ namespace cppwinrt write_preamble(w); write_include_guard(w); + if (settings.modules) + { + w.write("#ifdef WINRT_CONSUME_MODULE\n"); + w.write("#include \"winrt/module.h\"\n"); + for (auto&& depends : w.depends) + { + w.write("import %;\n", depends.first); + } + w.write("#else\n"); + } + for (auto&& depends : w.depends) { w.write_depends(depends.first); } + if (settings.modules) + { + w.write("#endif\n"); + } + auto filename = settings.output_folder + get_generated_component_filename(type) + ".g.h"; path folder = filename; folder.remove_filename(); @@ -319,4 +367,316 @@ namespace cppwinrt write_component_cpp(w, type); w.flush_to_file(path); } + + // --- Per-namespace C++20 module interface unit (.ixx) writers --- + + static void write_module_preamble(writer& w) + { + write_preamble(w); + w.write("module;\n"); + w.write("#define WINRT_MODULE\n"); + auto format = R"(#include +#include +#include +#ifdef _DEBUG +#include +#endif +#include "winrt/module.h" +)"; + w.write(format); + } + + static void write_module_h() + { + writer w; + write_preamble(w); + auto format = R"(#pragma once +#ifndef WINRT_MODULE_H +#define WINRT_MODULE_H + +#ifdef _DEBUG + +#define WINRT_ASSERT _ASSERTE +#define WINRT_VERIFY WINRT_ASSERT +#define WINRT_VERIFY_(result, expression) WINRT_ASSERT(result == expression) + +#else + +#define WINRT_ASSERT(expression) ((void)0) +#define WINRT_VERIFY(expression) (void)(expression) +#define WINRT_VERIFY_(result, expression) (void)(expression) + +#endif + +#define WINRT_IMPL_SHIM(...) (*(abi_t<__VA_ARGS__>**)&static_cast<__VA_ARGS__ const&>(static_cast(*this))) + +#ifdef _MSC_VER +// Note: this is a workaround for a false-positive warning produced by the Visual C++ 15.9 compiler. +#pragma warning(disable : 5046) + +// Note: this is a workaround for a false-positive warning produced by the Visual C++ 16.3 compiler. +#pragma warning(disable : 4268) + +// C++ module warnings by /W4 +#pragma warning(disable : 4499) +#pragma warning(disable : 4630) +#endif + +#ifndef WINRT_EXPORT +#ifdef WINRT_MODULE +#define WINRT_EXPORT export extern "C++" +#else +#define WINRT_EXPORT +#endif +#endif + +// pulls in large, hard-to-control legacy headers. In header builds we keep the +// existing behavior, but in module builds it's provided by the winrt.numerics module. +#if !(defined(WINRT_MODULE) || defined(WINRT_CONSUME_MODULE)) + +#ifdef WINRT_IMPL_NUMERICS +#define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ WINRT_EXPORT namespace winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_END_NAMESPACE_ +#include +#undef _WINDOWS_NUMERICS_NAMESPACE_ +#undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ +#undef _WINDOWS_NUMERICS_END_NAMESPACE_ +#endif + +#endif + +#if defined(_MSC_VER) +#define WINRT_IMPL_NOINLINE __declspec(noinline) +#elif defined(__GNUC__) +#define WINRT_IMPL_NOINLINE __attribute__((noinline)) +#else +#define WINRT_IMPL_NOINLINE +#endif + +#if defined(_MSC_VER) +#define WINRT_IMPL_EMPTY_BASES __declspec(empty_bases) +#else +#define WINRT_IMPL_EMPTY_BASES +#endif + +#if defined(_MSC_VER) +#define WINRT_IMPL_NOVTABLE __declspec(novtable) +#else +#define WINRT_IMPL_NOVTABLE +#endif + +#if defined(__clang__) && defined(__has_attribute) +#if __has_attribute(__lto_visibility_public__) +#define WINRT_IMPL_PUBLIC __attribute__((lto_visibility_public)) +#else +#define WINRT_IMPL_PUBLIC +#endif // __has_attribute(__lto_visibility_public__) +#else +#define WINRT_IMPL_PUBLIC +#endif + +#define WINRT_IMPL_ABI_DECL WINRT_IMPL_NOVTABLE WINRT_IMPL_PUBLIC + +#if defined(__clang__) +#define WINRT_IMPL_HAS_DECLSPEC_UUID __has_declspec_attribute(uuid) +#elif defined(_MSC_VER) +#define WINRT_IMPL_HAS_DECLSPEC_UUID 1 +#else +#define WINRT_IMPL_HAS_DECLSPEC_UUID 0 +#endif + +#if defined(__IUnknown_INTERFACE_DEFINED__) || defined(WINRT_ENABLE_LEGACY_COM) +#define WINRT_IMPL_IUNKNOWN_DEFINED +#else +// Forward declare so we can talk about it. +struct IUnknown; +typedef struct _GUID GUID; +#endif + +#if defined(__cpp_consteval) +#define WINRT_IMPL_CONSTEVAL consteval +#else +#define WINRT_IMPL_CONSTEVAL constexpr +#endif + +#endif +)"; + w.write(format); + w.flush_to_file(settings.output_folder + "winrt/module.h"); + } + + static void write_base_ixx() + { + writer w; + write_module_preamble(w); + auto format = R"( +#ifdef WINRT_ENABLE_LEGACY_COM +#include +#include +#undef GetCurrentTime +#endif + +export module winrt.base; + +import std; +export import winrt.numerics; + +#if __has_include() +#define WINRT_IMPL_NUMERICS +#endif + +#include "winrt/base.h" +)"; + w.write(format); + w.flush_to_file(settings.output_folder + "winrt/winrt.base.ixx"); + } + + static void write_numerics_ixx() + { + writer w; + write_module_preamble(w); + auto format = R"( +export module winrt.numerics; + +#if __has_include() +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 5244) +#endif +#include + +#define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ export extern "C++" namespace winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_END_NAMESPACE_ +#include +#undef _WINDOWS_NUMERICS_NAMESPACE_ +#undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ +#undef _WINDOWS_NUMERICS_END_NAMESPACE_ + +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#endif +)"; + w.write(format); + w.flush_to_file(settings.output_folder + "winrt/winrt.numerics.ixx"); + } + + static void write_namespace_ixx( + std::string_view const& ns, + std::set const& deps, + std::set const& projected_namespaces) + { + writer w; + write_module_preamble(w); + + // Module declaration + w.write("export module %;\n\n", ns); + + // Import std and base + w.write("import std;\n"); + w.write("export import winrt.base;\n"); + + // Import dependency namespace modules + for (auto& dep : deps) + { + if (projected_namespaces.count(dep)) + { + w.write("import %;\n", dep); + } + } + + w.write("\n"); + + // Include namespace headers in module purview + w.write("#include \"winrt/impl/%.0.h\"\n", ns); + w.write("#include \"winrt/impl/%.1.h\"\n", ns); + w.write("#include \"winrt/impl/%.2.h\"\n", ns); + w.write("#include \"winrt/%.h\"\n", ns); + + w.flush_to_file(settings.output_folder + "winrt/" + std::string(ns) + ".ixx"); + } + + static void write_namespace_scc_owner_ixx( + cache const& c, + std::string_view const& owner, + std::vector const& scc_members, + std::set const& external_deps, + std::set const& projected_namespaces) + { + writer w; + write_module_preamble(w); + + // Module declaration (owner namespace) + w.write("export module %;\n\n", owner); + + // Import std and base + w.write("import std;\n"); + w.write("export import winrt.base;\n"); + + // Import external dependency modules (outside the SCC) + for (auto& dep : external_deps) + { + if (projected_namespaces.count(dep)) + { + w.write("import %;\n", dep); + } + } + + w.write("\n"); + + // Forward declarations for all SCC members (exported at module purview scope) + for (auto& ns : scc_members) + { + auto found = c.namespaces().find(ns); + if (found == c.namespaces().end()) + { + continue; + } + auto& members = found->second; + + auto wrap_type = wrap_type_namespace(w, ns); + w.write_each(members.enums); + w.write_each(members.interfaces); + w.write_each(members.classes); + w.write_each(members.structs); + w.write_each(members.delegates); + w.write_each(members.contracts); + } + + // Include all SCC members' headers in phase order + for (auto& ns : scc_members) + { + w.write("#include \"winrt/impl/%.0.h\"\n", ns); + } + for (auto& ns : scc_members) + { + w.write("#include \"winrt/impl/%.1.h\"\n", ns); + } + for (auto& ns : scc_members) + { + w.write("#include \"winrt/impl/%.2.h\"\n", ns); + } + for (auto& ns : scc_members) + { + w.write("#include \"winrt/%.h\"\n", ns); + } + + w.flush_to_file(settings.output_folder + "winrt/" + std::string(owner) + ".ixx"); + } + + static void write_namespace_reexport_ixx( + std::string_view const& ns, + std::string_view const& owner) + { + writer w; + write_preamble(w); + w.write("\n// NOTE: This module does not define declarations of its own.\n"); + w.write("// It re-exports all declarations from the '%' module. This is used to break cycles in the\n", owner); + w.write("// WinRT namespace module dependency graph (SCC owner consolidation).\n\n"); + w.write("export module %;\n", ns); + w.write("export import %;\n", owner); + w.flush_to_file(settings.output_folder + "winrt/" + std::string(ns) + ".ixx"); + } } diff --git a/cppwinrt/main.cpp b/cppwinrt/main.cpp index 70a55b076..05ee3119d 100644 --- a/cppwinrt/main.cpp +++ b/cppwinrt/main.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include +#include #include "strings.h" #include "settings.h" #include "type_writers.h" @@ -39,6 +40,7 @@ namespace cppwinrt { "fastabi", 0, 0 }, // Enable support for the Fast ABI { "ignore_velocity", 0, 0 }, // Ignore feature staging metadata and always include implementations { "synchronous", 0, 0 }, // Instructs cppwinrt to run on a single thread to avoid file system issues in batch builds + { "modules", 0, 0, {}, "Generate per-namespace C++20 module interface units (.ixx)" }, }; static void print_usage(writer& w) @@ -85,6 +87,7 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder { settings.verbose = args.exists("verbose"); settings.fastabi = args.exists("fastabi"); + settings.modules = args.exists("modules"); settings.input = args.files("input", database::is_database); settings.reference = args.files("reference", database::is_database); @@ -281,6 +284,79 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder c.remove_type("Windows.Foundation.Numerics", "Vector4"); } + // Tarjan's algorithm for finding strongly connected components in the + // namespace dependency graph. Namespaces in an SCC have cyclic deps and + // must be combined into a single module. + static std::vector> find_sccs( + std::map, std::less<>> const& graph) + { + struct context + { + std::map index_of; + std::map lowlink; + std::map on_stack; + std::vector stack; + int next_index = 0; + std::vector> result; + + void strongconnect(std::string const& v, + std::map, std::less<>> const& g) + { + index_of[v] = next_index; + lowlink[v] = next_index; + next_index++; + stack.push_back(v); + on_stack[v] = true; + + auto it = g.find(v); + if (it != g.end()) + { + for (auto& w : it->second) + { + if (g.find(w) == g.end()) + { + continue; // dep not in graph (not a projected namespace) + } + + if (index_of.find(w) == index_of.end()) + { + strongconnect(w, g); + lowlink[v] = (std::min)(lowlink[v], lowlink[w]); + } + else if (on_stack[w]) + { + lowlink[v] = (std::min)(lowlink[v], index_of[w]); + } + } + } + + if (lowlink[v] == index_of[v]) + { + std::vector scc; + std::string w; + do + { + w = stack.back(); + stack.pop_back(); + on_stack[w] = false; + scc.push_back(std::move(w)); + } while (scc.back() != v); + result.push_back(std::move(scc)); + } + } + }; + + context ctx; + for (auto& [node, _] : graph) + { + if (ctx.index_of.find(node) == ctx.index_of.end()) + { + ctx.strongconnect(node, graph); + } + } + return std::move(ctx.result); + } + static int run(int const argc, char** argv) { int result{}; @@ -342,11 +418,11 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder w.flush_to_console(); task_group group; group.synchronous(args.exists("synchronous")); - writer ixx; - write_preamble(ixx); - ixx.write("module;\n"); - ixx.write(strings::base_includes); - ixx.write("\nexport module winrt;\n#define WINRT_EXPORT export\n\n"); + + // Dependency collection for per-namespace modules (v2) + std::map, std::less<>> ns_deps_map; + std::set projected_namespaces; + std::mutex ns_deps_mutex; for (auto&&[ns, members] : c.namespaces()) { @@ -355,21 +431,32 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder continue; } - ixx.write("#include \"winrt/%.h\"\n", ns); + if (settings.modules) + { + projected_namespaces.insert(std::string(ns)); + } group.add([&, &ns = ns, &members = members] { - write_namespace_0_h(ns, members); - write_namespace_1_h(ns, members); - write_namespace_2_h(ns, members); - write_namespace_h(c, ns, members); + std::set ns_deps; + auto* deps_ptr = settings.modules ? &ns_deps : nullptr; + + write_namespace_0_h(ns, members, deps_ptr); + write_namespace_1_h(ns, members, deps_ptr); + write_namespace_2_h(ns, members, deps_ptr); + write_namespace_h(c, ns, members, deps_ptr); + + if (settings.modules) + { + std::lock_guard lock(ns_deps_mutex); + ns_deps_map[std::string(ns)] = std::move(ns_deps); + } }); } if (settings.base) { write_base_h(); - ixx.flush_to_file(settings.output_folder + "winrt/winrt.ixx"); } if (settings.component) @@ -404,6 +491,54 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder group.get(); + // Generate per-namespace module interface files (v2) + if (settings.modules && settings.base) + { + write_module_h(); + write_numerics_ixx(); + write_base_ixx(); + + // Tarjan's SCC algorithm for cyclic namespace dependencies + auto sccs = find_sccs(ns_deps_map); + + for (auto& scc : sccs) + { + if (scc.size() == 1) + { + // Standalone namespace module + auto& ns = scc[0]; + write_namespace_ixx(ns, ns_deps_map[ns], projected_namespaces); + } + else + { + // SCC: choose owner (alphabetically first), others re-export + std::sort(scc.begin(), scc.end()); + auto& owner = scc[0]; + + // External deps = union of all SCC members' deps, minus SCC members themselves + std::set external_deps; + std::set scc_set(scc.begin(), scc.end()); + for (auto& ns : scc) + { + for (auto& dep : ns_deps_map[ns]) + { + if (!scc_set.count(dep)) + { + external_deps.insert(dep); + } + } + } + + write_namespace_scc_owner_ixx(c, owner, scc, external_deps, projected_namespaces); + + for (size_t i = 1; i < scc.size(); ++i) + { + write_namespace_reexport_ixx(scc[i], owner); + } + } + } + } + if (settings.verbose) { w.write(" time: %ms\n", get_elapsed_time(start)); diff --git a/cppwinrt/settings.h b/cppwinrt/settings.h index e07df4ea2..e3a686367 100644 --- a/cppwinrt/settings.h +++ b/cppwinrt/settings.h @@ -31,6 +31,8 @@ namespace cppwinrt bool fastabi{}; std::map fastabi_cache; + + bool modules{}; // Generate per-namespace C++20 module interface units (.ixx) }; extern settings_type settings; diff --git a/strings/base_abi.h b/strings/base_abi.h index 72946a3fa..4b7b8f77f 100644 --- a/strings/base_abi.h +++ b/strings/base_abi.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template <> struct abi { diff --git a/strings/base_activation.h b/strings/base_activation.h index 1a195d865..c657c692d 100644 --- a/strings/base_activation.h +++ b/strings/base_activation.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct library_traits { @@ -125,7 +125,7 @@ WINRT_EXPORT namespace winrt #define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM64_BARRIER_ISH)); #endif -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { inline std::int32_t interlocked_read_32(std::int32_t const volatile* target) noexcept { @@ -548,7 +548,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template T fast_activate(Windows::Foundation::IActivationFactory const& factory) diff --git a/strings/base_agile_ref.h b/strings/base_agile_ref.h index 88fbea065..14447706a 100644 --- a/strings/base_agile_ref.h +++ b/strings/base_agile_ref.h @@ -47,7 +47,7 @@ WINRT_EXPORT namespace winrt #endif } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct module_lock_updater; diff --git a/strings/base_array.h b/strings/base_array.h index 48d10c6ce..d29bee883 100644 --- a/strings/base_array.h +++ b/strings/base_array.h @@ -483,7 +483,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct array_size_proxy diff --git a/strings/base_collections.h b/strings/base_collections.h index 4e1af51eb..7d8dc2e77 100644 --- a/strings/base_collections.h +++ b/strings/base_collections.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { namespace wfc = Windows::Foundation::Collections; diff --git a/strings/base_collections_base.h b/strings/base_collections_base.h index 6fe10fa64..d299cc0c8 100644 --- a/strings/base_collections_base.h +++ b/strings/base_collections_base.h @@ -1,4 +1,4 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct nop_lock_guard {}; diff --git a/strings/base_collections_input_iterable.h b/strings/base_collections_input_iterable.h index e75211c30..e9d3af251 100644 --- a/strings/base_collections_input_iterable.h +++ b/strings/base_collections_input_iterable.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct input_iterable : diff --git a/strings/base_collections_input_map.h b/strings/base_collections_input_map.h index b33975fe6..3fe146bf6 100644 --- a/strings/base_collections_input_map.h +++ b/strings/base_collections_input_map.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct map_impl : diff --git a/strings/base_collections_input_map_view.h b/strings/base_collections_input_map_view.h index bfd8d82a9..d79eed61d 100644 --- a/strings/base_collections_input_map_view.h +++ b/strings/base_collections_input_map_view.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct input_map_view : diff --git a/strings/base_collections_input_vector.h b/strings/base_collections_input_vector.h index b5b76de38..a06e73b33 100644 --- a/strings/base_collections_input_vector.h +++ b/strings/base_collections_input_vector.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct vector_impl : diff --git a/strings/base_collections_input_vector_view.h b/strings/base_collections_input_vector_view.h index 3793e239a..30768c18e 100644 --- a/strings/base_collections_input_vector_view.h +++ b/strings/base_collections_input_vector_view.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct input_vector_view : diff --git a/strings/base_collections_map.h b/strings/base_collections_map.h index fa769fb88..b40247dab 100644 --- a/strings/base_collections_map.h +++ b/strings/base_collections_map.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template using multi_threaded_map = map_impl; diff --git a/strings/base_collections_vector.h b/strings/base_collections_vector.h index 3e9c1b254..3388806af 100644 --- a/strings/base_collections_vector.h +++ b/strings/base_collections_vector.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template using multi_threaded_vector = vector_impl; diff --git a/strings/base_com_ptr.h b/strings/base_com_ptr.h index 27496789a..0f02fabeb 100644 --- a/strings/base_com_ptr.h +++ b/strings/base_com_ptr.h @@ -5,7 +5,7 @@ WINRT_EXPORT namespace winrt struct com_ptr; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct capture_decay { @@ -349,7 +349,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template std::int32_t capture_to(void** result, com_ptr const& object, M method, Args&& ...args) diff --git a/strings/base_composable.h b/strings/base_composable.h index e606d1292..5a7712ef7 100644 --- a/strings/base_composable.h +++ b/strings/base_composable.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct composable_factory diff --git a/strings/base_coroutine_foundation.h b/strings/base_coroutine_foundation.h index 4f1c8d0b6..cde752aa3 100644 --- a/strings/base_coroutine_foundation.h +++ b/strings/base_coroutine_foundation.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct async_completed_handler; @@ -312,7 +312,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct cancellation_token diff --git a/strings/base_coroutine_threadpool.h b/strings/base_coroutine_threadpool.h index 6748906ca..3ffcf7002 100644 --- a/strings/base_coroutine_threadpool.h +++ b/strings/base_coroutine_threadpool.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { #ifdef WINRT_IMPL_COROUTINES inline auto submit_threadpool_callback(void(__stdcall* callback)(void*, void* context), void* context) @@ -321,7 +321,7 @@ WINRT_EXPORT namespace winrt }; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct apartment_awaiter { diff --git a/strings/base_delegate.h b/strings/base_delegate.h index 1cfe58710..1fe00ecd1 100644 --- a/strings/base_delegate.h +++ b/strings/base_delegate.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { #if defined(_MSC_VER) #pragma warning(push) diff --git a/strings/base_error.h b/strings/base_error.h index c58635e5d..d42ca516b 100644 --- a/strings/base_error.h +++ b/strings/base_error.h @@ -7,7 +7,7 @@ #define WINRT_IMPL_RETURNADDRESS() nullptr #endif -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct heap_traits { @@ -536,7 +536,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { inline hresult check_hresult_allow_bounds(hresult const result, winrt::impl::slim_source_location const& sourceInformation = winrt::impl::slim_source_location::current()) { diff --git a/strings/base_events.h b/strings/base_events.h index f7e2e6976..c21124e9c 100644 --- a/strings/base_events.h +++ b/strings/base_events.h @@ -130,7 +130,7 @@ WINRT_EXPORT namespace winrt }; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct event_revoker diff --git a/strings/base_fast_forward.h b/strings/base_fast_forward.h index dc89fe6ce..c7cf6c3be 100644 --- a/strings/base_fast_forward.h +++ b/strings/base_fast_forward.h @@ -30,7 +30,7 @@ static_assert(WINRT_FAST_ABI_SIZE >= %); #pragma detect_mismatch("WINRT_FAST_ABI_SIZE", WINRT_IMPL_STRING(WINRT_FAST_ABI_SIZE)) -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { // Thunk definitions are in arch-specific assembly sources % diff --git a/strings/base_foundation.h b/strings/base_foundation.h index ea8881edc..083252753 100644 --- a/strings/base_foundation.h +++ b/strings/base_foundation.h @@ -100,7 +100,7 @@ WINRT_EXPORT namespace winrt::Windows::Foundation } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template <> inline constexpr auto& name_v = L"Windows.Foundation.Point"; template <> inline constexpr auto& name_v = L"Windows.Foundation.Size"; diff --git a/strings/base_identity.h b/strings/base_identity.h index 30830bc5a..7c61c83e2 100644 --- a/strings/base_identity.h +++ b/strings/base_identity.h @@ -17,7 +17,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template constexpr std::array to_array(T const* value, std::index_sequence const) noexcept diff --git a/strings/base_implements.h b/strings/base_implements.h index 7edf32149..0eb8db0bd 100644 --- a/strings/base_implements.h +++ b/strings/base_implements.h @@ -6,7 +6,7 @@ #endif #endif -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct marker { @@ -30,7 +30,7 @@ WINRT_EXPORT namespace winrt struct implements; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template using tuple_cat_t = decltype(std::tuple_cat(std::declval()...)); @@ -267,7 +267,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct interface_list; diff --git a/strings/base_iterator.h b/strings/base_iterator.h index acb0ebd42..46c0c6b63 100644 --- a/strings/base_iterator.h +++ b/strings/base_iterator.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct fast_iterator diff --git a/strings/base_macros.h b/strings/base_macros.h index 3dc01fa2d..0d5851ef4 100644 --- a/strings/base_macros.h +++ b/strings/base_macros.h @@ -31,6 +31,7 @@ #define WINRT_EXPORT #endif +#if !(defined(WINRT_MODULE) || defined(WINRT_CONSUME_MODULE)) #ifdef WINRT_IMPL_NUMERICS #define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics #define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ WINRT_EXPORT namespace winrt::Windows::Foundation::Numerics @@ -40,6 +41,7 @@ #undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ #undef _WINDOWS_NUMERICS_END_NAMESPACE_ #endif +#endif #if defined(_MSC_VER) #define WINRT_IMPL_NOINLINE __declspec(noinline) @@ -140,7 +142,7 @@ typedef struct _GUID GUID; #define WINRT_IMPL_BUILTIN_FUNCTION nullptr #endif -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { // This struct is intended to be highly similar to std::source_location. The key difference is // that function_name is NOT included. Function names do not fold to identical strings and can diff --git a/strings/base_marshaler.h b/strings/base_marshaler.h index 526f4d4c3..9be6959c9 100644 --- a/strings/base_marshaler.h +++ b/strings/base_marshaler.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { inline std::int32_t make_marshaler(unknown_abi* outer, void** result) noexcept { diff --git a/strings/base_meta.h b/strings/base_meta.h index 7dbb4c386..6b28640ef 100644 --- a/strings/base_meta.h +++ b/strings/base_meta.h @@ -48,7 +48,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { using namespace std::literals; diff --git a/strings/base_natvis.h b/strings/base_natvis.h index 60c5f5548..9e78563cb 100644 --- a/strings/base_natvis.h +++ b/strings/base_natvis.h @@ -5,7 +5,7 @@ #ifdef WINRT_NATVIS -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct natvis { diff --git a/strings/base_reference_produce.h b/strings/base_reference_produce.h index 2820aff50..abffb3384 100644 --- a/strings/base_reference_produce.h +++ b/strings/base_reference_produce.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct reference : implements, Windows::Foundation::IReference, Windows::Foundation::IPropertyValue> @@ -420,7 +420,7 @@ WINRT_EXPORT namespace winrt::Windows::Foundation } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template T unbox_value_type(From&& value) diff --git a/strings/base_std_hash.h b/strings/base_std_hash.h index 864c31b13..cdffb13c6 100644 --- a/strings/base_std_hash.h +++ b/strings/base_std_hash.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { inline std::size_t hash_data(void const* ptr, std::size_t const bytes) noexcept { diff --git a/strings/base_string.h b/strings/base_string.h index 92295e81c..6b1fb37b5 100644 --- a/strings/base_string.h +++ b/strings/base_string.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct atomic_ref_count { @@ -442,7 +442,7 @@ template<> struct std::formatter : std::formatter {}; #endif -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template <> struct abi { diff --git a/strings/base_string_input.h b/strings/base_string_input.h index 5ac0221f6..71cd5f3c0 100644 --- a/strings/base_string_input.h +++ b/strings/base_string_input.h @@ -65,7 +65,7 @@ WINRT_EXPORT namespace winrt::param } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template using param_type = std::conditional_t, param::hstring, T>; diff --git a/strings/base_string_operators.h b/strings/base_string_operators.h index 25e2eccce..223769d01 100644 --- a/strings/base_string_operators.h +++ b/strings/base_string_operators.h @@ -94,7 +94,7 @@ WINRT_EXPORT namespace winrt bool operator>=(std::nullptr_t left, hstring const& right) = delete; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { inline hstring concat_hstring(std::wstring_view const& left, std::wstring_view const& right) { diff --git a/strings/base_types.h b/strings/base_types.h index 18529e116..dc2b13632 100644 --- a/strings/base_types.h +++ b/strings/base_types.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { using ptp_io = struct tp_io*; using ptp_timer = struct tp_timer*; @@ -208,7 +208,7 @@ WINRT_EXPORT namespace winrt::Windows::Foundation using DateTime = std::chrono::time_point; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { #ifdef WINRT_IMPL_IUNKNOWN_DEFINED using hresult_type = long; diff --git a/strings/base_windows.h b/strings/base_windows.h index 21c4163e5..dcd164574 100644 --- a/strings/base_windows.h +++ b/strings/base_windows.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { #ifdef WINRT_DIAGNOSTICS diff --git a/strings/base_xaml_typename.h b/strings/base_xaml_typename.h index b7b3a954a..2cc45b0bb 100644 --- a/strings/base_xaml_typename.h +++ b/strings/base_xaml_typename.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct xaml_typename_name From 469c96342501e633cb250557fe317c7920d448c5 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Thu, 23 Apr 2026 09:59:23 -0700 Subject: [PATCH 02/29] Rename WINRT_MODULE -> WINRT_IMPL_BUILD_MODULE; WINRT_CONSUME_MODULE -> WINRT_IMPORT_MODULE --- cppwinrt/code_writers.h | 6 +++--- cppwinrt/file_writers.h | 12 ++++++------ strings/base_macros.h | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cppwinrt/code_writers.h b/cppwinrt/code_writers.h index 131c157eb..6e29d7cad 100644 --- a/cppwinrt/code_writers.h +++ b/cppwinrt/code_writers.h @@ -41,14 +41,14 @@ namespace cppwinrt } // When modules are enabled, wraps a block of #include directives in - // #ifndef WINRT_MODULE ... #endif so that in module builds (where - // WINRT_MODULE is defined in the global module fragment), textual + // #ifndef WINRT_IMPL_BUILD_MODULE ... #endif so that in module builds (where + // WINRT_IMPL_BUILD_MODULE is defined in the global module fragment), textual // includes are suppressed — dependencies come via import instead. [[nodiscard]] static finish_with wrap_module_aware_includes_guard(writer& w, bool modules_enabled) { if (modules_enabled) { - w.write("#ifndef WINRT_MODULE\n"); + w.write("#ifndef WINRT_IMPL_BUILD_MODULE\n"); return { w, write_endif }; } else diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index 6cb5e1778..a3d18fb60 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -238,14 +238,14 @@ namespace cppwinrt write_close_file_guard(w); if (settings.modules) { - w.write("#endif\n"); // WINRT_CONSUME_MODULE + w.write("#endif\n"); // WINRT_IMPORT_MODULE } w.swap(); write_preamble(w); write_open_file_guard(w, ns); if (settings.modules) { - w.write("#ifndef WINRT_CONSUME_MODULE\n\n"); + w.write("#ifndef WINRT_IMPORT_MODULE\n\n"); } { auto wrap_includes = wrap_module_aware_includes_guard(w, settings.modules); @@ -284,7 +284,7 @@ namespace cppwinrt if (settings.modules) { - w.write("#ifdef WINRT_CONSUME_MODULE\n"); + w.write("#ifdef WINRT_IMPORT_MODULE\n"); w.write("#include \"winrt/module.h\"\n"); for (auto&& depends : w.depends) { @@ -374,7 +374,7 @@ namespace cppwinrt { write_preamble(w); w.write("module;\n"); - w.write("#define WINRT_MODULE\n"); + w.write("#define WINRT_IMPL_BUILD_MODULE\n"); auto format = R"(#include #include #include @@ -423,7 +423,7 @@ namespace cppwinrt #endif #ifndef WINRT_EXPORT -#ifdef WINRT_MODULE +#ifdef WINRT_IMPL_BUILD_MODULE #define WINRT_EXPORT export extern "C++" #else #define WINRT_EXPORT @@ -432,7 +432,7 @@ namespace cppwinrt // pulls in large, hard-to-control legacy headers. In header builds we keep the // existing behavior, but in module builds it's provided by the winrt.numerics module. -#if !(defined(WINRT_MODULE) || defined(WINRT_CONSUME_MODULE)) +#if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) #ifdef WINRT_IMPL_NUMERICS #define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics diff --git a/strings/base_macros.h b/strings/base_macros.h index 0d5851ef4..7654a0e1c 100644 --- a/strings/base_macros.h +++ b/strings/base_macros.h @@ -31,7 +31,7 @@ #define WINRT_EXPORT #endif -#if !(defined(WINRT_MODULE) || defined(WINRT_CONSUME_MODULE)) +#if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) #ifdef WINRT_IMPL_NUMERICS #define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics #define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ WINRT_EXPORT namespace winrt::Windows::Foundation::Numerics From 84da05ad4ab86677c6d43dc34036a5b809ca2789 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Thu, 23 Apr 2026 10:13:16 -0700 Subject: [PATCH 03/29] Namespace modules are winrt.Namespace. Non-namespace modules are winrt_base/winrt_numerics Co-authored-by: Copilot --- cppwinrt/file_writers.h | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index a3d18fb60..e22c80636 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -288,7 +288,7 @@ namespace cppwinrt w.write("#include \"winrt/module.h\"\n"); for (auto&& depends : w.depends) { - w.write("import %;\n", depends.first); + w.write("import winrt.%;\n", depends.first); } w.write("#else\n"); } @@ -431,7 +431,7 @@ namespace cppwinrt #endif // pulls in large, hard-to-control legacy headers. In header builds we keep the -// existing behavior, but in module builds it's provided by the winrt.numerics module. +// existing behavior, but in module builds it's provided by the winrt_numerics module. #if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) #ifdef WINRT_IMPL_NUMERICS @@ -517,10 +517,10 @@ typedef struct _GUID GUID; #undef GetCurrentTime #endif -export module winrt.base; +export module winrt_base; import std; -export import winrt.numerics; +export import winrt_numerics; #if __has_include() #define WINRT_IMPL_NUMERICS @@ -529,7 +529,7 @@ export import winrt.numerics; #include "winrt/base.h" )"; w.write(format); - w.flush_to_file(settings.output_folder + "winrt/winrt.base.ixx"); + w.flush_to_file(settings.output_folder + "winrt/winrt_base.ixx"); } static void write_numerics_ixx() @@ -537,7 +537,7 @@ export import winrt.numerics; writer w; write_module_preamble(w); auto format = R"( -export module winrt.numerics; +export module winrt_numerics; #if __has_include() #ifdef _MSC_VER @@ -560,7 +560,7 @@ export module winrt.numerics; #endif )"; w.write(format); - w.flush_to_file(settings.output_folder + "winrt/winrt.numerics.ixx"); + w.flush_to_file(settings.output_folder + "winrt/winrt_numerics.ixx"); } static void write_namespace_ixx( @@ -572,18 +572,18 @@ export module winrt.numerics; write_module_preamble(w); // Module declaration - w.write("export module %;\n\n", ns); + w.write("export module winrt.%;\n\n", ns); // Import std and base w.write("import std;\n"); - w.write("export import winrt.base;\n"); + w.write("export import winrt_base;\n"); // Import dependency namespace modules for (auto& dep : deps) { if (projected_namespaces.count(dep)) { - w.write("import %;\n", dep); + w.write("import winrt.%;\n", dep); } } @@ -609,18 +609,18 @@ export module winrt.numerics; write_module_preamble(w); // Module declaration (owner namespace) - w.write("export module %;\n\n", owner); + w.write("export module winrt.%;\n\n", owner); // Import std and base w.write("import std;\n"); - w.write("export import winrt.base;\n"); + w.write("export import winrt_base;\n"); // Import external dependency modules (outside the SCC) for (auto& dep : external_deps) { if (projected_namespaces.count(dep)) { - w.write("import %;\n", dep); + w.write("import winrt.%;\n", dep); } } @@ -673,10 +673,10 @@ export module winrt.numerics; writer w; write_preamble(w); w.write("\n// NOTE: This module does not define declarations of its own.\n"); - w.write("// It re-exports all declarations from the '%' module. This is used to break cycles in the\n", owner); + w.write("// It re-exports all declarations from the 'winrt.%' module. This is used to break cycles in the\n", owner); w.write("// WinRT namespace module dependency graph (SCC owner consolidation).\n\n"); - w.write("export module %;\n", ns); - w.write("export import %;\n", owner); + w.write("export module winrt.%;\n", ns); + w.write("export import winrt.%;\n", owner); w.flush_to_file(settings.output_folder + "winrt/" + std::string(ns) + ".ixx"); } } From ab7288291184122b2ef731d912c8ef634b358cff Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Thu, 23 Apr 2026 20:12:13 -0700 Subject: [PATCH 04/29] Bootstrap basic unit test Co-authored-by: Copilot --- cppwinrt.sln | 14 ++++ strings/base_collections_map.h | 2 +- strings/base_coroutine_foundation.h | 2 +- strings/base_coroutine_threadpool.h | 2 +- strings/base_std_hash.h | 2 +- test/test_module/foundation.cpp | 46 ++++++++++++ test/test_module/main.cpp | 24 +++++++ test/test_module/pch.cpp | 1 + test/test_module/pch.h | 3 + test/test_module/test_module.vcxproj | 103 +++++++++++++++++++++++++++ 10 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 test/test_module/foundation.cpp create mode 100644 test/test_module/main.cpp create mode 100644 test/test_module/pch.cpp create mode 100644 test/test_module/pch.h create mode 100644 test/test_module/test_module.vcxproj diff --git a/cppwinrt.sln b/cppwinrt.sln index 3bcfb33bc..2024a34e3 100644 --- a/cppwinrt.sln +++ b/cppwinrt.sln @@ -124,6 +124,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_nocoro", "test\test_no {D613FB39-5035-4043-91E2-BAB323908AF4} = {D613FB39-5035-4043-91E2-BAB323908AF4} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_module", "test\test_module\test_module.vcxproj", "{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}" + ProjectSection(ProjectDependencies) = postProject + {D613FB39-5035-4043-91E2-BAB323908AF4} = {D613FB39-5035-4043-91E2-BAB323908AF4} + EndProjectSection +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D15C8430-A7CD-4616-BD84-243B26A9F1C2}" ProjectSection(SolutionItems) = preProject build_nuget.cmd = build_nuget.cmd @@ -411,6 +416,14 @@ Global {9E392830-805A-4AAF-932D-C493143EFACA}.Release|x64.Build.0 = Release|x64 {9E392830-805A-4AAF-932D-C493143EFACA}.Release|x86.ActiveCfg = Release|Win32 {9E392830-805A-4AAF-932D-C493143EFACA}.Release|x86.Build.0 = Release|Win32 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|ARM64.ActiveCfg = Debug|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|x64.ActiveCfg = Debug|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|x64.Build.0 = Debug|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|x86.ActiveCfg = Debug|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|ARM64.ActiveCfg = Release|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|x64.ActiveCfg = Release|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|x64.Build.0 = Release|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -435,6 +448,7 @@ Global {5FF6CD6C-515A-4D55-97B6-62AD9BCB77EA} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} {D4C8F881-84D5-4A7B-8BDE-AB4E34A05374} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} {9E392830-805A-4AAF-932D-C493143EFACA} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2783B8FD-EA3B-4D6B-9F81-662D289E02AA} diff --git a/strings/base_collections_map.h b/strings/base_collections_map.h index b40247dab..a205050ce 100644 --- a/strings/base_collections_map.h +++ b/strings/base_collections_map.h @@ -116,7 +116,7 @@ WINRT_EXPORT namespace winrt } } -namespace std +WINRT_EXPORT namespace std { template struct tuple_size> diff --git a/strings/base_coroutine_foundation.h b/strings/base_coroutine_foundation.h index cde752aa3..9012cbce1 100644 --- a/strings/base_coroutine_foundation.h +++ b/strings/base_coroutine_foundation.h @@ -704,7 +704,7 @@ WINRT_EXPORT namespace winrt::impl }; } -namespace std +WINRT_EXPORT namespace std { template struct coroutine_traits diff --git a/strings/base_coroutine_threadpool.h b/strings/base_coroutine_threadpool.h index 3ffcf7002..fdbd2c491 100644 --- a/strings/base_coroutine_threadpool.h +++ b/strings/base_coroutine_threadpool.h @@ -671,7 +671,7 @@ WINRT_EXPORT namespace winrt struct fire_and_forget {}; } -namespace std +WINRT_EXPORT namespace std { template struct coroutine_traits diff --git a/strings/base_std_hash.h b/strings/base_std_hash.h index cdffb13c6..3d5e9d961 100644 --- a/strings/base_std_hash.h +++ b/strings/base_std_hash.h @@ -32,7 +32,7 @@ WINRT_EXPORT namespace winrt::impl }; } -namespace std +WINRT_EXPORT namespace std { template<> struct hash { diff --git a/test/test_module/foundation.cpp b/test/test_module/foundation.cpp new file mode 100644 index 000000000..152131a49 --- /dev/null +++ b/test/test_module/foundation.cpp @@ -0,0 +1,46 @@ +#include "pch.h" + +import std; +import winrt_base; +import winrt.Windows.Foundation; + +using namespace winrt; +using namespace Windows::Foundation; + +TEST_CASE("module_uri") +{ + Uri uri(L"https://example.com/path?query=1"); + REQUIRE(!uri.AbsoluteUri().empty()); + REQUIRE(uri.Host() == L"example.com"); + REQUIRE(uri.Path() == L"/path"); +} + +TEST_CASE("module_property_value") +{ + auto pv = PropertyValue::CreateInt32(42); + REQUIRE(pv.as().GetInt32() == 42); + + auto pvs = PropertyValue::CreateString(L"hello"); + REQUIRE(pvs.as().GetString() == L"hello"); +} + +TEST_CASE("module_hstring") +{ + hstring text = L"C++/WinRT modules"; + REQUIRE(!text.empty()); + REQUIRE(text.size() == 17); + + hstring empty; + REQUIRE(empty.empty()); + REQUIRE(empty.size() == 0); +} + +TEST_CASE("module_events") +{ + winrt::event> my_event; + int received = 0; + auto token = my_event.add([&](auto&&, int value) { received = value; }); + my_event(nullptr, 42); + REQUIRE(received == 42); + my_event.remove(token); +} diff --git a/test/test_module/main.cpp b/test/test_module/main.cpp new file mode 100644 index 000000000..415e26f0f --- /dev/null +++ b/test/test_module/main.cpp @@ -0,0 +1,24 @@ +#include +#define CATCH_CONFIG_RUNNER +#define CATCH_CONFIG_WINDOWS_SEH +#include "catch.hpp" + +import winrt_base; + +using namespace winrt; + +int main(int const argc, char** argv) +{ + init_apartment(); + std::set_terminate([] { reportFatal("Abnormal termination"); ExitProcess(1); }); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + (void)_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + (void)_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + return Catch::Session().run(argc, argv); +} + +CATCH_TRANSLATE_EXCEPTION(hresult_error const& e) +{ + return to_string(e.message()); +} diff --git a/test/test_module/pch.cpp b/test/test_module/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/test_module/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/test_module/pch.h b/test/test_module/pch.h new file mode 100644 index 000000000..d0eb301ac --- /dev/null +++ b/test/test_module/pch.h @@ -0,0 +1,3 @@ +#pragma once + +#include "catch.hpp" diff --git a/test/test_module/test_module.vcxproj b/test/test_module/test_module.vcxproj new file mode 100644 index 000000000..5930e4244 --- /dev/null +++ b/test/test_module/test_module.vcxproj @@ -0,0 +1,103 @@ + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + 16.0 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72} + test_module + test_module + 10.0 + v145 + + + + Application + v145 + + + true + + + false + true + + + + + + + + + $(IntDir)Generated Files\ + + + + Use + pch.h + stdcpplatest + $(CppWinRTGenDir);..\;%(AdditionalIncludeDirectories) + NOMINMAX;%(PreprocessorDefinitions) + Level4 + true + 5311 + /bigobj + true + true + + + Console + ole32.lib;windowsapp.lib;%(AdditionalDependencies) + + + "$(CppWinRTDir)cppwinrt.exe" -in local -out "$(CppWinRTGenDir)." -modules -base -verbose" + + + + + + + CompileAsCppModule + true + NotUsing + + + + + + + + + Create + + + NotUsing + + + + + \ No newline at end of file From 464a75c126d216b70ac0e979f00bc93718624692 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 02:27:09 -0700 Subject: [PATCH 05/29] Fix coroutine export. A few more unit tests. Co-authored-by: Copilot --- cppwinrt/file_writers.h | 4 ++ test/test_module/collections.cpp | 58 +++++++++++++++++++++++++++ test/test_module/coroutines.cpp | 59 ++++++++++++++++++++++++++++ test/test_module/test_module.vcxproj | 18 ++++++++- 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 test/test_module/collections.cpp create mode 100644 test/test_module/coroutines.cpp diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index e22c80636..fb1786218 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -408,6 +408,10 @@ namespace cppwinrt #endif +#if defined(__cpp_lib_coroutine) +#define WINRT_IMPL_COROUTINES +#endif + #define WINRT_IMPL_SHIM(...) (*(abi_t<__VA_ARGS__>**)&static_cast<__VA_ARGS__ const&>(static_cast(*this))) #ifdef _MSC_VER diff --git a/test/test_module/collections.cpp b/test/test_module/collections.cpp new file mode 100644 index 000000000..64b8531a9 --- /dev/null +++ b/test/test_module/collections.cpp @@ -0,0 +1,58 @@ +#include "pch.h" + +import winrt_base; +import winrt.Windows.Foundation; + +using namespace winrt; +using namespace Windows::Foundation::Collections; + +TEST_CASE("module_vector") +{ + auto vec = single_threaded_vector(); + vec.Append(10); + vec.Append(20); + vec.Append(30); + REQUIRE(vec.Size() == 3); + REQUIRE(vec.GetAt(0) == 10); + REQUIRE(vec.GetAt(2) == 30); + + vec.RemoveAtEnd(); + REQUIRE(vec.Size() == 2); +} + +TEST_CASE("module_map") +{ + auto map = single_threaded_map(); + map.Insert(L"key1", L"value1"); + map.Insert(L"key2", L"value2"); + REQUIRE(map.Size() == 2); + REQUIRE(map.Lookup(L"key1") == L"value1"); + REQUIRE(map.HasKey(L"key2")); + REQUIRE(!map.HasKey(L"key3")); +} + +TEST_CASE("module_observable_vector") +{ + auto vec = single_threaded_observable_vector(); + int change_count = 0; + auto token = vec.VectorChanged([&](auto&&, auto&&) { ++change_count; }); + vec.Append(1); + vec.Append(2); + REQUIRE(change_count == 2); + vec.VectorChanged(token); +} + +TEST_CASE("module_iterable") +{ + auto vec = single_threaded_vector(); + vec.Append(1); + vec.Append(2); + vec.Append(3); + + int sum = 0; + for (auto v : vec) + { + sum += v; + } + REQUIRE(sum == 6); +} diff --git a/test/test_module/coroutines.cpp b/test/test_module/coroutines.cpp new file mode 100644 index 000000000..7c509ac06 --- /dev/null +++ b/test/test_module/coroutines.cpp @@ -0,0 +1,59 @@ +#include "pch.h" + +import std; +import winrt_base; +import winrt.Windows.Foundation; + +using namespace winrt; +using namespace Windows::Foundation; + +IAsyncAction do_nothing_async() +{ + co_return; +} + +IAsyncOperation return_42_async() +{ + co_return 42; +} + +IAsyncOperation return_string_async() +{ + co_return L"module coroutine"; +} + +IAsyncAction chain_async() +{ + auto result = co_await return_string_async(); + REQUIRE(!result.empty()); +} + +IAsyncOperation slow_operation() +{ + co_await resume_after(std::chrono::hours(1)); + co_return 0; +} + +TEST_CASE("module_async_action") +{ + auto action = do_nothing_async(); + action.get(); + REQUIRE(action.Status() == AsyncStatus::Completed); +} + +TEST_CASE("module_async_operation") +{ + REQUIRE(return_42_async().get() == 42); +} + +TEST_CASE("module_async_chain") +{ + chain_async().get(); +} + +TEST_CASE("module_async_cancel") +{ + auto op = slow_operation(); + op.Cancel(); + REQUIRE(op.Status() == AsyncStatus::Canceled); +} diff --git a/test/test_module/test_module.vcxproj b/test/test_module/test_module.vcxproj index 5930e4244..4deba2be6 100644 --- a/test/test_module/test_module.vcxproj +++ b/test/test_module/test_module.vcxproj @@ -80,7 +80,22 @@ BeforeTargets="FixupCLCompileOptions" ensures items are added before the module dependency scanner. --> - + + CompileAsCppModule + true + NotUsing + + + CompileAsCppModule + true + NotUsing + + + CompileAsCppModule + true + NotUsing + + CompileAsCppModule true NotUsing @@ -98,6 +113,7 @@ NotUsing + \ No newline at end of file From b6d6186cdd2a93cdae7c5fd3a2e96329bb2371ad Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 02:40:20 -0700 Subject: [PATCH 06/29] module_include, module_exclude Co-authored-by: Copilot --- cppwinrt/main.cpp | 44 +++++++++++++++++++++++----- cppwinrt/settings.h | 4 +++ test/test_module/test_module.vcxproj | 2 +- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/cppwinrt/main.cpp b/cppwinrt/main.cpp index 05ee3119d..39f4f95d9 100644 --- a/cppwinrt/main.cpp +++ b/cppwinrt/main.cpp @@ -41,6 +41,8 @@ namespace cppwinrt { "ignore_velocity", 0, 0 }, // Ignore feature staging metadata and always include implementations { "synchronous", 0, 0 }, // Instructs cppwinrt to run on a single thread to avoid file system issues in batch builds { "modules", 0, 0, {}, "Generate per-namespace C++20 module interface units (.ixx)" }, + { "module_include", 0, option::no_max, "", "Filter which namespaces are included in module .ixx generation" }, + { "module_exclude", 0, option::no_max, "", "Filter which namespaces are excluded from module .ixx generation" }, }; static void print_usage(writer& w) @@ -95,6 +97,15 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder settings.component = args.exists("component"); settings.base = args.exists("base"); + for (auto&& ns : args.values("module_include")) + { + settings.module_include.insert(ns); + } + for (auto&& ns : args.values("module_exclude")) + { + settings.module_exclude.insert(ns); + } + settings.license = args.exists("license"); settings.brackets = args.exists("brackets"); @@ -202,6 +213,13 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder static void build_filters(cache const& c) { + // Build module_filter from -module_include / -module_exclude args. + // This controls which namespaces get .ixx files without affecting header generation. + if (!settings.module_include.empty() || !settings.module_exclude.empty()) + { + settings.module_filter = { settings.module_include, settings.module_exclude }; + } + if (settings.reference.empty()) { return; @@ -424,29 +442,41 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder std::set projected_namespaces; std::mutex ns_deps_mutex; - for (auto&&[ns, members] : c.namespaces()) + // First pass: determine which namespaces will be in the module. + if (settings.modules) { - if (!has_projected_types(members) || !settings.projection_filter.includes(members)) + for (auto&& [ns, members] : c.namespaces()) { - continue; + if (!has_projected_types(members) || !settings.projection_filter.includes(members)) + { + continue; + } + if (settings.module_filter.empty() || settings.module_filter.includes(members)) + { + projected_namespaces.insert(std::string(ns)); + } } + } - if (settings.modules) + for (auto&&[ns, members] : c.namespaces()) + { + if (!has_projected_types(members) || !settings.projection_filter.includes(members)) { - projected_namespaces.insert(std::string(ns)); + continue; } group.add([&, &ns = ns, &members = members] { + bool in_module = projected_namespaces.count(std::string(ns)) > 0; std::set ns_deps; - auto* deps_ptr = settings.modules ? &ns_deps : nullptr; + auto* deps_ptr = (settings.modules && in_module) ? &ns_deps : nullptr; write_namespace_0_h(ns, members, deps_ptr); write_namespace_1_h(ns, members, deps_ptr); write_namespace_2_h(ns, members, deps_ptr); write_namespace_h(c, ns, members, deps_ptr); - if (settings.modules) + if (settings.modules && in_module) { std::lock_guard lock(ns_deps_mutex); ns_deps_map[std::string(ns)] = std::move(ns_deps); diff --git a/cppwinrt/settings.h b/cppwinrt/settings.h index e3a686367..110e64917 100644 --- a/cppwinrt/settings.h +++ b/cppwinrt/settings.h @@ -33,6 +33,10 @@ namespace cppwinrt std::map fastabi_cache; bool modules{}; // Generate per-namespace C++20 module interface units (.ixx) + + std::set module_include; + std::set module_exclude; + winmd::reader::filter module_filter; }; extern settings_type settings; diff --git a/test/test_module/test_module.vcxproj b/test/test_module/test_module.vcxproj index 4deba2be6..e6fb795ca 100644 --- a/test/test_module/test_module.vcxproj +++ b/test/test_module/test_module.vcxproj @@ -73,7 +73,7 @@ ole32.lib;windowsapp.lib;%(AdditionalDependencies) - "$(CppWinRTDir)cppwinrt.exe" -in local -out "$(CppWinRTGenDir)." -modules -base -verbose" + "$(CppWinRTDir)cppwinrt.exe" -in local -out "$(CppWinRTGenDir)." -modules -base -verbose -module_include "Windows.Foundation" @@ -810,6 +813,9 @@ $(XamlMetaDataProviderPch) $(_PCH) . + + . + -prefix -pch $(CppWinRTPrecompiledHeader) @@ -895,4 +901,27 @@ $(XamlMetaDataProviderPch) + + + + + CompileAsCppModule + true + NotUsing + + + NotUsing + + + + diff --git a/test/nuget/NuGetTest.sln b/test/nuget/NuGetTest.sln index 310b0f252..17962b17c 100644 --- a/test/nuget/NuGetTest.sln +++ b/test/nuget/NuGetTest.sln @@ -47,6 +47,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConsoleApplication1", "Cons EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestProxyStub", "TestProxyStub\TestProxyStub.vcxproj", "{98E28FC8-2EB7-4544-9B6A-941462C6D3E2}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleApp", "TestModuleApp\TestModuleApp.vcxproj", "{F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -279,6 +281,14 @@ Global {98E28FC8-2EB7-4544-9B6A-941462C6D3E2}.Release|x64.Build.0 = Release|x64 {98E28FC8-2EB7-4544-9B6A-941462C6D3E2}.Release|x86.ActiveCfg = Release|Win32 {98E28FC8-2EB7-4544-9B6A-941462C6D3E2}.Release|x86.Build.0 = Release|Win32 + {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Debug|ARM64.ActiveCfg = Debug|x64 + {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Debug|x64.ActiveCfg = Debug|x64 + {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Debug|x64.Build.0 = Debug|x64 + {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Debug|x86.ActiveCfg = Debug|x64 + {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Release|ARM64.ActiveCfg = Release|x64 + {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Release|x64.ActiveCfg = Release|x64 + {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Release|x64.Build.0 = Release|x64 + {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/test/nuget/TestModuleApp/PropertySheet.props b/test/nuget/TestModuleApp/PropertySheet.props new file mode 100644 index 000000000..379c2c3a2 --- /dev/null +++ b/test/nuget/TestModuleApp/PropertySheet.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/nuget/TestModuleApp/TestModuleApp.cpp b/test/nuget/TestModuleApp/TestModuleApp.cpp new file mode 100644 index 000000000..80c5a3b2c --- /dev/null +++ b/test/nuget/TestModuleApp/TestModuleApp.cpp @@ -0,0 +1,8 @@ +#include "pch.h" + +#define WINRT_IMPORT_MODULE +import winrt_base; +import winrt.Windows.Foundation; + +#include "TestModuleApp.h" +#include "ModuleTestHelper.g.cpp" diff --git a/test/nuget/TestModuleApp/TestModuleApp.def b/test/nuget/TestModuleApp/TestModuleApp.def new file mode 100644 index 000000000..53d2e7cbf --- /dev/null +++ b/test/nuget/TestModuleApp/TestModuleApp.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/test/nuget/TestModuleApp/TestModuleApp.h b/test/nuget/TestModuleApp/TestModuleApp.h new file mode 100644 index 000000000..c5a699c6b --- /dev/null +++ b/test/nuget/TestModuleApp/TestModuleApp.h @@ -0,0 +1,27 @@ +#pragma once +#include "ModuleTestHelper.g.h" + +namespace winrt::TestModuleApp::implementation +{ + struct ModuleTestHelper : ModuleTestHelperT + { + ModuleTestHelper() = default; + + Windows::Foundation::Uri CreateUri(hstring const& url) + { + return Windows::Foundation::Uri(url); + } + + Windows::Foundation::IAsyncOperation GetStringAsync() + { + co_return L"hello from module"; + } + }; +} + +namespace winrt::TestModuleApp::factory_implementation +{ + struct ModuleTestHelper : ModuleTestHelperT + { + }; +} diff --git a/test/nuget/TestModuleApp/TestModuleApp.idl b/test/nuget/TestModuleApp/TestModuleApp.idl new file mode 100644 index 000000000..27e1842d4 --- /dev/null +++ b/test/nuget/TestModuleApp/TestModuleApp.idl @@ -0,0 +1,11 @@ +namespace TestModuleApp +{ + // A simple runtime class using platform SDK types. + [default_interface] + runtimeclass ModuleTestHelper + { + ModuleTestHelper(); + Windows.Foundation.Uri CreateUri(String url); + Windows.Foundation.IAsyncOperation GetStringAsync(); + } +} diff --git a/test/nuget/TestModuleApp/TestModuleApp.vcxproj b/test/nuget/TestModuleApp/TestModuleApp.vcxproj new file mode 100644 index 000000000..5985f003c --- /dev/null +++ b/test/nuget/TestModuleApp/TestModuleApp.vcxproj @@ -0,0 +1,105 @@ + + + + + true + true + true + Windows.Foundation;TestModuleApp + true + {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C} + TestModuleApp + TestModuleApp + en-US + 14.0 + + + + + Debug + x64 + + + Release + x64 + + + + Application + v145 + Unicode + + + true + + + false + true + + + + + + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + stdcpplatest + Level4 + true + %(AdditionalOptions) /bigobj + 5311;28204 + NOMINMAX;%(PreprocessorDefinitions) + true + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + + TestModuleApp.idl + + + + + Create + + + + TestModuleApp.idl + + + + + + + + + + + + + diff --git a/test/nuget/TestModuleApp/main.cpp b/test/nuget/TestModuleApp/main.cpp new file mode 100644 index 000000000..5e1ae51d4 --- /dev/null +++ b/test/nuget/TestModuleApp/main.cpp @@ -0,0 +1,26 @@ +#include "pch.h" + +#define WINRT_IMPORT_MODULE +import winrt_base; +import winrt.Windows.Foundation; + +#include "TestModuleApp.h" + +using namespace winrt; +using namespace Windows::Foundation; + +int main() +{ + init_apartment(); + + // Test ModuleTestHelper + auto helper = TestModuleApp::ModuleTestHelper(); + auto uri = helper.CreateUri(L"https://example.com"); + printf("URI: %ls\n", uri.AbsoluteUri().c_str()); + + auto str = helper.GetStringAsync().get(); + printf("Async: %ls\n", str.c_str()); + + printf("All module tests passed.\n"); + return 0; +} diff --git a/test/nuget/TestModuleApp/pch.cpp b/test/nuget/TestModuleApp/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/nuget/TestModuleApp/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/nuget/TestModuleApp/pch.h b/test/nuget/TestModuleApp/pch.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/test/nuget/TestModuleApp/pch.h @@ -0,0 +1 @@ +#pragma once From ca0090ab8ac9d59d5daf0f8e6858664e39e3e594 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 11:52:53 -0700 Subject: [PATCH 09/29] Refine generated files in component. Co-authored-by: Copilot --- cppwinrt/file_writers.h | 19 +++++++++++++++++++ ...TestModuleApp.cpp => ModuleTestHelper.cpp} | 2 +- .../{TestModuleApp.h => ModuleTestHelper.h} | 0 .../nuget/TestModuleApp/TestModuleApp.vcxproj | 4 ++-- test/nuget/TestModuleApp/main.cpp | 2 +- 5 files changed, 23 insertions(+), 4 deletions(-) rename test/nuget/TestModuleApp/{TestModuleApp.cpp => ModuleTestHelper.cpp} (81%) rename test/nuget/TestModuleApp/{TestModuleApp.h => ModuleTestHelper.h} (100%) diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index 1e47f79d0..983c393de 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -364,6 +364,25 @@ namespace cppwinrt writer w; write_pch(w); + + if (settings.modules) + { + // Emit module-aware preamble for the component stub. + // The .g.h handles its own imports, but the implementation .h + // needs the types available in scope, so we import them here. + writer dep_scanner; + dep_scanner.add_depends(type); + write_component_g_h(dep_scanner, type); + + w.write("\n#define WINRT_IMPORT_MODULE\n"); + w.write("import winrt_base;\n"); + for (auto&& depends : dep_scanner.depends) + { + w.write("import winrt.%;\n", depends.first); + } + w.write("\n"); + } + write_component_cpp(w, type); w.flush_to_file(path); } diff --git a/test/nuget/TestModuleApp/TestModuleApp.cpp b/test/nuget/TestModuleApp/ModuleTestHelper.cpp similarity index 81% rename from test/nuget/TestModuleApp/TestModuleApp.cpp rename to test/nuget/TestModuleApp/ModuleTestHelper.cpp index 80c5a3b2c..4ab57c0fd 100644 --- a/test/nuget/TestModuleApp/TestModuleApp.cpp +++ b/test/nuget/TestModuleApp/ModuleTestHelper.cpp @@ -4,5 +4,5 @@ import winrt_base; import winrt.Windows.Foundation; -#include "TestModuleApp.h" +#include "ModuleTestHelper.h" #include "ModuleTestHelper.g.cpp" diff --git a/test/nuget/TestModuleApp/TestModuleApp.h b/test/nuget/TestModuleApp/ModuleTestHelper.h similarity index 100% rename from test/nuget/TestModuleApp/TestModuleApp.h rename to test/nuget/TestModuleApp/ModuleTestHelper.h diff --git a/test/nuget/TestModuleApp/TestModuleApp.vcxproj b/test/nuget/TestModuleApp/TestModuleApp.vcxproj index 5985f003c..7e06208de 100644 --- a/test/nuget/TestModuleApp/TestModuleApp.vcxproj +++ b/test/nuget/TestModuleApp/TestModuleApp.vcxproj @@ -79,7 +79,7 @@ - + TestModuleApp.idl @@ -88,7 +88,7 @@ Create - + TestModuleApp.idl diff --git a/test/nuget/TestModuleApp/main.cpp b/test/nuget/TestModuleApp/main.cpp index 5e1ae51d4..e87f12000 100644 --- a/test/nuget/TestModuleApp/main.cpp +++ b/test/nuget/TestModuleApp/main.cpp @@ -4,7 +4,7 @@ import winrt_base; import winrt.Windows.Foundation; -#include "TestModuleApp.h" +#include "ModuleTestHelper.h" using namespace winrt; using namespace Windows::Foundation; From 1aceeede7d1f90d667f5eb9324c502eb4bc813d1 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 12:03:13 -0700 Subject: [PATCH 10/29] Added a type deriving from DependencyObject Co-authored-by: Copilot --- .../TestModuleApp/CustomDependencyObject.cpp | 9 ++++++++ .../TestModuleApp/CustomDependencyObject.h | 23 +++++++++++++++++++ test/nuget/TestModuleApp/TestModuleApp.idl | 9 ++++++++ .../nuget/TestModuleApp/TestModuleApp.vcxproj | 8 ++++++- test/nuget/TestModuleApp/main.cpp | 16 +++++++++++++ 5 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 test/nuget/TestModuleApp/CustomDependencyObject.cpp create mode 100644 test/nuget/TestModuleApp/CustomDependencyObject.h diff --git a/test/nuget/TestModuleApp/CustomDependencyObject.cpp b/test/nuget/TestModuleApp/CustomDependencyObject.cpp new file mode 100644 index 000000000..757286e9e --- /dev/null +++ b/test/nuget/TestModuleApp/CustomDependencyObject.cpp @@ -0,0 +1,9 @@ +#include "pch.h" + +#define WINRT_IMPORT_MODULE +import winrt_base; +import winrt.Windows.Foundation; +import winrt.Windows.UI.Xaml; + +#include "CustomDependencyObject.h" +#include "CustomDependencyObject.g.cpp" diff --git a/test/nuget/TestModuleApp/CustomDependencyObject.h b/test/nuget/TestModuleApp/CustomDependencyObject.h new file mode 100644 index 000000000..79fd6b1ff --- /dev/null +++ b/test/nuget/TestModuleApp/CustomDependencyObject.h @@ -0,0 +1,23 @@ +#pragma once +#include "CustomDependencyObject.g.h" + +namespace winrt::TestModuleApp::implementation +{ + struct CustomDependencyObject : CustomDependencyObjectT + { + CustomDependencyObject() = default; + + hstring Name() { return m_name; } + void Name(hstring const& value) { m_name = value; } + + private: + hstring m_name; + }; +} + +namespace winrt::TestModuleApp::factory_implementation +{ + struct CustomDependencyObject : CustomDependencyObjectT + { + }; +} diff --git a/test/nuget/TestModuleApp/TestModuleApp.idl b/test/nuget/TestModuleApp/TestModuleApp.idl index 27e1842d4..ab3c56707 100644 --- a/test/nuget/TestModuleApp/TestModuleApp.idl +++ b/test/nuget/TestModuleApp/TestModuleApp.idl @@ -1,5 +1,14 @@ namespace TestModuleApp { + // A type that inherits from Windows.UI.Xaml.DependencyObject to exercise + // cross-namespace inheritance in module builds. + [default_interface] + unsealed runtimeclass CustomDependencyObject : Windows.UI.Xaml.DependencyObject + { + CustomDependencyObject(); + String Name{ get; set; }; + } + // A simple runtime class using platform SDK types. [default_interface] runtimeclass ModuleTestHelper diff --git a/test/nuget/TestModuleApp/TestModuleApp.vcxproj b/test/nuget/TestModuleApp/TestModuleApp.vcxproj index 7e06208de..452ff5334 100644 --- a/test/nuget/TestModuleApp/TestModuleApp.vcxproj +++ b/test/nuget/TestModuleApp/TestModuleApp.vcxproj @@ -5,7 +5,7 @@ true true true - Windows.Foundation;TestModuleApp + Windows;TestModuleApp true {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C} TestModuleApp @@ -82,6 +82,9 @@ TestModuleApp.idl + + TestModuleApp.idl + @@ -91,6 +94,9 @@ TestModuleApp.idl + + TestModuleApp.idl + diff --git a/test/nuget/TestModuleApp/main.cpp b/test/nuget/TestModuleApp/main.cpp index e87f12000..a783e719c 100644 --- a/test/nuget/TestModuleApp/main.cpp +++ b/test/nuget/TestModuleApp/main.cpp @@ -3,8 +3,10 @@ #define WINRT_IMPORT_MODULE import winrt_base; import winrt.Windows.Foundation; +import winrt.Windows.UI.Xaml; #include "ModuleTestHelper.h" +#include "CustomDependencyObject.h" using namespace winrt; using namespace Windows::Foundation; @@ -21,6 +23,20 @@ int main() auto str = helper.GetStringAsync().get(); printf("Async: %ls\n", str.c_str()); + // Test CustomDependencyObject (inherits from DependencyObject) + // Note: DependencyObject requires XAML runtime, which isn't available in a console app. + // We verify the type compiles and links correctly; runtime creation would need a XAML host. + try + { + auto obj = winrt::make(); + obj.Name(L"test"); + printf("Name: %ls\n", obj.Name().c_str()); + } + catch (winrt::hresult_error const& e) + { + printf("CustomDependencyObject: expected runtime error (no XAML host): %ls\n", e.message().c_str()); + } + printf("All module tests passed.\n"); return 0; } From 05a845efa4e7599c7c469e1fd8fdfa72034d3694 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 15:17:44 -0700 Subject: [PATCH 11/29] Basic module build/consume support Co-authored-by: Copilot --- cppwinrt/file_writers.h | 42 ++++------ cppwinrt/main.cpp | 20 +---- nuget/Microsoft.Windows.CppWinRT.targets | 56 ++++++++++++- test/nuget/NuGetTest.sln | 45 ++++++++++ .../TestModuleBuilder/PropertySheet.props | 7 ++ .../TestModuleBuilder.vcxproj | 58 +++++++++++++ test/nuget/TestModuleBuilder/pch.cpp | 1 + test/nuget/TestModuleBuilder/pch.h | 1 + test/nuget/TestModuleComponent1/Greeter.cpp | 8 ++ test/nuget/TestModuleComponent1/Greeter.h | 25 ++++++ .../TestModuleComponent1/PropertySheet.props | 7 ++ .../TestModuleComponent1.def | 3 + .../TestModuleComponent1.idl | 12 +++ .../TestModuleComponent1.vcxproj | 84 +++++++++++++++++++ test/nuget/TestModuleComponent1/pch.cpp | 1 + test/nuget/TestModuleComponent1/pch.h | 1 + 16 files changed, 320 insertions(+), 51 deletions(-) create mode 100644 test/nuget/TestModuleBuilder/PropertySheet.props create mode 100644 test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj create mode 100644 test/nuget/TestModuleBuilder/pch.cpp create mode 100644 test/nuget/TestModuleBuilder/pch.h create mode 100644 test/nuget/TestModuleComponent1/Greeter.cpp create mode 100644 test/nuget/TestModuleComponent1/Greeter.h create mode 100644 test/nuget/TestModuleComponent1/PropertySheet.props create mode 100644 test/nuget/TestModuleComponent1/TestModuleComponent1.def create mode 100644 test/nuget/TestModuleComponent1/TestModuleComponent1.idl create mode 100644 test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj create mode 100644 test/nuget/TestModuleComponent1/pch.cpp create mode 100644 test/nuget/TestModuleComponent1/pch.h diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index 983c393de..c1ed94712 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -11,7 +11,7 @@ namespace cppwinrt auto wrap_file_guard = wrap_open_file_guard(w, "BASE"); { - auto wrap_includes = wrap_module_aware_includes_guard(w, settings.modules); + auto wrap_includes = wrap_module_aware_includes_guard(w, true); w.write(strings::base_includes); } w.write(strings::base_macros); @@ -150,7 +150,7 @@ namespace cppwinrt write_open_file_guard(w, ns, '1'); { - auto wrap_includes = wrap_module_aware_includes_guard(w, settings.modules); + auto wrap_includes = wrap_module_aware_includes_guard(w, true); for (auto&& depends : w.depends) { w.write_depends(depends.first, '0'); @@ -184,7 +184,7 @@ namespace cppwinrt char const impl = promote ? '2' : '1'; { - auto wrap_includes = wrap_module_aware_includes_guard(w, settings.modules); + auto wrap_includes = wrap_module_aware_includes_guard(w, true); for (auto&& depends : w.depends) { w.write_depends(depends.first, impl); @@ -236,19 +236,13 @@ namespace cppwinrt write_namespace_special(w, ns); write_close_file_guard(w); - if (settings.modules) - { - w.write("#endif\n"); // WINRT_IMPORT_MODULE - } + w.write("#endif\n"); // WINRT_IMPORT_MODULE w.swap(); write_preamble(w); write_open_file_guard(w, ns); - if (settings.modules) - { - w.write("#ifndef WINRT_IMPORT_MODULE\n\n"); - } + w.write("#ifndef WINRT_IMPORT_MODULE\n\n"); { - auto wrap_includes = wrap_module_aware_includes_guard(w, settings.modules); + auto wrap_includes = wrap_module_aware_includes_guard(w, true); write_version_assert(w); write_parent_depends(w, c, ns); @@ -282,26 +276,20 @@ namespace cppwinrt write_preamble(w); write_include_guard(w); - if (settings.modules) + w.write("#ifdef WINRT_IMPORT_MODULE\n"); + w.write("#include \"winrt/module.h\"\n"); + for (auto&& depends : w.depends) { - w.write("#ifdef WINRT_IMPORT_MODULE\n"); - w.write("#include \"winrt/module.h\"\n"); - for (auto&& depends : w.depends) - { - w.write("import winrt.%;\n", depends.first); - } - w.write("#else\n"); + w.write("import winrt.%;\n", depends.first); } + w.write("#else\n"); for (auto&& depends : w.depends) { w.write_depends(depends.first); } - if (settings.modules) - { - w.write("#endif\n"); - } + w.write("#endif\n"); auto filename = settings.output_folder + get_generated_component_filename(type) + ".g.h"; path folder = filename; @@ -365,11 +353,9 @@ namespace cppwinrt writer w; write_pch(w); - if (settings.modules) + // The .g.h handles its own imports, but the implementation .h + // needs the types available in scope, so we import them here. { - // Emit module-aware preamble for the component stub. - // The .g.h handles its own imports, but the implementation .h - // needs the types available in scope, so we import them here. writer dep_scanner; dep_scanner.add_depends(type); write_component_g_h(dep_scanner, type); diff --git a/cppwinrt/main.cpp b/cppwinrt/main.cpp index d0eebb813..607834071 100644 --- a/cppwinrt/main.cpp +++ b/cppwinrt/main.cpp @@ -490,6 +490,7 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder if (settings.base) { write_base_h(); + write_module_h(); } if (settings.component) @@ -524,30 +525,11 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder group.get(); - if (settings.modules) - { - fprintf(stderr, "module namespaces: %zu\n", projected_namespaces.size()); - for (auto& ns : projected_namespaces) - { - auto it = ns_deps_map.find(ns); - size_t ndeps = (it != ns_deps_map.end()) ? it->second.size() : 0; - fprintf(stderr, " ns: %s (%zu deps)\n", ns.c_str(), ndeps); - if (it != ns_deps_map.end()) - { - for (auto& dep : it->second) - { - fprintf(stderr, " dep: %s\n", dep.c_str()); - } - } - } - } - // Generate per-namespace module interface files (v2) if (settings.modules && (settings.base || settings.component)) { if (settings.base) { - write_module_h(); write_numerics_ixx(); write_base_ixx(); } diff --git a/nuget/Microsoft.Windows.CppWinRT.targets b/nuget/Microsoft.Windows.CppWinRT.targets index 834d911da..d147fe198 100644 --- a/nuget/Microsoft.Windows.CppWinRT.targets +++ b/nuget/Microsoft.Windows.CppWinRT.targets @@ -26,9 +26,10 @@ Copyright (C) Microsoft Corporation. All rights reserved. $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)))..\..\ $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory))) $(CppWinRTParameters) -fastabi - $(CppWinRTParameters) -modules - $(CppWinRTParameters) -module_include $(CppWinRTModuleInclude.Replace(';', ' ')) - $(CppWinRTParameters) -module_exclude $(CppWinRTModuleExclude.Replace(';', ' ')) + + -modules + -module_include $(CppWinRTModuleInclude.Replace(';', ' ')) + $(CppWinRTCommandModuleFilter) -module_exclude $(CppWinRTModuleExclude.Replace(';', ' ')) "$(CppWinRTPackageDir)bin\" "$(CppWinRTPackageDir)" @@ -654,6 +655,9 @@ $(XamlMetaDataProviderPch) <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) + + <_CppwinrtParameters Condition="'$(CppWinRTConsumeModule)'!='true'">$(_CppwinrtParameters) $(CppWinRTCommandModules) $(CppWinRTCommandModuleFilter) <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtInputs->'-in "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) -out "$(GeneratedFilesDir)." @@ -841,7 +845,7 @@ $(XamlMetaDataProviderPch) <_CppwinrtCompRefs Include="@(CppWinRTPlatformWinMDReferences)"/> - <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) -overwrite -name $(RootNamespace) $(CppWinRTCommandPrecompiledHeader) $(CppWinRTCommandUsePrefixes) -comp "$(GeneratedFilesDir)sources" + <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) $(CppWinRTCommandModules) -overwrite -name $(RootNamespace) $(CppWinRTCommandPrecompiledHeader) $(CppWinRTCommandUsePrefixes) -comp "$(GeneratedFilesDir)sources" <_CppwinrtParameters Condition="'$(CppWinRTOptimized)'=='true'">$(_CppwinrtParameters) -opt <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtCompInputs->'-in "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtCompRefs->'-ref "%(WinMDPath)"', ' ') @@ -912,6 +916,7 @@ $(XamlMetaDataProviderPch) Condition="'$(CppWinRTBuildModule)'=='true'" DependsOnTargets="CppWinRTMakeProjections" BeforeTargets="FixupCLCompileOptions"> + CompileAsCppModule @@ -924,4 +929,47 @@ $(XamlMetaDataProviderPch) + + + + + $(GeneratedFilesDir) + $(IntDir) + $(OutDir) + + + + + + + + + + + + + + + + @(_CppWinRTResolvedModuleRefs->'%(CppWinRTModuleIfcDir)') + + + diff --git a/test/nuget/NuGetTest.sln b/test/nuget/NuGetTest.sln index 17962b17c..3f2210665 100644 --- a/test/nuget/NuGetTest.sln +++ b/test/nuget/NuGetTest.sln @@ -49,6 +49,19 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestProxyStub", "TestProxyS EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleApp", "TestModuleApp\TestModuleApp.vcxproj", "{F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleBuilder", "TestModuleBuilder\TestModuleBuilder.vcxproj", "{A1B2C3D4-1111-2222-3333-444455556666}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleComponent1", "TestModuleComponent1\TestModuleComponent1.vcxproj", "{C1C2C3C4-1111-2222-3333-444455556666}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleComponent2", "TestModuleComponent2\TestModuleComponent2.vcxproj", "{C2C2C3C4-1111-2222-3333-444455556666}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleConsumerApp", "TestModuleConsumerApp\TestModuleConsumerApp.vcxproj", "{B1B2C3D4-5555-6666-7777-888899990000}" + ProjectSection(ProjectDependencies) = postProject + {A1B2C3D4-1111-2222-3333-444455556666} = {A1B2C3D4-1111-2222-3333-444455556666} + {C1C2C3C4-1111-2222-3333-444455556666} = {C1C2C3C4-1111-2222-3333-444455556666} + {C2C2C3C4-1111-2222-3333-444455556666} = {C2C2C3C4-1111-2222-3333-444455556666} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -289,6 +302,38 @@ Global {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Release|x64.ActiveCfg = Release|x64 {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Release|x64.Build.0 = Release|x64 {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Release|x86.ActiveCfg = Release|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Debug|ARM64.ActiveCfg = Release|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Debug|x64.ActiveCfg = Release|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Debug|x64.Build.0 = Release|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Debug|x86.ActiveCfg = Release|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Release|ARM64.ActiveCfg = Release|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Release|x64.ActiveCfg = Release|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Release|x64.Build.0 = Release|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Release|x86.ActiveCfg = Release|x64 + {C1C2C3C4-1111-2222-3333-444455556666}.Debug|ARM64.ActiveCfg = Release|x64 + {C1C2C3C4-1111-2222-3333-444455556666}.Debug|x64.ActiveCfg = Release|x64 + {C1C2C3C4-1111-2222-3333-444455556666}.Debug|x64.Build.0 = Release|x64 + {C1C2C3C4-1111-2222-3333-444455556666}.Debug|x86.ActiveCfg = Release|x64 + {C1C2C3C4-1111-2222-3333-444455556666}.Release|ARM64.ActiveCfg = Release|x64 + {C1C2C3C4-1111-2222-3333-444455556666}.Release|x64.ActiveCfg = Release|x64 + {C1C2C3C4-1111-2222-3333-444455556666}.Release|x64.Build.0 = Release|x64 + {C1C2C3C4-1111-2222-3333-444455556666}.Release|x86.ActiveCfg = Release|x64 + {C2C2C3C4-1111-2222-3333-444455556666}.Debug|ARM64.ActiveCfg = Release|x64 + {C2C2C3C4-1111-2222-3333-444455556666}.Debug|x64.ActiveCfg = Release|x64 + {C2C2C3C4-1111-2222-3333-444455556666}.Debug|x64.Build.0 = Release|x64 + {C2C2C3C4-1111-2222-3333-444455556666}.Debug|x86.ActiveCfg = Release|x64 + {C2C2C3C4-1111-2222-3333-444455556666}.Release|ARM64.ActiveCfg = Release|x64 + {C2C2C3C4-1111-2222-3333-444455556666}.Release|x64.ActiveCfg = Release|x64 + {C2C2C3C4-1111-2222-3333-444455556666}.Release|x64.Build.0 = Release|x64 + {C2C2C3C4-1111-2222-3333-444455556666}.Release|x86.ActiveCfg = Release|x64 + {B1B2C3D4-5555-6666-7777-888899990000}.Debug|ARM64.ActiveCfg = Release|x64 + {B1B2C3D4-5555-6666-7777-888899990000}.Debug|x64.ActiveCfg = Release|x64 + {B1B2C3D4-5555-6666-7777-888899990000}.Debug|x64.Build.0 = Release|x64 + {B1B2C3D4-5555-6666-7777-888899990000}.Debug|x86.ActiveCfg = Release|x64 + {B1B2C3D4-5555-6666-7777-888899990000}.Release|ARM64.ActiveCfg = Release|x64 + {B1B2C3D4-5555-6666-7777-888899990000}.Release|x64.ActiveCfg = Release|x64 + {B1B2C3D4-5555-6666-7777-888899990000}.Release|x64.Build.0 = Release|x64 + {B1B2C3D4-5555-6666-7777-888899990000}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/test/nuget/TestModuleBuilder/PropertySheet.props b/test/nuget/TestModuleBuilder/PropertySheet.props new file mode 100644 index 000000000..379c2c3a2 --- /dev/null +++ b/test/nuget/TestModuleBuilder/PropertySheet.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj b/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj new file mode 100644 index 000000000..7c9190e15 --- /dev/null +++ b/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj @@ -0,0 +1,58 @@ + + + + + true + Windows.Foundation + true + {A1B2C3D4-1111-2222-3333-444455556666} + TestModuleBuilder + TestModuleBuilder + en-US + 14.0 + + + + + Release + x64 + + + + StaticLibrary + v145 + Unicode + + + false + true + + + + + + + + + + Use + pch.h + stdcpplatest + Level4 + true + %(AdditionalOptions) /bigobj + true + NOMINMAX;%(PreprocessorDefinitions) + + + + + + + + Create + + + + + diff --git a/test/nuget/TestModuleBuilder/pch.cpp b/test/nuget/TestModuleBuilder/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/nuget/TestModuleBuilder/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/nuget/TestModuleBuilder/pch.h b/test/nuget/TestModuleBuilder/pch.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/test/nuget/TestModuleBuilder/pch.h @@ -0,0 +1 @@ +#pragma once diff --git a/test/nuget/TestModuleComponent1/Greeter.cpp b/test/nuget/TestModuleComponent1/Greeter.cpp new file mode 100644 index 000000000..04c668e10 --- /dev/null +++ b/test/nuget/TestModuleComponent1/Greeter.cpp @@ -0,0 +1,8 @@ +#include "pch.h" + +#define WINRT_IMPORT_MODULE +import winrt_base; +import winrt.Windows.Foundation; + +#include "Greeter.h" +#include "Greeter.g.cpp" diff --git a/test/nuget/TestModuleComponent1/Greeter.h b/test/nuget/TestModuleComponent1/Greeter.h new file mode 100644 index 000000000..726fb7d22 --- /dev/null +++ b/test/nuget/TestModuleComponent1/Greeter.h @@ -0,0 +1,25 @@ +#pragma once +#include "Greeter.g.h" + +namespace winrt::TestModuleComponent1::implementation +{ + struct Greeter : GreeterT + { + Greeter() : m_name(L"World") {} + Greeter(hstring const& name) : m_name(name) {} + + hstring Name() { return m_name; } + hstring Greet() { return L"Hello, " + m_name + L"!"; } + Windows::Foundation::Uri Homepage() { return Windows::Foundation::Uri(L"https://example.com/" + m_name); } + + private: + hstring m_name; + }; +} + +namespace winrt::TestModuleComponent1::factory_implementation +{ + struct Greeter : GreeterT + { + }; +} diff --git a/test/nuget/TestModuleComponent1/PropertySheet.props b/test/nuget/TestModuleComponent1/PropertySheet.props new file mode 100644 index 000000000..379c2c3a2 --- /dev/null +++ b/test/nuget/TestModuleComponent1/PropertySheet.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/nuget/TestModuleComponent1/TestModuleComponent1.def b/test/nuget/TestModuleComponent1/TestModuleComponent1.def new file mode 100644 index 000000000..53d2e7cbf --- /dev/null +++ b/test/nuget/TestModuleComponent1/TestModuleComponent1.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/test/nuget/TestModuleComponent1/TestModuleComponent1.idl b/test/nuget/TestModuleComponent1/TestModuleComponent1.idl new file mode 100644 index 000000000..73a983ee2 --- /dev/null +++ b/test/nuget/TestModuleComponent1/TestModuleComponent1.idl @@ -0,0 +1,12 @@ +namespace TestModuleComponent1 +{ + [default_interface] + runtimeclass Greeter + { + Greeter(); + Greeter(String name); + String Name{ get; }; + String Greet(); + Windows.Foundation.Uri Homepage{ get; }; + } +} diff --git a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj new file mode 100644 index 000000000..9a968c63e --- /dev/null +++ b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj @@ -0,0 +1,84 @@ + + + + + true + true + true + true + TestModuleComponent1 + true + {C1C2C3C4-1111-2222-3333-444455556666} + TestModuleComponent1 + TestModuleComponent1 + en-US + 14.0 + + + + + Release + x64 + + + + DynamicLibrary + v145 + Unicode + + + false + true + + + + + + + + + + Use + pch.h + stdcpplatest + Level4 + true + %(AdditionalOptions) /bigobj /ifcSearchDir "$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleBuilder" + true + _WINRT_DLL;NOMINMAX;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + false + TestModuleComponent1.def + + + + + + TestModuleComponent1.idl + + + + + Create + + + TestModuleComponent1.idl + + + + + + + + + + + + + + + + diff --git a/test/nuget/TestModuleComponent1/pch.cpp b/test/nuget/TestModuleComponent1/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/nuget/TestModuleComponent1/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/nuget/TestModuleComponent1/pch.h b/test/nuget/TestModuleComponent1/pch.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/test/nuget/TestModuleComponent1/pch.h @@ -0,0 +1 @@ +#pragma once From 6fffa1dc73bc342526c30874cc05753eb216195c Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 15:39:27 -0700 Subject: [PATCH 12/29] Prefix namespace ixx files with "winrt." for consistency with module names and built ifc files. Co-authored-by: Copilot --- cppwinrt/file_writers.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index c1ed94712..b5e043b9d 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -604,7 +604,7 @@ export module winrt_numerics; w.write("#include \"winrt/impl/%.2.h\"\n", ns); w.write("#include \"winrt/%.h\"\n", ns); - w.flush_to_file(settings.output_folder + "winrt/" + std::string(ns) + ".ixx"); + w.flush_to_file(settings.output_folder + "winrt/winrt." + std::string(ns) + ".ixx"); } static void write_namespace_scc_owner_ixx( @@ -672,7 +672,7 @@ export module winrt_numerics; w.write("#include \"winrt/%.h\"\n", ns); } - w.flush_to_file(settings.output_folder + "winrt/" + std::string(owner) + ".ixx"); + w.flush_to_file(settings.output_folder + "winrt/winrt." + std::string(owner) + ".ixx"); } static void write_namespace_reexport_ixx( @@ -686,6 +686,6 @@ export module winrt_numerics; w.write("// WinRT namespace module dependency graph (SCC owner consolidation).\n\n"); w.write("export module winrt.%;\n", ns); w.write("export import winrt.%;\n", owner); - w.flush_to_file(settings.output_folder + "winrt/" + std::string(ns) + ".ixx"); + w.flush_to_file(settings.output_folder + "winrt/winrt." + std::string(ns) + ".ixx"); } } From b8b97152699ae0e8f88d387b744a4cad4b1bf0a6 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 17:00:15 -0700 Subject: [PATCH 13/29] IFC needs better per-project scoping. Now it fully works end to end. --- cppwinrt/main.cpp | 2 +- nuget/Microsoft.Windows.CppWinRT.targets | 45 ++++++---- .../TestModuleComponent1.vcxproj | 5 +- .../TestModuleComponent2/GreeterGroup.cpp | 9 ++ .../nuget/TestModuleComponent2/GreeterGroup.h | 36 ++++++++ .../TestModuleComponent2/PropertySheet.props | 7 ++ .../TestModuleComponent2.def | 3 + .../TestModuleComponent2.idl | 10 +++ .../TestModuleComponent2.vcxproj | 86 +++++++++++++++++++ test/nuget/TestModuleComponent2/pch.cpp | 1 + test/nuget/TestModuleComponent2/pch.h | 1 + .../TestModuleConsumerApp/PropertySheet.props | 7 ++ .../TestModuleConsumerApp.vcxproj | 68 +++++++++++++++ test/nuget/TestModuleConsumerApp/main.cpp | 32 +++++++ test/nuget/TestModuleConsumerApp/pch.cpp | 1 + test/nuget/TestModuleConsumerApp/pch.h | 1 + 16 files changed, 294 insertions(+), 20 deletions(-) create mode 100644 test/nuget/TestModuleComponent2/GreeterGroup.cpp create mode 100644 test/nuget/TestModuleComponent2/GreeterGroup.h create mode 100644 test/nuget/TestModuleComponent2/PropertySheet.props create mode 100644 test/nuget/TestModuleComponent2/TestModuleComponent2.def create mode 100644 test/nuget/TestModuleComponent2/TestModuleComponent2.idl create mode 100644 test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj create mode 100644 test/nuget/TestModuleComponent2/pch.cpp create mode 100644 test/nuget/TestModuleComponent2/pch.h create mode 100644 test/nuget/TestModuleConsumerApp/PropertySheet.props create mode 100644 test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj create mode 100644 test/nuget/TestModuleConsumerApp/main.cpp create mode 100644 test/nuget/TestModuleConsumerApp/pch.cpp create mode 100644 test/nuget/TestModuleConsumerApp/pch.h diff --git a/cppwinrt/main.cpp b/cppwinrt/main.cpp index 607834071..2ae755ae7 100644 --- a/cppwinrt/main.cpp +++ b/cppwinrt/main.cpp @@ -526,7 +526,7 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder group.get(); // Generate per-namespace module interface files (v2) - if (settings.modules && (settings.base || settings.component)) + if (settings.modules) { if (settings.base) { diff --git a/nuget/Microsoft.Windows.CppWinRT.targets b/nuget/Microsoft.Windows.CppWinRT.targets index d147fe198..4cb6d8d4f 100644 --- a/nuget/Microsoft.Windows.CppWinRT.targets +++ b/nuget/Microsoft.Windows.CppWinRT.targets @@ -656,8 +656,8 @@ $(XamlMetaDataProviderPch) <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) - <_CppwinrtParameters Condition="'$(CppWinRTConsumeModule)'!='true'">$(_CppwinrtParameters) $(CppWinRTCommandModules) $(CppWinRTCommandModuleFilter) + Module consumers: don't pass -modules to platform projection (just generate headers + module.h). --> + <_CppwinrtParameters Condition="'$(_CppWinRTConsumesPlatformModules)'!='true'">$(_CppwinrtParameters) $(CppWinRTCommandModules) $(CppWinRTCommandModuleFilter) <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtInputs->'-in "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) -out "$(GeneratedFilesDir)." @@ -736,7 +736,7 @@ $(XamlMetaDataProviderPch) <_CppwinrtRefRefs Include="@(CppWinRTPlatformWinMDReferences)"/> - <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) + <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) $(CppWinRTCommandModules) <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtRefInputs->'-in "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtRefRefs->'-ref "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) -out "$(GeneratedFilesDir)." @@ -947,29 +947,40 @@ $(XamlMetaDataProviderPch) - - + + + <_CppWinRTModuleProviders Remove="@(_CppWinRTModuleProviders)" /> + <_CppWinRTModuleProviders Include="@(ProjectReference)" + Condition="'%(ProjectReference.CppWinRTConsumeModules)' == 'true'" /> + + + + + <_CppWinRTConsumesPlatformModules>true + + + + + BuildInParallel="false" + Condition="'@(_CppWinRTModuleProviders)' != ''"> - + - - - @(_CppWinRTResolvedModuleRefs->'%(CppWinRTModuleIfcDir)') - diff --git a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj index 9a968c63e..690c32eb5 100644 --- a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj +++ b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj @@ -4,7 +4,6 @@ true true - true true TestModuleComponent1 true @@ -77,7 +76,9 @@ - + + true + diff --git a/test/nuget/TestModuleComponent2/GreeterGroup.cpp b/test/nuget/TestModuleComponent2/GreeterGroup.cpp new file mode 100644 index 000000000..6e4d5064a --- /dev/null +++ b/test/nuget/TestModuleComponent2/GreeterGroup.cpp @@ -0,0 +1,9 @@ +#include "pch.h" + +#define WINRT_IMPORT_MODULE +import winrt_base; +import winrt.Windows.Foundation; +import winrt.TestModuleComponent1; + +#include "GreeterGroup.h" +#include "GreeterGroup.g.cpp" diff --git a/test/nuget/TestModuleComponent2/GreeterGroup.h b/test/nuget/TestModuleComponent2/GreeterGroup.h new file mode 100644 index 000000000..461570425 --- /dev/null +++ b/test/nuget/TestModuleComponent2/GreeterGroup.h @@ -0,0 +1,36 @@ +#pragma once +#include "GreeterGroup.g.h" + +namespace winrt::TestModuleComponent2::implementation +{ + struct GreeterGroup : GreeterGroupT + { + GreeterGroup() = default; + + void Add(winrt::TestModuleComponent1::Greeter const& greeter) + { + m_greeters.push_back(greeter); + } + + hstring GreetAll() + { + hstring result; + for (auto const& g : m_greeters) + { + if (!result.empty()) result = result + L", "; + result = result + g.Greet(); + } + return result; + } + + private: + std::vector m_greeters; + }; +} + +namespace winrt::TestModuleComponent2::factory_implementation +{ + struct GreeterGroup : GreeterGroupT + { + }; +} diff --git a/test/nuget/TestModuleComponent2/PropertySheet.props b/test/nuget/TestModuleComponent2/PropertySheet.props new file mode 100644 index 000000000..379c2c3a2 --- /dev/null +++ b/test/nuget/TestModuleComponent2/PropertySheet.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/nuget/TestModuleComponent2/TestModuleComponent2.def b/test/nuget/TestModuleComponent2/TestModuleComponent2.def new file mode 100644 index 000000000..53d2e7cbf --- /dev/null +++ b/test/nuget/TestModuleComponent2/TestModuleComponent2.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/test/nuget/TestModuleComponent2/TestModuleComponent2.idl b/test/nuget/TestModuleComponent2/TestModuleComponent2.idl new file mode 100644 index 000000000..e45eed438 --- /dev/null +++ b/test/nuget/TestModuleComponent2/TestModuleComponent2.idl @@ -0,0 +1,10 @@ +namespace TestModuleComponent2 +{ + [default_interface] + runtimeclass GreeterGroup + { + GreeterGroup(); + void Add(TestModuleComponent1.Greeter greeter); + String GreetAll(); + } +} diff --git a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj new file mode 100644 index 000000000..62c79146f --- /dev/null +++ b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj @@ -0,0 +1,86 @@ + + + + + true + true + true + TestModuleComponent2 + true + {C2C2C3C4-1111-2222-3333-444455556666} + TestModuleComponent2 + TestModuleComponent2 + en-US + 14.0 + + + + + Release + x64 + + + + DynamicLibrary + v145 + Unicode + + + false + true + + + + + + + + + + Use + pch.h + stdcpplatest + Level4 + true + %(AdditionalOptions) /bigobj /ifcSearchDir "$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleBuilder" /ifcSearchDir "$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleComponent1" /reference "winrt.TestModuleComponent1=$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleComponent1\winrt.TestModuleComponent1.ixx.ifc" + true + _WINRT_DLL;NOMINMAX;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + false + TestModuleComponent2.def + + + + + + TestModuleComponent2.idl + + + + + Create + + + TestModuleComponent2.idl + + + + + + + + + + + + + true + + + + + + diff --git a/test/nuget/TestModuleComponent2/pch.cpp b/test/nuget/TestModuleComponent2/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/nuget/TestModuleComponent2/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/nuget/TestModuleComponent2/pch.h b/test/nuget/TestModuleComponent2/pch.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/test/nuget/TestModuleComponent2/pch.h @@ -0,0 +1 @@ +#pragma once diff --git a/test/nuget/TestModuleConsumerApp/PropertySheet.props b/test/nuget/TestModuleConsumerApp/PropertySheet.props new file mode 100644 index 000000000..379c2c3a2 --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/PropertySheet.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj new file mode 100644 index 000000000..11ff7af03 --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj @@ -0,0 +1,68 @@ + + + + + true + true + {B1B2C3D4-5555-6666-7777-888899990000} + TestModuleConsumerApp + TestModuleConsumerApp + en-US + 14.0 + + + + + Release + x64 + + + + Application + v145 + Unicode + + + false + true + + + + + + + + + + Use + pch.h + stdcpplatest + Level4 + true + %(AdditionalOptions) /bigobj /ifcSearchDir "$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleBuilder" + true + NOMINMAX;%(PreprocessorDefinitions) + + + Console + + + + + + + + Create + + + + + + true + + + + + + + diff --git a/test/nuget/TestModuleConsumerApp/main.cpp b/test/nuget/TestModuleConsumerApp/main.cpp new file mode 100644 index 000000000..b3a4cab3f --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/main.cpp @@ -0,0 +1,32 @@ +#include "pch.h" + +import winrt_base; +import winrt.Windows.Foundation; +import winrt.TestModuleComponent1; +import winrt.TestModuleComponent2; + +using namespace winrt; +using namespace Windows::Foundation; + +int main() +{ + init_apartment(); + + // Platform types from pre-built modules + Uri uri(L"https://example.com/consumer"); + printf("URI: %ls\n", uri.AbsoluteUri().c_str()); + + // Component1 + auto greeter = TestModuleComponent1::Greeter(L"Modules"); + printf("Greet: %ls\n", greeter.Greet().c_str()); + printf("Homepage: %ls\n", greeter.Homepage().AbsoluteUri().c_str()); + + // Component2 (depends on Component1) + auto group = TestModuleComponent2::GreeterGroup(); + group.Add(TestModuleComponent1::Greeter(L"Alice")); + group.Add(TestModuleComponent1::Greeter(L"Bob")); + printf("GreetAll: %ls\n", group.GreetAll().c_str()); + + printf("All consumer tests passed.\n"); + return 0; +} diff --git a/test/nuget/TestModuleConsumerApp/pch.cpp b/test/nuget/TestModuleConsumerApp/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/nuget/TestModuleConsumerApp/pch.h b/test/nuget/TestModuleConsumerApp/pch.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/pch.h @@ -0,0 +1 @@ +#pragma once From 25ce3350e41e6323cdb30fee474bf7203b831c7d Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 19:21:19 -0700 Subject: [PATCH 14/29] Minor fix to TestModuleComponent2 --- test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj index 62c79146f..b49279d54 100644 --- a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj +++ b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj @@ -42,7 +42,7 @@ stdcpplatest Level4 true - %(AdditionalOptions) /bigobj /ifcSearchDir "$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleBuilder" /ifcSearchDir "$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleComponent1" /reference "winrt.TestModuleComponent1=$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleComponent1\winrt.TestModuleComponent1.ixx.ifc" + %(AdditionalOptions) /bigobj /ifcSearchDir "$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleBuilder" true _WINRT_DLL;NOMINMAX;%(PreprocessorDefinitions) $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) From ccfee6584fa0873af766b8c716008e794dbdab07 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 20:26:02 -0700 Subject: [PATCH 15/29] Documentation and polish. Co-authored-by: YexuanXiao Co-authored-by: Copilot --- .github/instructions/cppwinrt.instructions.md | 56 ++++++ .github/instructions/modules.instructions.md | 41 ++++ cppwinrt/component_writers.h | 5 +- cppwinrt/file_writers.h | 108 +++++++++- cppwinrt/main.cpp | 10 +- docs/modules-design.md | 190 ++++++++++++++++++ nuget/Microsoft.Windows.CppWinRT.targets | 16 +- nuget/modules.md | 150 ++++++++++++++ nuget/readme.md | 12 ++ .../TestModuleApp/CustomDependencyObject.cpp | 1 - test/nuget/TestModuleApp/ModuleTestHelper.cpp | 1 - test/nuget/TestModuleApp/main.cpp | 1 - .../TestModuleComponent1.vcxproj | 2 +- .../TestModuleComponent2/GreeterGroup.cpp | 1 - .../TestModuleComponent2.vcxproj | 2 +- .../TestModuleConsumerApp.vcxproj | 2 +- test/nuget/TestModuleConsumerApp/main.cpp | 1 - test/test_cpp20_module/collections.cpp | 1 - test/test_cpp20_module/coroutines.cpp | 1 - test/test_cpp20_module/foundation.cpp | 1 - .../test_cpp20_module.vcxproj | 17 +- 21 files changed, 573 insertions(+), 46 deletions(-) create mode 100644 .github/instructions/cppwinrt.instructions.md create mode 100644 .github/instructions/modules.instructions.md create mode 100644 docs/modules-design.md create mode 100644 nuget/modules.md diff --git a/.github/instructions/cppwinrt.instructions.md b/.github/instructions/cppwinrt.instructions.md new file mode 100644 index 000000000..375163b11 --- /dev/null +++ b/.github/instructions/cppwinrt.instructions.md @@ -0,0 +1,56 @@ +# C++/WinRT Codebase — Agent Instructions + +## Repository Structure + +- `cppwinrt/` — The cppwinrt.exe code generator (C++ source) + - `main.cpp` — CLI parsing, namespace iteration, SCC detection, .ixx orchestration + - `file_writers.h` — All file generation functions (headers, .ixx modules, component stubs) + - `code_writers.h` — Code-level writing utilities (guards, namespace wrappers, type writers) + - `type_writers.h` — Type formatting (ABI signatures, names, GUIDs) + - `component_writers.h` — Component authoring code generation + - `helpers.h` — Metadata reading helpers + - `settings.h` — Global settings populated from CLI args + - `text_writer.h` — Core text writer infrastructure +- `strings/` — String literal `.h` files embedded by the prebuild step. Changes require: delete prebuild.exe → rebuild solution +- `nuget/` — MSBuild targets, props, and NuGet packaging + - `Microsoft.Windows.CppWinRT.targets` — Main MSBuild integration (projections, module support) +- `test/` — Test projects + - `test/test_cpp20_module/` — Standalone module test (in main solution) + - `test/nuget/` — NuGet integration tests (multi-project module chain) +- `docs/` — Documentation +- `natvis/` — Visual Studio debug visualizer (includes strings/*.h in its pch.h — add new files there too) + +## Build Process + +- Use VS Developer Shell for correct toolset environment +- `cmake --build build --config Release --target cppwinrt` for cppwinrt.exe (or MSBuild: `msbuild cppwinrt\cppwinrt.vcxproj /p:Configuration=Release /p:Platform=x64`) +- NuGet tests: `msbuild test\nuget\NuGetTest.sln /p:Configuration=Release /p:Platform=x64` +- Module test projects require v145 toolset (VS 2026). Directory.Build.Props sets v143 by default — override with `v145` in Configuration PropertyGroup + +## Key Patterns + +### Prebuild Embedding +The `strings/*.h` files are embedded as string literals by the prebuild step. If you modify any `strings/*.h` file, you must delete `prebuild.exe` and rebuild the entire solution for changes to take effect. + +### Module Guard Macros +- `WINRT_IMPL_BUILD_MODULE` — Defined in .ixx global fragment. Makes `WINRT_EXPORT` expand to `export extern "C++"` and suppresses `#include` of dependencies +- `WINRT_IMPORT_MODULE` — Defined by consumers who import modules. Makes namespace headers no-op (types come from module import) +- `WINRT_EXPORT` — Empty in header mode, `export extern "C++"` in module mode + +### Generated Header Structure +Each namespace produces four header files: +- `impl/.0.h` — Forward declarations, ABIs, GUIDs, categories +- `impl/.1.h` — Interface definitions +- `impl/.2.h` — Delegates, structs, class implementations +- `.h` — Public API surface (consume definitions, class wrappers, operators) + +### Dependency Collection +When generating headers with `-modules`, writer.depends is inspected after each header to build a namespace dependency graph. This graph drives SCC detection and module import lists. + +## Common Gotchas + +- Module IFCs are NOT compatible across toolset versions — always clean rebuild when switching +- PCH and modules can coexist but PCH must NOT contain imports from WinRT headers when using modules, and winrt imports are preferred over textual inclusion +- `/ifcSearchDir` works for the module dependency scanner to find IFCs, but cross-component modules may need explicit `/reference "name=path.ifc"` flags +- `import std;` requires `BuildStlModules=true` +- Component modules (-opt) encode direct instantiation — they cannot be shared across DLL boundaries diff --git a/.github/instructions/modules.instructions.md b/.github/instructions/modules.instructions.md new file mode 100644 index 000000000..a4cf7a8d8 --- /dev/null +++ b/.github/instructions/modules.instructions.md @@ -0,0 +1,41 @@ +# C++/WinRT Modules — Agent Instructions + +## Module Architecture (v2 — Per-Namespace) + +Each WinRT namespace gets its own C++20 named module (`winrt.`). Base infrastructure is in `winrt_base` and `winrt_numerics`. + +### Code Generator Flow + +1. `-modules` flag enables .ixx generation in cppwinrt.exe +2. `-module_include`/`-module_exclude` filter which namespaces get modules +3. Headers are generated with dependency tracking (deps_ptr parameter) +4. Tarjan's SCC algorithm detects cyclic namespace groups +5. Standalone namespaces get individual .ixx; cyclic groups get consolidated SCC owner + re-export stubs + +### MSBuild Flow + +1. `CppWinRTBuildModule=true` adds `-modules` to cppwinrt.exe invocations +2. `CppWinRTAddModuleInterfaces` discovers `$(GeneratedFilesDir)winrt\*.ixx` and adds to ClCompile +3. `CppWinRTConsumeModule` metadata on ProjectReference controls per-reference IFC sharing +4. `CppWinRTResolveModuleReferences` calls `CppWinRTGetModuleOutputs` on tagged references +5. Platform projection suppresses `-modules` when consuming pre-built IFCs + +### Critical Invariants + +- Module guards are unconditional in codegen — `-modules` only controls .ixx generation +- Component modules use `-opt` (direct instantiation) — NEVER share across projects +- Reference and platform projection modules DON'T use `-opt` (activation factory) — safe for cross-project consumption +- SCC owner is alphabetically first namespace in the cycle +- All .ixx filenames use `winrt` prefix: `winrt.Windows.Foundation.ixx`, `winrt_base.ixx` + +### Testing Changes + +After modifying cppwinrt.exe code: +1. Rebuild cppwinrt.exe: `msbuild cppwinrt\cppwinrt.vcxproj /p:Configuration=Release /p:Platform=x64` +2. Run standalone test: build `test_cpp20_module` in main solution +3. Run NuGet tests: `msbuild test\nuget\NuGetTest.sln /p:Configuration=Release /p:Platform=x64` + +After modifying targets: +1. Clean NuGet test obj dirs +2. Build with `/v:normal` and check "Module providers:" diagnostic messages +3. Inspect `.rsp` files in `obj/` to verify correct `-modules` flag placement diff --git a/cppwinrt/component_writers.h b/cppwinrt/component_writers.h index 3966cad6c..af5626d14 100644 --- a/cppwinrt/component_writers.h +++ b/cppwinrt/component_writers.h @@ -136,7 +136,10 @@ namespace cppwinrt static void write_module_g_cpp(writer& w, std::vector const& classes) { - w.write_root_include("base"); + if (!settings.modules) + { + w.write_root_include("base"); + } auto format = R"(% bool __stdcall %_can_unload_now() noexcept { diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index b5e043b9d..ece13aa97 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -262,6 +262,26 @@ namespace cppwinrt writer w; write_preamble(w); write_pch(w); + + if (settings.modules) + { + // In module builds, import winrt_base instead of #include "winrt/base.h". + // The component's namespace module(s) provide type visibility. + w.write("\nimport winrt_base;\n"); + + // Collect all unique namespaces from the component classes + std::set namespaces; + for (auto&& type : classes) + { + namespaces.insert(std::string(type.TypeNamespace())); + } + for (auto&& ns : namespaces) + { + w.write("import winrt.%;\n", ns); + } + w.write("\n"); + } + write_module_g_cpp(w, classes); w.flush_to_file(settings.output_folder + "module.g.cpp"); } @@ -353,15 +373,15 @@ namespace cppwinrt writer w; write_pch(w); - // The .g.h handles its own imports, but the implementation .h - // needs the types available in scope, so we import them here. + if (settings.modules) { + // The .g.h handles its own imports, but the implementation .h + // needs the types available in scope, so we import them here. writer dep_scanner; dep_scanner.add_depends(type); write_component_g_h(dep_scanner, type); w.write("\n#define WINRT_IMPORT_MODULE\n"); - w.write("import winrt_base;\n"); for (auto&& depends : dep_scanner.depends) { w.write("import winrt.%;\n", depends.first); @@ -375,6 +395,11 @@ namespace cppwinrt // --- Per-namespace C++20 module interface unit (.ixx) writers --- + // Emits the common global module fragment used by all generated .ixx files. + // Defines WINRT_IMPL_BUILD_MODULE so generated headers switch WINRT_EXPORT + // to 'export extern "C++"' and suppress textual #includes of dependencies + // (dependencies arrive via module imports instead). + // Includes minimal headers needed for macros, intrinsics, and debug assertions. static void write_module_preamble(writer& w) { write_preamble(w); @@ -391,6 +416,15 @@ namespace cppwinrt w.write(format); } + // Emits $(out)/winrt/module.h + // This header provides macros that are needed inside module interface units + // but cannot cross module boundaries via 'import'. Each .ixx file includes + // this in its global module fragment. It provides: + // - WINRT_ASSERT/WINRT_VERIFY macros + // - WINRT_IMPL_COROUTINES detection + // - WINRT_IMPL_SHIM macro + // - WINRT_EXPORT (switches between empty and 'export extern "C++"') + // - MSVC warning suppressions for module-related diagnostics static void write_module_h() { writer w; @@ -572,6 +606,17 @@ export module winrt_numerics; w.flush_to_file(settings.output_folder + "winrt/winrt_numerics.ixx"); } + // Emits a per-namespace module interface unit for namespaces that are NOT + // part of a dependency cycle (standalone module). + // Output: $(out)/winrt/winrt..ixx (export module winrt.;) + // + // The generated .ixx: + // 1. Starts with the global module fragment (WINRT_IMPL_BUILD_MODULE, minimal includes) + // 2. Declares 'export module winrt.;' + // 3. Imports std and re-exports winrt_base + // 4. Imports each dependent namespace module (computed from type references in headers) + // 5. Includes the impl headers (*.0.h, *.1.h, *.2.h) and public header (.h) + // in the module purview, where WINRT_EXPORT causes declarations to be exported static void write_namespace_ixx( std::string_view const& ns, std::set const& deps, @@ -583,6 +628,26 @@ export module winrt_numerics; // Module declaration w.write("export module winrt.%;\n\n", ns); + // Document dependencies + w.write("// Module dependencies:\n"); + w.write("// - std\n"); + w.write("// - winrt_base (re-exported)\n"); + if (deps.empty()) + { + w.write("// - (no additional namespace imports)\n"); + } + else + { + for (auto& dep : deps) + { + if (module_namespaces.count(dep) || module_namespaces.empty()) + { + w.write("// - winrt.%\n", dep); + } + } + } + w.write("\n"); + // Import std and base w.write("import std;\n"); w.write("export import winrt_base;\n"); @@ -607,6 +672,22 @@ export module winrt_numerics; w.flush_to_file(settings.output_folder + "winrt/winrt." + std::string(ns) + ".ixx"); } + // Emits the SCC (Strongly Connected Component) owner module interface unit. + // When multiple namespaces form a dependency cycle, they cannot each have their + // own independent module (circular imports are illegal in C++20 modules). + // Instead, one namespace is chosen as the "owner" (alphabetically first in the SCC), + // and ALL cyclic namespaces' declarations are consolidated into this single module. + // The other namespaces in the SCC get thin re-export stubs (see write_namespace_reexport_ixx). + // + // Output: $(out)/winrt/winrt..ixx (export module winrt.;) + // + // The owner module: + // 1. Imports external dependencies (deps outside the SCC) + // 2. Forward-declares all projected types for ALL SCC namespaces before any + // impl headers — this breaks the type reference cycles + // 3. Includes impl headers in stable phase order: all *.0.h, then all *.1.h, + // then all *.2.h, then all public headers — preserving the original header + // layering while keeping SCC compilation deterministic static void write_namespace_scc_owner_ixx( cache const& c, std::string_view const& owner, @@ -618,6 +699,13 @@ export module winrt_numerics; write_module_preamble(w); // Module declaration (owner namespace) + w.write("// This module is an SCC owner (cycle breaker). The following namespaces\n"); + w.write("// form a dependency cycle and are consolidated into this single module:\n"); + for (auto& ns : scc_members) + { + w.write("// - %\n", ns); + } + w.write("// Other SCC namespaces are emitted as re-export stubs.\n\n"); w.write("export module winrt.%;\n\n", owner); // Import std and base @@ -635,7 +723,10 @@ export module winrt_numerics; w.write("\n"); - // Forward declarations for all SCC members (exported at module purview scope) + // Forward declarations for all projected types in this SCC. + // This is required because SCC members have cyclic type references, + // and generated headers suppress dependent #includes when WINRT_IMPL_BUILD_MODULE + // is defined. Forward declarations provide the names needed before definitions. for (auto& ns : scc_members) { auto found = c.namespaces().find(ns); @@ -654,7 +745,10 @@ export module winrt_numerics; w.write_each(members.contracts); } - // Include all SCC members' headers in phase order + // Include all SCC members' headers in stable phase order. + // All *.0.h (forward decls + ABIs), then all *.1.h (interfaces), + // then all *.2.h (delegates/structs/classes), then all public headers. + // This preserves the original header layering while keeping compilation deterministic. for (auto& ns : scc_members) { w.write("#include \"winrt/impl/%.0.h\"\n", ns); @@ -675,6 +769,10 @@ export module winrt_numerics; w.flush_to_file(settings.output_folder + "winrt/winrt." + std::string(owner) + ".ixx"); } + // Emits a thin re-export stub module for SCC non-owner namespaces. + // This allows 'import winrt.;' to work even though the actual declarations + // live in the SCC owner module. The stub simply re-exports the owner. + // Output: $(out)/winrt/winrt..ixx (export module winrt.; export import winrt.;) static void write_namespace_reexport_ixx( std::string_view const& ns, std::string_view const& owner) diff --git a/cppwinrt/main.cpp b/cppwinrt/main.cpp index 2ae755ae7..61b6cf894 100644 --- a/cppwinrt/main.cpp +++ b/cppwinrt/main.cpp @@ -525,7 +525,15 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder group.get(); - // Generate per-namespace module interface files (v2) + // Generate per-namespace module interface files (.ixx) + // + // Each projected namespace gets its own C++20 named module (winrt.). + // Namespaces that form dependency cycles are detected using Tarjan's SCC algorithm + // and consolidated: one namespace "owns" the SCC module (containing all declarations), + // while others get thin re-export stubs so 'import winrt.;' always works. + // + // Base infrastructure modules (winrt_base, winrt_numerics) are only generated + // for platform projection builds (-base flag). if (settings.modules) { if (settings.base) diff --git a/docs/modules-design.md b/docs/modules-design.md new file mode 100644 index 000000000..bb948ef6d --- /dev/null +++ b/docs/modules-design.md @@ -0,0 +1,190 @@ +# C++/WinRT Per-Namespace Modules: Design & Internals + +This document describes the design and implementation of per-namespace C++20 module support in C++/WinRT. It is intended for cppwinrt maintainers and contributors. + +## Architecture Overview + +The module system generates one C++20 named module per WinRT namespace. Each module encapsulates the same content as the traditional header files but exports declarations via `WINRT_EXPORT` in module purview. + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ winrt_numerics.ixx ── export module winrt_numerics; │ +│ winrt_base.ixx ── export module winrt_base; │ +│ export import winrt_numerics; │ +│ winrt.Windows.Foundation.ixx │ +│ ── export module winrt.Windows.Foundation; │ +│ import std; export import winrt_base; │ +│ import winrt.Windows.Foundation.Collections; │ +│ #include "winrt/impl/Windows.Foundation.0.h" │ +│ ... │ +│ #include "winrt/Windows.Foundation.h" │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Key Design Decisions + +### Unconditional Guards + +Module guards (`WINRT_IMPL_BUILD_MODULE`, `WINRT_IMPORT_MODULE`) are emitted unconditionally in generated projection headers — they are always present regardless of whether `-modules` was passed to cppwinrt.exe. The `-modules` flag controls `.ixx` generation and whether generated component files (`module.g.cpp`, stub `.cpp`) use module imports. This means: + +- Projection headers generated without `-modules` still work correctly when later compiled inside a module interface unit +- No regeneration of projection headers needed when switching between header and module consumption + +### WINRT_EXPORT Macro + +`WINRT_EXPORT` is defined in `module.h`: +- When `WINRT_IMPL_BUILD_MODULE` is defined (inside `.ixx` compilation): `export extern "C++"` +- Otherwise (header mode): empty + +All `namespace winrt::impl` and `namespace std` blocks use `WINRT_EXPORT` so they export correctly from modules. The `extern "C++"` wrapping enables include-before-import compatibility (same technique as MSVC STL). + +### Per-Namespace vs Monolithic + +Unlike the v1 approach (single `import winrt;`), v2 generates one module per namespace. This provides: + +- **Finer granularity**: Import only what you need +- **Better parallelism**: Independent modules compile in parallel +- **Component module support**: Component namespaces get their own modules + +The trade-off is handling dependency cycles between namespaces (see SCC below). + +This is enforced via the MSBuild `CppWinRTConsumeModule` metadata on ProjectReference — it only points at the platform module builder, not at component projects. + +## Code Generator Pipeline + +### Entry Point: `main.cpp` + +1. **Namespace enumeration**: First pass determines which namespaces are module-eligible using `module_filter` (from `-module_include`/`-module_exclude`) + +2. **Header generation**: Standard header generation with optional dependency collection. When `-modules` is active and a namespace is in the module, `write_namespace_*_h()` functions populate `ns_deps` sets via the `deps_ptr` parameter + +3. **Dependency graph construction**: After all headers are generated, `ns_deps_map` contains the full namespace dependency graph + +4. **SCC detection**: Tarjan's algorithm (`find_sccs()`) identifies strongly-connected components + +5. **Module generation**: For each SCC: + - Size 1: `write_namespace_ixx()` — standalone module + - Size > 1: `write_namespace_scc_owner_ixx()` for the owner (alphabetically first) + `write_namespace_reexport_ixx()` for others + +### CLI Options + +| Flag | Description | +|-|-| +| `-modules` | Enable `.ixx` generation | +| `-module_include ...` | Only generate modules for these namespace prefixes | +| `-module_exclude ...` | Skip these namespace prefixes | + +The `module_filter` is populated from these flags and checked against ALL cache namespaces (not just the projection filter). This is important for component builds where the platform namespaces are not being projected but their modules exist from a prior builder invocation. + +### Generated Files + +| File | When Generated | Purpose | +|-|-|-| +| `winrt/module.h` | Always with `-base` | Macros for module builds (WINRT_EXPORT, etc.) | +| `winrt/winrt_base.ixx` | `-modules -base` | Core types module | +| `winrt/winrt_numerics.ixx` | `-modules -base` | Numerics module | +| `winrt/winrt..ixx` | `-modules` | Per-namespace module | + +## SCC (Strongly Connected Components) + +### The Problem + +WinRT namespaces have cyclic dependencies. For example: +- `Windows.Foundation` depends on `Windows.Foundation.Collections` (via `IVector`, `IMap`, etc.) +- `Windows.Foundation.Collections` depends on `Windows.Foundation` (via `IAsyncOperation`, `Uri`, etc.) + +C++20 modules cannot have circular imports. If module A imports module B, then module B cannot import module A. + +### The Solution: SCC Consolidation + +Tarjan's algorithm identifies groups of namespaces that form dependency cycles. These groups (SCCs) are consolidated: + +1. **Owner selection**: The alphabetically first namespace in the SCC becomes the "owner" +2. **Owner module**: Contains ALL declarations from ALL SCC namespaces. Forward-declares all types first, then includes headers in phase order (all `*.0.h`, then `*.1.h`, then `*.2.h`, then public headers) +3. **Re-export stubs**: Other SCC members get thin `.ixx` files that just re-export the owner module + +This means `import winrt.Windows.Foundation;` and `import winrt.Windows.Foundation.Collections;` both work — they resolve to the same underlying module. + +### Example Generated Files + +**Owner** (`winrt.Windows.Foundation.ixx`): +```cpp +module; +#define WINRT_IMPL_BUILD_MODULE +#include "winrt/module.h" +// ... + +// This module is an SCC owner (cycle breaker). The following namespaces +// form a dependency cycle and are consolidated into this single module: +// - Windows.Foundation +// - Windows.Foundation.Collections +// Other SCC namespaces are emitted as re-export stubs. + +export module winrt.Windows.Foundation; + +import std; +export import winrt_base; + +// Forward declarations for all SCC namespaces... +// #include all impl headers in phase order... +``` + +**Re-export stub** (`winrt.Windows.Foundation.Collections.ixx`): +```cpp +// NOTE: This module does not define declarations of its own. +// It re-exports all declarations from the 'winrt.Windows.Foundation' module. +export module winrt.Windows.Foundation.Collections; +export import winrt.Windows.Foundation; +``` + +## MSBuild Integration + +### Targets Flow + +``` +CppWinRTResolveModuleReferences (resolves IFC paths from ProjectReference metadata) + ↓ +CppWinRTMakePlatformProjection (generates headers + .ixx for platform types) +CppWinRTMakeReferenceProjection (generates headers + .ixx for referenced WinMDs) +CppWinRTMakeComponentProjection (generates headers + .ixx for component types) + ↓ +CppWinRTAddModuleInterfaces (discovers .ixx files, adds to ClCompile items) + ↓ +FixupCLCompileOptions (MSVC module dependency scanner processes .ixx) + ↓ +ClCompile (compiles .ixx → .ifc + .obj) +``` + +### Key Properties + +- `CppWinRTBuildModule`: Enables `-modules` for all three projections (platform, reference, component), causing `.ixx` generation and compilation. +- `CppWinRTConsumeModule` (ProjectReference metadata): Per-reference opt-in for IFC consumption. When set, suppresses `-modules` on the platform projection so the consumer uses pre-built IFCs from the referenced project instead of generating its own. +- `_CppWinRTConsumesPlatformModules`: Internal property set by `CppWinRTResolveModuleReferences` when any ProjectReference has `CppWinRTConsumeModule=true`. Controls whether the platform projection receives `-modules`. + +### Cross-Project IFC Resolution + +MSVC's module dependency scanner uses `/ifcSearchDir` for within-project module resolution. For cross-project modules, the scanner generates explicit `/reference "module.name=path.ifc"` entries based on the dependency scan results. The `/ifcSearchDir` pointing to the builder's `$(IntDir)` allows the scanner to find the pre-built IFCs. + +## Dependency Collection + +During header generation, when `-modules` is active, each `write_namespace_*_h()` function receives a `deps_ptr` parameter. The writer's `w.depends` map is inspected to find referenced namespaces. Only namespaces that: +1. Exist in the cache +2. Have projected types +3. Are in the module namespace set (or set is empty) + +are added to the dependency set. Self-references are excluded. The union of dependencies from all four header files (`*.0.h`, `*.1.h`, `*.2.h`, `.h`) gives the complete dependency set for a namespace module. + +## Testing + +### test/test_cpp20_module/ (in-repo) + +Standalone test built by the main solution. Uses a PreBuildEvent to run cppwinrt.exe with `-modules -base -module_include "Windows.Foundation"`. Tests URI, events, collections, and coroutines. + +### test/nuget/ (NuGet integration) + +Multi-project solution: +- **TestModuleBuilder**: Static library that pre-builds platform modules +- **TestModuleComponent1**: Component DLL (Greeter class), consumes builder's modules +- **TestModuleComponent2**: Component DLL (GreeterGroup), depends on Component1 +- **TestModuleConsumerApp**: Console app, consumes builder + both components +- **TestModuleApp**: Single-project that builds and consumes its own modules diff --git a/nuget/Microsoft.Windows.CppWinRT.targets b/nuget/Microsoft.Windows.CppWinRT.targets index 4cb6d8d4f..84c54b830 100644 --- a/nuget/Microsoft.Windows.CppWinRT.targets +++ b/nuget/Microsoft.Windows.CppWinRT.targets @@ -817,9 +817,6 @@ $(XamlMetaDataProviderPch) $(_PCH) . - - . - -prefix -pch $(CppWinRTPrecompiledHeader) @@ -909,23 +906,18 @@ $(XamlMetaDataProviderPch) When CppWinRTBuildModule is true, discover generated .ixx module interface units and add them as ClCompile items for the C++ module build system to compile. BeforeTargets="FixupCLCompileOptions" ensures items are added before the module - dependency scanner processes them. Generated .cpp files also have PCH disabled - since they reference types via module imports. + dependency scanner processes them. --> - CompileAsCppModule true NotUsing - - NotUsing - @@ -948,8 +940,8 @@ $(XamlMetaDataProviderPch) diff --git a/nuget/modules.md b/nuget/modules.md new file mode 100644 index 000000000..aacd2f2c7 --- /dev/null +++ b/nuget/modules.md @@ -0,0 +1,150 @@ +# C++/WinRT C++20 Modules Guide + +## Overview + +C++/WinRT can generate per-namespace C++20 named modules (`.ixx` files) alongside the traditional projection headers. This allows you to write: + +```cpp +import winrt.Windows.Foundation; +``` + +instead of: + +```cpp +#include +``` + +Modules provide faster builds through pre-compiled module interfaces (IFCs) and better isolation of macro and declaration scopes. + +## Quick Start — Single Project + +For a project that builds and consumes its own modules: + +1. Set `CppWinRTBuildModule` to `true` in your project: + ```xml + + true + + ``` + +2. Optionally limit which namespaces get modules: + ```xml + + Windows.Foundation;Windows.Storage + + ``` + +3. Enable `BuildStlModules` for `import std;` support: + ```xml + + true + + ``` + +4. In your `.cpp` files: + ```cpp + import winrt.Windows.Foundation; + + int main() { + winrt::init_apartment(); + winrt::Windows::Foundation::Uri uri(L"https://example.com"); + } + ``` + +## Quick Start — Multi-Project (Recommended) + +For larger solutions, compile platform modules once in a dedicated "builder" static library, and share the pre-built IFCs with other projects. + +### Module Builder (static library) + +```xml + + StaticLibrary + true + Windows.Foundation + +``` + +### Consumer (exe or dll) + +```xml + + true + + + + true + + +``` + +The `CppWinRTConsumeModule` metadata on the ProjectReference tells the build system to: +- Use the builder's pre-built platform IFCs instead of compiling platform `.ixx` files again +- Skip generating platform `.ixx` files in the consumer's own projection + +### Component DLLs + +WinRT component projects can also use modules. Set `CppWinRTBuildModule=true` and all three projections (platform, reference, component) will generate `.ixx` files. + +```xml + + true + MyComponent + + + + true + + +``` + +### Consuming Components from Other Projects + +If project A references a component DLL from project B, project A builds its own reference projection modules from B's `.winmd`: + +```cpp +// These modules are built locally from the component's .winmd +import winrt.MyComponent; + +auto obj = winrt::MyComponent::MyClass(); +``` + +## MSBuild Properties + +| Property | Default | Description | +|-|-|-| +| `CppWinRTBuildModule` | false | Generate `.ixx` module interface units from projections | +| `CppWinRTModuleInclude` | (all) | Semicolon-delimited namespace prefixes to include in module generation | +| `CppWinRTModuleExclude` | (none) | Semicolon-delimited namespace prefixes to exclude from module generation | + +| ProjectReference Metadata | Default | Description | +|-|-|-| +| `CppWinRTConsumeModule` | false | Consume pre-built platform module IFCs from this project reference | + +## Module Names + +| Module | Contents | +|-|-| +| `winrt_base` | Core C++/WinRT types (`hstring`, `com_ptr`, `IUnknown`, etc.) — re-exported by all namespace modules | +| `winrt_numerics` | `Windows::Foundation::Numerics` types — re-exported by `winrt_base` | +| `winrt.` | Per-namespace projection (e.g., `winrt.Windows.Foundation`) | + +## Requirements + +- MSVC v145 toolset (Visual Studio 2026) or later recommended +- C++20 or later (`/std:c++20` or `/std:c++latest`) +- `BuildStlModules=true` for `import std;` support + +## Limitations + +- Component modules (built with `-opt`) contain optimizations only valid inside the originating DLL. They should not be shared across projects. +- Module IFCs are not compatible across toolset versions. All projects must use the same toolset. +- Cyclic namespace dependencies (e.g., `Windows.Foundation` ↔ `Windows.Foundation.Collections`) are handled automatically via SCC consolidation, but the resulting module name is chosen alphabetically. Adding new APIs could change SCC groupings. + +## Troubleshooting + +**"could not find module 'winrt.X'"** — Ensure the `.ixx` was generated (check `$(GeneratedFilesDir)winrt\`) and that `CppWinRTBuildModule=true` is set. For cross-project references, verify the builder project has `CppWinRTConsumeModule=true` on its ProjectReference and that the builder's IntDir is accessible via `/ifcSearchDir`. + +**Linker errors for component constructors** — You may be importing a component's internal module instead of building your own reference projection. Remove explicit `/reference` flags for component IFCs and ensure your project has `CppWinRTBuildModule=true` so it builds reference projection modules from the component's `.winmd`. + +**Redefinition errors** — Don't mix `#include` and `import` for the same namespace in the same translation unit. Use `import` consistently. diff --git a/nuget/readme.md b/nuget/readme.md index 9379aa716..c8c0b9537 100644 --- a/nuget/readme.md +++ b/nuget/readme.md @@ -70,6 +70,9 @@ C++/WinRT behavior can be customized with these project properties: | CppWinRTOptimized | true \| *false | Enables component projection [optimization features](https://kennykerr.ca/2019/06/07/cppwinrt-optimizing-components/) | | CppWinRTGenerateWindowsMetadata | true \| *false | Indicates whether this project produces Windows Metadata | | CppWinRTEnableDefaultPrivateFalse | true \| *false | Indicates whether this project uses C++/WinRT optimized default for copying binaries to the output directory | +| CppWinRTBuildModule | true \| *false | Generates per-namespace C++20 module interface units (.ixx) alongside projection headers | +| CppWinRTModuleInclude | namespace list | Semicolon-delimited namespaces to include in module generation (default: all) | +| CppWinRTModuleExclude | namespace list | Semicolon-delimited namespaces to exclude from module generation | \*Default value To customize common C++/WinRT project properties: @@ -132,6 +135,15 @@ void DerivedPage::InitializeComponent() } ``` +## C++20 Modules + +C++/WinRT supports C++20 named modules as an alternative to `#include`-based consumption. Instead of `#include `, you can write `import winrt.Windows.Foundation;`. See [modules.md](modules.md) for the full guide. + +| ProjectReference metadata | Description | +|-|-| +| CppWinRTConsumeModule | true \| *false | When set on a ProjectReference, consumes pre-built platform module IFCs from the referenced project | +\*Default value + ## Troubleshooting The msbuild verbosity level maps to msbuild message importance as follows: diff --git a/test/nuget/TestModuleApp/CustomDependencyObject.cpp b/test/nuget/TestModuleApp/CustomDependencyObject.cpp index 757286e9e..81631e387 100644 --- a/test/nuget/TestModuleApp/CustomDependencyObject.cpp +++ b/test/nuget/TestModuleApp/CustomDependencyObject.cpp @@ -1,7 +1,6 @@ #include "pch.h" #define WINRT_IMPORT_MODULE -import winrt_base; import winrt.Windows.Foundation; import winrt.Windows.UI.Xaml; diff --git a/test/nuget/TestModuleApp/ModuleTestHelper.cpp b/test/nuget/TestModuleApp/ModuleTestHelper.cpp index 4ab57c0fd..1e3d7bc1a 100644 --- a/test/nuget/TestModuleApp/ModuleTestHelper.cpp +++ b/test/nuget/TestModuleApp/ModuleTestHelper.cpp @@ -1,7 +1,6 @@ #include "pch.h" #define WINRT_IMPORT_MODULE -import winrt_base; import winrt.Windows.Foundation; #include "ModuleTestHelper.h" diff --git a/test/nuget/TestModuleApp/main.cpp b/test/nuget/TestModuleApp/main.cpp index a783e719c..b50f0f893 100644 --- a/test/nuget/TestModuleApp/main.cpp +++ b/test/nuget/TestModuleApp/main.cpp @@ -1,7 +1,6 @@ #include "pch.h" #define WINRT_IMPORT_MODULE -import winrt_base; import winrt.Windows.Foundation; import winrt.Windows.UI.Xaml; diff --git a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj index 690c32eb5..94d73ae90 100644 --- a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj +++ b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj @@ -77,7 +77,7 @@ - true + true diff --git a/test/nuget/TestModuleComponent2/GreeterGroup.cpp b/test/nuget/TestModuleComponent2/GreeterGroup.cpp index 6e4d5064a..4afeac3ac 100644 --- a/test/nuget/TestModuleComponent2/GreeterGroup.cpp +++ b/test/nuget/TestModuleComponent2/GreeterGroup.cpp @@ -1,7 +1,6 @@ #include "pch.h" #define WINRT_IMPORT_MODULE -import winrt_base; import winrt.Windows.Foundation; import winrt.TestModuleComponent1; diff --git a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj index b49279d54..0cffadfd7 100644 --- a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj +++ b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj @@ -77,7 +77,7 @@ - true + true diff --git a/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj index 11ff7af03..0efb5baa7 100644 --- a/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj +++ b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj @@ -58,7 +58,7 @@ - true + true diff --git a/test/nuget/TestModuleConsumerApp/main.cpp b/test/nuget/TestModuleConsumerApp/main.cpp index b3a4cab3f..80bcfff2c 100644 --- a/test/nuget/TestModuleConsumerApp/main.cpp +++ b/test/nuget/TestModuleConsumerApp/main.cpp @@ -1,6 +1,5 @@ #include "pch.h" -import winrt_base; import winrt.Windows.Foundation; import winrt.TestModuleComponent1; import winrt.TestModuleComponent2; diff --git a/test/test_cpp20_module/collections.cpp b/test/test_cpp20_module/collections.cpp index 64b8531a9..b80e123d2 100644 --- a/test/test_cpp20_module/collections.cpp +++ b/test/test_cpp20_module/collections.cpp @@ -1,6 +1,5 @@ #include "pch.h" -import winrt_base; import winrt.Windows.Foundation; using namespace winrt; diff --git a/test/test_cpp20_module/coroutines.cpp b/test/test_cpp20_module/coroutines.cpp index 7c509ac06..1553b1c6d 100644 --- a/test/test_cpp20_module/coroutines.cpp +++ b/test/test_cpp20_module/coroutines.cpp @@ -1,7 +1,6 @@ #include "pch.h" import std; -import winrt_base; import winrt.Windows.Foundation; using namespace winrt; diff --git a/test/test_cpp20_module/foundation.cpp b/test/test_cpp20_module/foundation.cpp index 152131a49..b5adbbe48 100644 --- a/test/test_cpp20_module/foundation.cpp +++ b/test/test_cpp20_module/foundation.cpp @@ -1,7 +1,6 @@ #include "pch.h" import std; -import winrt_base; import winrt.Windows.Foundation; using namespace winrt; diff --git a/test/test_cpp20_module/test_cpp20_module.vcxproj b/test/test_cpp20_module/test_cpp20_module.vcxproj index 4153ff1b1..f300d434a 100644 --- a/test/test_cpp20_module/test_cpp20_module.vcxproj +++ b/test/test_cpp20_module/test_cpp20_module.vcxproj @@ -80,22 +80,7 @@ BeforeTargets="FixupCLCompileOptions" ensures items are added before the module dependency scanner. --> - - CompileAsCppModule - true - NotUsing - - - CompileAsCppModule - true - NotUsing - - - CompileAsCppModule - true - NotUsing - - + CompileAsCppModule true NotUsing From f4073d3a062a4bf1ee8d98d3b9185d6cb681faca Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 20:50:06 -0700 Subject: [PATCH 16/29] Fix CI failures Co-authored-by: Copilot --- .github/workflows/ci.yml | 9 +++++++++ strings/base_fast_forward.h | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d660400a0..6c2a04353 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -379,6 +379,15 @@ jobs: $target_platform = "${{ matrix.arch }}" & "_build\$target_platform\$target_configuration\cppwinrt.exe" -in local -out _build\$target_platform\$target_configuration -verbose + - name: Remove module test projects on v143 + if: matrix.toolchain.platform_toolset == 'v143' + run: | + # Module test projects require v145 toolset + mv test\nuget\NugetTest.sln test\nuget\NugetTest.sln.orig + Get-Content test\nuget\NugetTest.sln.orig | + Where-Object { -not ($_ -match 'TestModule') } | + Set-Content test\nuget\NugetTest.sln + - name: Run nuget test run: | cmd /c "$env:VSDevCmd" "&" msbuild /m /clp:ForceConsoleColor "$env:msbuild_config_props" test\nuget\NugetTest.sln diff --git a/strings/base_fast_forward.h b/strings/base_fast_forward.h index c7cf6c3be..ab870a323 100644 --- a/strings/base_fast_forward.h +++ b/strings/base_fast_forward.h @@ -30,6 +30,10 @@ static_assert(WINRT_FAST_ABI_SIZE >= %); #pragma detect_mismatch("WINRT_FAST_ABI_SIZE", WINRT_IMPL_STRING(WINRT_FAST_ABI_SIZE)) +#ifndef WINRT_EXPORT +#define WINRT_EXPORT +#endif + WINRT_EXPORT namespace winrt::impl { // Thunk definitions are in arch-specific assembly sources From d5d18218732204ab25244868d548893e71f13b4c Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 21:21:20 -0700 Subject: [PATCH 17/29] Try for better std hygiene --- cppwinrt/file_writers.h | 19 ++++++++++++++++--- strings/base_collections_map.h | 2 +- strings/base_coroutine_foundation.h | 2 +- strings/base_coroutine_threadpool.h | 2 +- strings/base_std_hash.h | 2 +- test/nuget/TestModuleApp/main.cpp | 11 ++++++----- test/nuget/TestModuleConsumerApp/main.cpp | 11 ++++++----- 7 files changed, 32 insertions(+), 17 deletions(-) diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index ece13aa97..4d4850ffb 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -265,9 +265,11 @@ namespace cppwinrt if (settings.modules) { - // In module builds, import winrt_base instead of #include "winrt/base.h". - // The component's namespace module(s) provide type visibility. - w.write("\nimport winrt_base;\n"); + // In module builds, import std and winrt_base instead of #include "winrt/base.h". + // std is needed for std::wstring_view, std::equal, std::int32_t used in + // the activation factory lookup code. + w.write("\nimport std;\n"); + w.write("import winrt_base;\n"); // Collect all unique namespaces from the component classes std::set namespaces; @@ -473,6 +475,17 @@ namespace cppwinrt #endif #endif +// Template specializations in namespace std (hash, coroutine_traits) need extern "C++" +// linkage in module builds for proper merging with the std module, but must NOT be +// exported — exporting namespace std would make all of std transitively visible. +#ifndef WINRT_IMPL_STD_EXPORT +#ifdef WINRT_IMPL_BUILD_MODULE +#define WINRT_IMPL_STD_EXPORT extern "C++" +#else +#define WINRT_IMPL_STD_EXPORT +#endif +#endif + // pulls in large, hard-to-control legacy headers. In header builds we keep the // existing behavior, but in module builds it's provided by the winrt_numerics module. #if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) diff --git a/strings/base_collections_map.h b/strings/base_collections_map.h index a205050ce..5d0f9e746 100644 --- a/strings/base_collections_map.h +++ b/strings/base_collections_map.h @@ -116,7 +116,7 @@ WINRT_EXPORT namespace winrt } } -WINRT_EXPORT namespace std +WINRT_IMPL_STD_EXPORT namespace std { template struct tuple_size> diff --git a/strings/base_coroutine_foundation.h b/strings/base_coroutine_foundation.h index 9012cbce1..5cefad836 100644 --- a/strings/base_coroutine_foundation.h +++ b/strings/base_coroutine_foundation.h @@ -704,7 +704,7 @@ WINRT_EXPORT namespace winrt::impl }; } -WINRT_EXPORT namespace std +WINRT_IMPL_STD_EXPORT namespace std { template struct coroutine_traits diff --git a/strings/base_coroutine_threadpool.h b/strings/base_coroutine_threadpool.h index fdbd2c491..057d5b548 100644 --- a/strings/base_coroutine_threadpool.h +++ b/strings/base_coroutine_threadpool.h @@ -671,7 +671,7 @@ WINRT_EXPORT namespace winrt struct fire_and_forget {}; } -WINRT_EXPORT namespace std +WINRT_IMPL_STD_EXPORT namespace std { template struct coroutine_traits diff --git a/strings/base_std_hash.h b/strings/base_std_hash.h index 3d5e9d961..4777f8082 100644 --- a/strings/base_std_hash.h +++ b/strings/base_std_hash.h @@ -32,7 +32,7 @@ WINRT_EXPORT namespace winrt::impl }; } -WINRT_EXPORT namespace std +WINRT_IMPL_STD_EXPORT namespace std { template<> struct hash { diff --git a/test/nuget/TestModuleApp/main.cpp b/test/nuget/TestModuleApp/main.cpp index b50f0f893..907e4afed 100644 --- a/test/nuget/TestModuleApp/main.cpp +++ b/test/nuget/TestModuleApp/main.cpp @@ -1,6 +1,7 @@ #include "pch.h" #define WINRT_IMPORT_MODULE +import std; import winrt.Windows.Foundation; import winrt.Windows.UI.Xaml; @@ -17,10 +18,10 @@ int main() // Test ModuleTestHelper auto helper = TestModuleApp::ModuleTestHelper(); auto uri = helper.CreateUri(L"https://example.com"); - printf("URI: %ls\n", uri.AbsoluteUri().c_str()); + std::printf("URI: %ls\n", uri.AbsoluteUri().c_str()); auto str = helper.GetStringAsync().get(); - printf("Async: %ls\n", str.c_str()); + std::printf("Async: %ls\n", str.c_str()); // Test CustomDependencyObject (inherits from DependencyObject) // Note: DependencyObject requires XAML runtime, which isn't available in a console app. @@ -29,13 +30,13 @@ int main() { auto obj = winrt::make(); obj.Name(L"test"); - printf("Name: %ls\n", obj.Name().c_str()); + std::printf("Name: %ls\n", obj.Name().c_str()); } catch (winrt::hresult_error const& e) { - printf("CustomDependencyObject: expected runtime error (no XAML host): %ls\n", e.message().c_str()); + std::printf("CustomDependencyObject: expected runtime error (no XAML host): %ls\n", e.message().c_str()); } - printf("All module tests passed.\n"); + std::printf("All module tests passed.\n"); return 0; } diff --git a/test/nuget/TestModuleConsumerApp/main.cpp b/test/nuget/TestModuleConsumerApp/main.cpp index 80bcfff2c..da930703b 100644 --- a/test/nuget/TestModuleConsumerApp/main.cpp +++ b/test/nuget/TestModuleConsumerApp/main.cpp @@ -1,5 +1,6 @@ #include "pch.h" +import std; import winrt.Windows.Foundation; import winrt.TestModuleComponent1; import winrt.TestModuleComponent2; @@ -13,19 +14,19 @@ int main() // Platform types from pre-built modules Uri uri(L"https://example.com/consumer"); - printf("URI: %ls\n", uri.AbsoluteUri().c_str()); + std::printf("URI: %ls\n", uri.AbsoluteUri().c_str()); // Component1 auto greeter = TestModuleComponent1::Greeter(L"Modules"); - printf("Greet: %ls\n", greeter.Greet().c_str()); - printf("Homepage: %ls\n", greeter.Homepage().AbsoluteUri().c_str()); + std::printf("Greet: %ls\n", greeter.Greet().c_str()); + std::printf("Homepage: %ls\n", greeter.Homepage().AbsoluteUri().c_str()); // Component2 (depends on Component1) auto group = TestModuleComponent2::GreeterGroup(); group.Add(TestModuleComponent1::Greeter(L"Alice")); group.Add(TestModuleComponent1::Greeter(L"Bob")); - printf("GreetAll: %ls\n", group.GreetAll().c_str()); + std::printf("GreetAll: %ls\n", group.GreetAll().c_str()); - printf("All consumer tests passed.\n"); + std::printf("All consumer tests passed.\n"); return 0; } From 4d91f754f55e889ffbac765da404d9ab36e704a9 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 21:30:06 -0700 Subject: [PATCH 18/29] Address some PR feedback --- nuget/modules.md | 2 +- test/nuget/TestModuleComponent2/GreeterGroup.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nuget/modules.md b/nuget/modules.md index aacd2f2c7..08c09f9d5 100644 --- a/nuget/modules.md +++ b/nuget/modules.md @@ -143,7 +143,7 @@ auto obj = winrt::MyComponent::MyClass(); ## Troubleshooting -**"could not find module 'winrt.X'"** — Ensure the `.ixx` was generated (check `$(GeneratedFilesDir)winrt\`) and that `CppWinRTBuildModule=true` is set. For cross-project references, verify the builder project has `CppWinRTConsumeModule=true` on its ProjectReference and that the builder's IntDir is accessible via `/ifcSearchDir`. +**"could not find module 'winrt.X'"** — Ensure the `.ixx` was generated (check `$(GeneratedFilesDir)winrt\`) and that `CppWinRTBuildModule=true` is set. For cross-project references, verify the consuming project's `ProjectReference` to the builder has `CppWinRTConsumeModule=true`, and that the builder's `IntDir` is accessible via `/ifcSearchDir`. **Linker errors for component constructors** — You may be importing a component's internal module instead of building your own reference projection. Remove explicit `/reference` flags for component IFCs and ensure your project has `CppWinRTBuildModule=true` so it builds reference projection modules from the component's `.winmd`. diff --git a/test/nuget/TestModuleComponent2/GreeterGroup.cpp b/test/nuget/TestModuleComponent2/GreeterGroup.cpp index 4afeac3ac..7c6410b44 100644 --- a/test/nuget/TestModuleComponent2/GreeterGroup.cpp +++ b/test/nuget/TestModuleComponent2/GreeterGroup.cpp @@ -1,6 +1,7 @@ #include "pch.h" #define WINRT_IMPORT_MODULE +import std; import winrt.Windows.Foundation; import winrt.TestModuleComponent1; From 79eb574ee8464010ed0eadd1a633497aa8defe75 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Fri, 24 Apr 2026 21:42:56 -0700 Subject: [PATCH 19/29] Better automation of AdditionalBMIDirectories Co-authored-by: Copilot --- nuget/Microsoft.Windows.CppWinRT.targets | 11 +++++++++++ .../TestModuleComponent1/TestModuleComponent1.vcxproj | 2 +- .../TestModuleComponent2/TestModuleComponent2.vcxproj | 2 +- .../TestModuleConsumerApp.vcxproj | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/nuget/Microsoft.Windows.CppWinRT.targets b/nuget/Microsoft.Windows.CppWinRT.targets index 84c54b830..6fe7acc60 100644 --- a/nuget/Microsoft.Windows.CppWinRT.targets +++ b/nuget/Microsoft.Windows.CppWinRT.targets @@ -973,6 +973,17 @@ $(XamlMetaDataProviderPch) + + + + <_CppWinRTModuleIfcSearchDirs>@(_CppWinRTResolvedModuleRefs->'%(CppWinRTModuleIfcDir)') + + + + $(_CppWinRTModuleIfcSearchDirs);%(ClCompile.AdditionalBMIDirectories) + + diff --git a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj index 94d73ae90..140474416 100644 --- a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj +++ b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj @@ -42,7 +42,7 @@ stdcpplatest Level4 true - %(AdditionalOptions) /bigobj /ifcSearchDir "$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleBuilder" + %(AdditionalOptions) /bigobj true _WINRT_DLL;NOMINMAX;%(PreprocessorDefinitions) $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) diff --git a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj index 0cffadfd7..a39cdc7f9 100644 --- a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj +++ b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj @@ -42,7 +42,7 @@ stdcpplatest Level4 true - %(AdditionalOptions) /bigobj /ifcSearchDir "$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleBuilder" + %(AdditionalOptions) /bigobj true _WINRT_DLL;NOMINMAX;%(PreprocessorDefinitions) $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) diff --git a/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj index 0efb5baa7..6f4ff25fd 100644 --- a/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj +++ b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj @@ -39,7 +39,7 @@ stdcpplatest Level4 true - %(AdditionalOptions) /bigobj /ifcSearchDir "$(IntDirRoot)$(Configuration)\$(PlatformDirectoryName)\TestModuleBuilder" + %(AdditionalOptions) /bigobj true NOMINMAX;%(PreprocessorDefinitions) From b0d46567c6ca1e0d83e940fadafab0bff69ba8ff Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Sat, 25 Apr 2026 21:23:14 -0700 Subject: [PATCH 20/29] Missed fallback definition of WINRT_IMPL_STD_EXPORT --- strings/base_macros.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/strings/base_macros.h b/strings/base_macros.h index 7654a0e1c..5917b2124 100644 --- a/strings/base_macros.h +++ b/strings/base_macros.h @@ -31,6 +31,10 @@ #define WINRT_EXPORT #endif +#ifndef WINRT_IMPL_STD_EXPORT +#define WINRT_IMPL_STD_EXPORT +#endif + #if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) #ifdef WINRT_IMPL_NUMERICS #define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics From 392a1c38a34db9c9bd07c2fc5cbe9cb3c9d782cf Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Sun, 26 Apr 2026 01:14:37 -0700 Subject: [PATCH 21/29] Wrap base.h and extern the handler pointers --- cppwinrt/file_writers.h | 3 +++ strings/base_extern.h | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index 4d4850ffb..39ea5fe8a 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -9,6 +9,7 @@ namespace cppwinrt w.write(strings::base_version_odr, CPPWINRT_VERSION_STRING); { auto wrap_file_guard = wrap_open_file_guard(w, "BASE"); + w.write("#ifndef WINRT_IMPORT_MODULE\n"); { auto wrap_includes = wrap_module_aware_includes_guard(w, true); @@ -45,6 +46,8 @@ namespace cppwinrt w.write(strings::base_coroutine_threadpool); w.write(strings::base_natvis); w.write(strings::base_version); + + w.write("#endif // WINRT_IMPORT_MODULE\n"); } w.flush_to_file(settings.output_folder + "winrt/base.h"); } diff --git a/strings/base_extern.h b/strings/base_extern.h index 84e2943c4..a17908ac4 100644 --- a/strings/base_extern.h +++ b/strings/base_extern.h @@ -1,8 +1,11 @@ -__declspec(selectany) std::int32_t(__stdcall* winrt_to_hresult_handler)(void* address) noexcept {}; -__declspec(selectany) winrt::hstring(__stdcall* winrt_to_message_handler)(void* address) {}; -__declspec(selectany) void(__stdcall* winrt_throw_hresult_handler)(std::uint32_t lineNumber, char const* fileName, char const* functionName, void* returnAddress, winrt::hresult const result) noexcept {}; -__declspec(selectany) std::int32_t(__stdcall* winrt_activation_handler)(void* classId, winrt::guid const& iid, void** factory) noexcept {}; +// These global function pointers must use WINRT_EXPORT (which expands to +// 'export extern "C++"' in module builds) so that module and non-module TUs +// in the same binary share the same instances. +WINRT_EXPORT __declspec(selectany) std::int32_t(__stdcall* winrt_to_hresult_handler)(void* address) noexcept {}; +WINRT_EXPORT __declspec(selectany) winrt::hstring(__stdcall* winrt_to_message_handler)(void* address) {}; +WINRT_EXPORT __declspec(selectany) void(__stdcall* winrt_throw_hresult_handler)(std::uint32_t lineNumber, char const* fileName, char const* functionName, void* returnAddress, winrt::hresult const result) noexcept {}; +WINRT_EXPORT __declspec(selectany) std::int32_t(__stdcall* winrt_activation_handler)(void* classId, winrt::guid const& iid, void** factory) noexcept {}; #if defined(_MSC_VER) #ifdef _M_HYBRID From 9207cf9d76d4b17d9927fa0a4cc838568d6d3f97 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Mon, 27 Apr 2026 12:20:20 -0700 Subject: [PATCH 22/29] Uniform namespace filtering --- nuget/Microsoft.Windows.CppWinRT.targets | 4 ++-- nuget/modules.md | 11 ++++++++++- .../TestModuleComponent1/TestModuleComponent1.vcxproj | 1 - .../TestModuleComponent2/TestModuleComponent2.vcxproj | 1 - 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/nuget/Microsoft.Windows.CppWinRT.targets b/nuget/Microsoft.Windows.CppWinRT.targets index 6fe7acc60..a297258f3 100644 --- a/nuget/Microsoft.Windows.CppWinRT.targets +++ b/nuget/Microsoft.Windows.CppWinRT.targets @@ -736,7 +736,7 @@ $(XamlMetaDataProviderPch) <_CppwinrtRefRefs Include="@(CppWinRTPlatformWinMDReferences)"/> - <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) $(CppWinRTCommandModules) + <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) $(CppWinRTCommandModules) $(CppWinRTCommandModuleFilter) <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtRefInputs->'-in "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtRefRefs->'-ref "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) -out "$(GeneratedFilesDir)." @@ -842,7 +842,7 @@ $(XamlMetaDataProviderPch) <_CppwinrtCompRefs Include="@(CppWinRTPlatformWinMDReferences)"/> - <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) $(CppWinRTCommandModules) -overwrite -name $(RootNamespace) $(CppWinRTCommandPrecompiledHeader) $(CppWinRTCommandUsePrefixes) -comp "$(GeneratedFilesDir)sources" + <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) $(CppWinRTCommandModules) $(CppWinRTCommandModuleFilter) -overwrite -name $(RootNamespace) $(CppWinRTCommandPrecompiledHeader) $(CppWinRTCommandUsePrefixes) -comp "$(GeneratedFilesDir)sources" <_CppwinrtParameters Condition="'$(CppWinRTOptimized)'=='true'">$(_CppwinrtParameters) -opt <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtCompInputs->'-in "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtCompRefs->'-ref "%(WinMDPath)"', ' ') diff --git a/nuget/modules.md b/nuget/modules.md index 08c09f9d5..c72d8f86c 100644 --- a/nuget/modules.md +++ b/nuget/modules.md @@ -137,10 +137,19 @@ auto obj = winrt::MyComponent::MyClass(); ## Limitations -- Component modules (built with `-opt`) contain optimizations only valid inside the originating DLL. They should not be shared across projects. - Module IFCs are not compatible across toolset versions. All projects must use the same toolset. - Cyclic namespace dependencies (e.g., `Windows.Foundation` ↔ `Windows.Foundation.Collections`) are handled automatically via SCC consolidation, but the resulting module name is chosen alphabetically. Adding new APIs could change SCC groupings. +## Caution: Module Reuse Across Projects + +Pre-built IFCs (via `CppWinRTConsumeModule`) should only be shared when the builder and consumer use the same compilation context. In particular: + +- **Component modules are project-private.** A component projection built with `CppWinRTOptimized=true` generates modules that bypass activation factories for in-component type instantiation (`-opt`). If a consuming project accidentally imports these modules instead of building its own reference projection, the consumer will attempt direct instantiation across DLL boundaries, resulting in linker errors or incorrect behavior. Each project should build its own modules from the component's `.winmd` — do not tag component ProjectReferences with `CppWinRTConsumeModule`. + +- **`CppWinRTConsumeModule` is intended for platform module builders only.** The builder project is a dedicated static library whose sole purpose is compiling platform SDK modules. Its compilation flags (no `-opt`, no `-comp`) produce modules safe for any consumer. Only tag this builder's ProjectReference with `CppWinRTConsumeModule=true`. + +- **Module filter scope matters.** `CppWinRTModuleInclude` / `CppWinRTModuleExclude` applies to all three projections (platform, reference, component). If you set `CppWinRTModuleInclude=MyComponent`, only `MyComponent` namespaces will get `.ixx` files — platform and reference namespace modules will not be generated. Make sure your filter includes all namespaces you intend to import as modules, or use `CppWinRTConsumeModule` to get platform modules from a builder that was configured with the appropriate filter. + ## Troubleshooting **"could not find module 'winrt.X'"** — Ensure the `.ixx` was generated (check `$(GeneratedFilesDir)winrt\`) and that `CppWinRTBuildModule=true` is set. For cross-project references, verify the consuming project's `ProjectReference` to the builder has `CppWinRTConsumeModule=true`, and that the builder's `IntDir` is accessible via `/ifcSearchDir`. diff --git a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj index 140474416..a5b477d02 100644 --- a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj +++ b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj @@ -5,7 +5,6 @@ true true true - TestModuleComponent1 true {C1C2C3C4-1111-2222-3333-444455556666} TestModuleComponent1 diff --git a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj index a39cdc7f9..6210d38c0 100644 --- a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj +++ b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj @@ -5,7 +5,6 @@ true true true - TestModuleComponent2 true {C2C2C3C4-1111-2222-3333-444455556666} TestModuleComponent2 From 9a981661bf34d5dfe9c0fa9b403a5a630d2c99a4 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Mon, 27 Apr 2026 14:57:27 -0700 Subject: [PATCH 23/29] Write trailing comments for #endif --- cppwinrt/code_writers.h | 21 ++++++++++++++------- cppwinrt/file_writers.h | 22 +++++++++++----------- strings/base_fast_forward.h | 2 +- strings/base_macros.h | 4 ++-- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/cppwinrt/code_writers.h b/cppwinrt/code_writers.h index 6e29d7cad..e0741031e 100644 --- a/cppwinrt/code_writers.h +++ b/cppwinrt/code_writers.h @@ -5,9 +5,9 @@ namespace cppwinrt struct finish_with { writer& w; - void (*finisher)(writer&); + std::function finisher; - finish_with(writer& w, void (*finisher)(writer&)) : w(w), finisher(finisher) {} + finish_with(writer& w, std::function finisher) : w(w), finisher(std::move(finisher)) {} finish_with(finish_with const&)= delete; void operator=(finish_with const&) = delete; @@ -35,9 +35,16 @@ namespace cppwinrt } } - static void write_endif(writer& w) + static void write_endif(writer& w, std::string_view macro = {}) { - w.write("#endif\n"); + if (macro.empty()) + { + w.write("#endif\n"); + } + else + { + w.write("#endif // %\n", macro); + } } // When modules are enabled, wraps a block of #include directives in @@ -49,7 +56,7 @@ namespace cppwinrt if (modules_enabled) { w.write("#ifndef WINRT_IMPL_BUILD_MODULE\n"); - return { w, write_endif }; + return { w, [](writer& w) { write_endif(w, "WINRT_IMPL_BUILD_MODULE"); } }; } else { @@ -119,7 +126,7 @@ namespace cppwinrt w.write(format); - return { w, write_endif }; + return { w, [](writer& w) { write_endif(w, "WINRT_LEAN_AND_MEAN"); } }; } else { @@ -134,7 +141,7 @@ namespace cppwinrt w.write(format, macro); - return { w, write_endif }; + return { w, [macro = std::string(macro)](writer& w) { write_endif(w, macro); } }; } static void write_parent_depends(writer& w, cache const& c, std::string_view const& type_namespace) diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index 39ea5fe8a..6cf529ddd 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -314,7 +314,7 @@ namespace cppwinrt w.write_depends(depends.first); } - w.write("#endif\n"); + w.write("#endif // WINRT_IMPORT_MODULE\n"); auto filename = settings.output_folder + get_generated_component_filename(type) + ".g.h"; path folder = filename; @@ -450,11 +450,11 @@ namespace cppwinrt #define WINRT_VERIFY(expression) (void)(expression) #define WINRT_VERIFY_(result, expression) (void)(expression) -#endif +#endif // _DEBUG #if defined(__cpp_lib_coroutine) #define WINRT_IMPL_COROUTINES -#endif +#endif // __cpp_lib_coroutine #define WINRT_IMPL_SHIM(...) (*(abi_t<__VA_ARGS__>**)&static_cast<__VA_ARGS__ const&>(static_cast(*this))) @@ -468,15 +468,15 @@ namespace cppwinrt // C++ module warnings by /W4 #pragma warning(disable : 4499) #pragma warning(disable : 4630) -#endif +#endif // _MSC_VER #ifndef WINRT_EXPORT #ifdef WINRT_IMPL_BUILD_MODULE #define WINRT_EXPORT export extern "C++" #else #define WINRT_EXPORT -#endif -#endif +#endif // WINRT_IMPL_BUILD_MODULE +#endif // WINRT_EXPORT // Template specializations in namespace std (hash, coroutine_traits) need extern "C++" // linkage in module builds for proper merging with the std module, but must NOT be @@ -486,8 +486,8 @@ namespace cppwinrt #define WINRT_IMPL_STD_EXPORT extern "C++" #else #define WINRT_IMPL_STD_EXPORT -#endif -#endif +#endif // WINRT_IMPL_BUILD_MODULE +#endif // WINRT_IMPL_STD_EXPORT // pulls in large, hard-to-control legacy headers. In header builds we keep the // existing behavior, but in module builds it's provided by the winrt_numerics module. @@ -501,9 +501,9 @@ namespace cppwinrt #undef _WINDOWS_NUMERICS_NAMESPACE_ #undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ #undef _WINDOWS_NUMERICS_END_NAMESPACE_ -#endif +#endif // WINRT_IMPL_NUMERICS -#endif +#endif // !(WINRT_IMPL_BUILD_MODULE || WINRT_IMPORT_MODULE) #if defined(_MSC_VER) #define WINRT_IMPL_NOINLINE __declspec(noinline) @@ -559,7 +559,7 @@ typedef struct _GUID GUID; #define WINRT_IMPL_CONSTEVAL constexpr #endif -#endif +#endif // WINRT_MODULE_H )"; w.write(format); w.flush_to_file(settings.output_folder + "winrt/module.h"); diff --git a/strings/base_fast_forward.h b/strings/base_fast_forward.h index ab870a323..a73e70a04 100644 --- a/strings/base_fast_forward.h +++ b/strings/base_fast_forward.h @@ -32,7 +32,7 @@ static_assert(WINRT_FAST_ABI_SIZE >= %); #ifndef WINRT_EXPORT #define WINRT_EXPORT -#endif +#endif // WINRT_EXPORT WINRT_EXPORT namespace winrt::impl { diff --git a/strings/base_macros.h b/strings/base_macros.h index 5917b2124..1c19519ce 100644 --- a/strings/base_macros.h +++ b/strings/base_macros.h @@ -29,11 +29,11 @@ #ifndef WINRT_EXPORT #define WINRT_EXPORT -#endif +#endif // WINRT_EXPORT #ifndef WINRT_IMPL_STD_EXPORT #define WINRT_IMPL_STD_EXPORT -#endif +#endif // WINRT_IMPL_STD_EXPORT #if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) #ifdef WINRT_IMPL_NUMERICS From c4e1b06f4c6e96f055efa2f82ab5a48dc23f3ab2 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Mon, 27 Apr 2026 15:36:09 -0700 Subject: [PATCH 24/29] Clarify language version requirement --- nuget/modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuget/modules.md b/nuget/modules.md index c72d8f86c..f1a796361 100644 --- a/nuget/modules.md +++ b/nuget/modules.md @@ -132,7 +132,7 @@ auto obj = winrt::MyComponent::MyClass(); ## Requirements - MSVC v145 toolset (Visual Studio 2026) or later recommended -- C++20 or later (`/std:c++20` or `/std:c++latest`) +- C++20 or later (`/std:c++20` or newer) - `BuildStlModules=true` for `import std;` support ## Limitations From 5889c9ebea70b3929d8d2621bc37725c1d32e381 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Mon, 27 Apr 2026 15:43:53 -0700 Subject: [PATCH 25/29] Add source_location test Co-authored-by: Copilot --- test/test_cpp20_module/source_location.cpp | 13 +++++++++++++ test/test_cpp20_module/test_cpp20_module.vcxproj | 1 + 2 files changed, 14 insertions(+) create mode 100644 test/test_cpp20_module/source_location.cpp diff --git a/test/test_cpp20_module/source_location.cpp b/test/test_cpp20_module/source_location.cpp new file mode 100644 index 000000000..ebf5139c3 --- /dev/null +++ b/test/test_cpp20_module/source_location.cpp @@ -0,0 +1,13 @@ +#include "pch.h" + +import std; +import winrt.Windows.Foundation; + +TEST_CASE("module_source_location") +{ + // Verify that slim_source_location works across the module boundary + auto loc = winrt::impl::slim_source_location::current(); + REQUIRE(loc.line() > 0); + std::string_view file(loc.file_name()); + REQUIRE(file.find("source_location.cpp") != std::string_view::npos); +} diff --git a/test/test_cpp20_module/test_cpp20_module.vcxproj b/test/test_cpp20_module/test_cpp20_module.vcxproj index f300d434a..ad20d2e8a 100644 --- a/test/test_cpp20_module/test_cpp20_module.vcxproj +++ b/test/test_cpp20_module/test_cpp20_module.vcxproj @@ -99,6 +99,7 @@ + \ No newline at end of file From 2c83d42a0580bfb193c0a0f35de34b9bce2fb413 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Mon, 27 Apr 2026 15:44:18 -0700 Subject: [PATCH 26/29] Refactor/cleanup some string writers Co-authored-by: Copilot --- cppwinrt/file_writers.h | 186 +---------------------------- strings/base_module.h | 126 +++++++++++++++++++ strings/base_module_base_ixx.h | 17 +++ strings/base_module_ixx_preamble.h | 9 ++ strings/base_module_numerics_ixx.h | 22 ++++ 5 files changed, 178 insertions(+), 182 deletions(-) create mode 100644 strings/base_module.h create mode 100644 strings/base_module_base_ixx.h create mode 100644 strings/base_module_ixx_preamble.h create mode 100644 strings/base_module_numerics_ixx.h diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index 6cf529ddd..443460a1e 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -408,17 +408,7 @@ namespace cppwinrt static void write_module_preamble(writer& w) { write_preamble(w); - w.write("module;\n"); - w.write("#define WINRT_IMPL_BUILD_MODULE\n"); - auto format = R"(#include -#include -#include -#ifdef _DEBUG -#include -#endif -#include "winrt/module.h" -)"; - w.write(format); + w.write(strings::base_module_ixx_preamble); } // Emits $(out)/winrt/module.h @@ -434,134 +424,7 @@ namespace cppwinrt { writer w; write_preamble(w); - auto format = R"(#pragma once -#ifndef WINRT_MODULE_H -#define WINRT_MODULE_H - -#ifdef _DEBUG - -#define WINRT_ASSERT _ASSERTE -#define WINRT_VERIFY WINRT_ASSERT -#define WINRT_VERIFY_(result, expression) WINRT_ASSERT(result == expression) - -#else - -#define WINRT_ASSERT(expression) ((void)0) -#define WINRT_VERIFY(expression) (void)(expression) -#define WINRT_VERIFY_(result, expression) (void)(expression) - -#endif // _DEBUG - -#if defined(__cpp_lib_coroutine) -#define WINRT_IMPL_COROUTINES -#endif // __cpp_lib_coroutine - -#define WINRT_IMPL_SHIM(...) (*(abi_t<__VA_ARGS__>**)&static_cast<__VA_ARGS__ const&>(static_cast(*this))) - -#ifdef _MSC_VER -// Note: this is a workaround for a false-positive warning produced by the Visual C++ 15.9 compiler. -#pragma warning(disable : 5046) - -// Note: this is a workaround for a false-positive warning produced by the Visual C++ 16.3 compiler. -#pragma warning(disable : 4268) - -// C++ module warnings by /W4 -#pragma warning(disable : 4499) -#pragma warning(disable : 4630) -#endif // _MSC_VER - -#ifndef WINRT_EXPORT -#ifdef WINRT_IMPL_BUILD_MODULE -#define WINRT_EXPORT export extern "C++" -#else -#define WINRT_EXPORT -#endif // WINRT_IMPL_BUILD_MODULE -#endif // WINRT_EXPORT - -// Template specializations in namespace std (hash, coroutine_traits) need extern "C++" -// linkage in module builds for proper merging with the std module, but must NOT be -// exported — exporting namespace std would make all of std transitively visible. -#ifndef WINRT_IMPL_STD_EXPORT -#ifdef WINRT_IMPL_BUILD_MODULE -#define WINRT_IMPL_STD_EXPORT extern "C++" -#else -#define WINRT_IMPL_STD_EXPORT -#endif // WINRT_IMPL_BUILD_MODULE -#endif // WINRT_IMPL_STD_EXPORT - -// pulls in large, hard-to-control legacy headers. In header builds we keep the -// existing behavior, but in module builds it's provided by the winrt_numerics module. -#if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) - -#ifdef WINRT_IMPL_NUMERICS -#define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics -#define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ WINRT_EXPORT namespace winrt::Windows::Foundation::Numerics -#define _WINDOWS_NUMERICS_END_NAMESPACE_ -#include -#undef _WINDOWS_NUMERICS_NAMESPACE_ -#undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ -#undef _WINDOWS_NUMERICS_END_NAMESPACE_ -#endif // WINRT_IMPL_NUMERICS - -#endif // !(WINRT_IMPL_BUILD_MODULE || WINRT_IMPORT_MODULE) - -#if defined(_MSC_VER) -#define WINRT_IMPL_NOINLINE __declspec(noinline) -#elif defined(__GNUC__) -#define WINRT_IMPL_NOINLINE __attribute__((noinline)) -#else -#define WINRT_IMPL_NOINLINE -#endif - -#if defined(_MSC_VER) -#define WINRT_IMPL_EMPTY_BASES __declspec(empty_bases) -#else -#define WINRT_IMPL_EMPTY_BASES -#endif - -#if defined(_MSC_VER) -#define WINRT_IMPL_NOVTABLE __declspec(novtable) -#else -#define WINRT_IMPL_NOVTABLE -#endif - -#if defined(__clang__) && defined(__has_attribute) -#if __has_attribute(__lto_visibility_public__) -#define WINRT_IMPL_PUBLIC __attribute__((lto_visibility_public)) -#else -#define WINRT_IMPL_PUBLIC -#endif // __has_attribute(__lto_visibility_public__) -#else -#define WINRT_IMPL_PUBLIC -#endif - -#define WINRT_IMPL_ABI_DECL WINRT_IMPL_NOVTABLE WINRT_IMPL_PUBLIC - -#if defined(__clang__) -#define WINRT_IMPL_HAS_DECLSPEC_UUID __has_declspec_attribute(uuid) -#elif defined(_MSC_VER) -#define WINRT_IMPL_HAS_DECLSPEC_UUID 1 -#else -#define WINRT_IMPL_HAS_DECLSPEC_UUID 0 -#endif - -#if defined(__IUnknown_INTERFACE_DEFINED__) || defined(WINRT_ENABLE_LEGACY_COM) -#define WINRT_IMPL_IUNKNOWN_DEFINED -#else -// Forward declare so we can talk about it. -struct IUnknown; -typedef struct _GUID GUID; -#endif - -#if defined(__cpp_consteval) -#define WINRT_IMPL_CONSTEVAL consteval -#else -#define WINRT_IMPL_CONSTEVAL constexpr -#endif - -#endif // WINRT_MODULE_H -)"; - w.write(format); + w.write(strings::base_module); w.flush_to_file(settings.output_folder + "winrt/module.h"); } @@ -569,25 +432,7 @@ typedef struct _GUID GUID; { writer w; write_module_preamble(w); - auto format = R"( -#ifdef WINRT_ENABLE_LEGACY_COM -#include -#include -#undef GetCurrentTime -#endif - -export module winrt_base; - -import std; -export import winrt_numerics; - -#if __has_include() -#define WINRT_IMPL_NUMERICS -#endif - -#include "winrt/base.h" -)"; - w.write(format); + w.write(strings::base_module_base_ixx); w.flush_to_file(settings.output_folder + "winrt/winrt_base.ixx"); } @@ -595,30 +440,7 @@ export import winrt_numerics; { writer w; write_module_preamble(w); - auto format = R"( -export module winrt_numerics; - -#if __has_include() -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 5244) -#endif -#include - -#define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics -#define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ export extern "C++" namespace winrt::Windows::Foundation::Numerics -#define _WINDOWS_NUMERICS_END_NAMESPACE_ -#include -#undef _WINDOWS_NUMERICS_NAMESPACE_ -#undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ -#undef _WINDOWS_NUMERICS_END_NAMESPACE_ - -#ifdef _MSC_VER -#pragma warning(pop) -#endif -#endif -)"; - w.write(format); + w.write(strings::base_module_numerics_ixx); w.flush_to_file(settings.output_folder + "winrt/winrt_numerics.ixx"); } diff --git a/strings/base_module.h b/strings/base_module.h new file mode 100644 index 000000000..f8265ba56 --- /dev/null +++ b/strings/base_module.h @@ -0,0 +1,126 @@ +#pragma once +#ifndef WINRT_MODULE_H +#define WINRT_MODULE_H + +#ifdef _DEBUG + +#define WINRT_ASSERT _ASSERTE +#define WINRT_VERIFY WINRT_ASSERT +#define WINRT_VERIFY_(result, expression) WINRT_ASSERT(result == expression) + +#else + +#define WINRT_ASSERT(expression) ((void)0) +#define WINRT_VERIFY(expression) (void)(expression) +#define WINRT_VERIFY_(result, expression) (void)(expression) + +#endif // _DEBUG + +#if defined(__cpp_lib_coroutine) +#define WINRT_IMPL_COROUTINES +#endif // __cpp_lib_coroutine + +#define WINRT_IMPL_SHIM(...) (*(abi_t<__VA_ARGS__>**)&static_cast<__VA_ARGS__ const&>(static_cast(*this))) + +#ifdef _MSC_VER +// Note: this is a workaround for a false-positive warning produced by the Visual C++ 15.9 compiler. +#pragma warning(disable : 5046) + +// Note: this is a workaround for a false-positive warning produced by the Visual C++ 16.3 compiler. +#pragma warning(disable : 4268) + +// C++ module warnings by /W4 +#pragma warning(disable : 4499) +#pragma warning(disable : 4630) +#endif // _MSC_VER + +#ifndef WINRT_EXPORT +#ifdef WINRT_IMPL_BUILD_MODULE +#define WINRT_EXPORT export extern "C++" +#else +#define WINRT_EXPORT +#endif // WINRT_IMPL_BUILD_MODULE +#endif // WINRT_EXPORT + +// Template specializations in namespace std (hash, coroutine_traits) need extern "C++" +// linkage in module builds for proper merging with the std module, but must NOT be +// exported — exporting namespace std would make all of std transitively visible. +#ifndef WINRT_IMPL_STD_EXPORT +#ifdef WINRT_IMPL_BUILD_MODULE +#define WINRT_IMPL_STD_EXPORT extern "C++" +#else +#define WINRT_IMPL_STD_EXPORT +#endif // WINRT_IMPL_BUILD_MODULE +#endif // WINRT_IMPL_STD_EXPORT + +// pulls in large, hard-to-control legacy headers. In header builds we keep the +// existing behavior, but in module builds it's provided by the winrt_numerics module. +#if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) + +#ifdef WINRT_IMPL_NUMERICS +#define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ WINRT_EXPORT namespace winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_END_NAMESPACE_ +#include +#undef _WINDOWS_NUMERICS_NAMESPACE_ +#undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ +#undef _WINDOWS_NUMERICS_END_NAMESPACE_ +#endif // WINRT_IMPL_NUMERICS + +#endif // !(WINRT_IMPL_BUILD_MODULE || WINRT_IMPORT_MODULE) + +#if defined(_MSC_VER) +#define WINRT_IMPL_NOINLINE __declspec(noinline) +#elif defined(__GNUC__) +#define WINRT_IMPL_NOINLINE __attribute__((noinline)) +#else +#define WINRT_IMPL_NOINLINE +#endif + +#if defined(_MSC_VER) +#define WINRT_IMPL_EMPTY_BASES __declspec(empty_bases) +#else +#define WINRT_IMPL_EMPTY_BASES +#endif + +#if defined(_MSC_VER) +#define WINRT_IMPL_NOVTABLE __declspec(novtable) +#else +#define WINRT_IMPL_NOVTABLE +#endif + +#if defined(__clang__) && defined(__has_attribute) +#if __has_attribute(__lto_visibility_public__) +#define WINRT_IMPL_PUBLIC __attribute__((lto_visibility_public)) +#else +#define WINRT_IMPL_PUBLIC +#endif // __has_attribute(__lto_visibility_public__) +#else +#define WINRT_IMPL_PUBLIC +#endif + +#define WINRT_IMPL_ABI_DECL WINRT_IMPL_NOVTABLE WINRT_IMPL_PUBLIC + +#if defined(__clang__) +#define WINRT_IMPL_HAS_DECLSPEC_UUID __has_declspec_attribute(uuid) +#elif defined(_MSC_VER) +#define WINRT_IMPL_HAS_DECLSPEC_UUID 1 +#else +#define WINRT_IMPL_HAS_DECLSPEC_UUID 0 +#endif + +#if defined(__IUnknown_INTERFACE_DEFINED__) || defined(WINRT_ENABLE_LEGACY_COM) +#define WINRT_IMPL_IUNKNOWN_DEFINED +#else +// Forward declare so we can talk about it. +struct IUnknown; +typedef struct _GUID GUID; +#endif + +#if defined(__cpp_consteval) +#define WINRT_IMPL_CONSTEVAL consteval +#else +#define WINRT_IMPL_CONSTEVAL constexpr +#endif + +#endif // WINRT_MODULE_H diff --git a/strings/base_module_base_ixx.h b/strings/base_module_base_ixx.h new file mode 100644 index 000000000..83336d75d --- /dev/null +++ b/strings/base_module_base_ixx.h @@ -0,0 +1,17 @@ + +#ifdef WINRT_ENABLE_LEGACY_COM +#include +#include +#undef GetCurrentTime +#endif + +export module winrt_base; + +import std; +export import winrt_numerics; + +#if __has_include() +#define WINRT_IMPL_NUMERICS +#endif + +#include "winrt/base.h" diff --git a/strings/base_module_ixx_preamble.h b/strings/base_module_ixx_preamble.h new file mode 100644 index 000000000..49efb58b4 --- /dev/null +++ b/strings/base_module_ixx_preamble.h @@ -0,0 +1,9 @@ +module; +#define WINRT_IMPL_BUILD_MODULE +#include +#include +#include +#ifdef _DEBUG +#include +#endif // _DEBUG +#include "winrt/module.h" diff --git a/strings/base_module_numerics_ixx.h b/strings/base_module_numerics_ixx.h new file mode 100644 index 000000000..d25c09b7b --- /dev/null +++ b/strings/base_module_numerics_ixx.h @@ -0,0 +1,22 @@ + +export module winrt_numerics; + +#if __has_include() +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 5244) +#endif +#include + +#define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ export extern "C++" namespace winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_END_NAMESPACE_ +#include +#undef _WINDOWS_NUMERICS_NAMESPACE_ +#undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ +#undef _WINDOWS_NUMERICS_END_NAMESPACE_ + +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#endif From cb62bc77ce91163f3706179c83f092f6e42c7109 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Mon, 27 Apr 2026 15:48:46 -0700 Subject: [PATCH 27/29] Add arm64 configs and replace bogus project guids with real guids. Co-authored-by: Copilot --- test/nuget/NuGetTest.sln | 110 ++++++++++-------- .../nuget/TestModuleApp/TestModuleApp.vcxproj | 18 ++- .../TestModuleBuilder.vcxproj | 10 +- .../TestModuleComponent1.vcxproj | 10 +- .../TestModuleComponent2.vcxproj | 10 +- .../TestModuleConsumerApp.vcxproj | 10 +- 6 files changed, 114 insertions(+), 54 deletions(-) diff --git a/test/nuget/NuGetTest.sln b/test/nuget/NuGetTest.sln index 3f2210665..c836b2551 100644 --- a/test/nuget/NuGetTest.sln +++ b/test/nuget/NuGetTest.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.33516.290 @@ -47,19 +47,19 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConsoleApplication1", "Cons EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestProxyStub", "TestProxyStub\TestProxyStub.vcxproj", "{98E28FC8-2EB7-4544-9B6A-941462C6D3E2}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleApp", "TestModuleApp\TestModuleApp.vcxproj", "{F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleApp", "TestModuleApp\TestModuleApp.vcxproj", "{8679913F-D38D-468F-A8B7-75B187A7A8BC}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleBuilder", "TestModuleBuilder\TestModuleBuilder.vcxproj", "{A1B2C3D4-1111-2222-3333-444455556666}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleBuilder", "TestModuleBuilder\TestModuleBuilder.vcxproj", "{AEE91B86-AA17-4C22-B0C2-08B2C287E375}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleComponent1", "TestModuleComponent1\TestModuleComponent1.vcxproj", "{C1C2C3C4-1111-2222-3333-444455556666}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleComponent1", "TestModuleComponent1\TestModuleComponent1.vcxproj", "{F54D9A50-84D7-4953-8350-BEFE73CC36F6}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleComponent2", "TestModuleComponent2\TestModuleComponent2.vcxproj", "{C2C2C3C4-1111-2222-3333-444455556666}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleComponent2", "TestModuleComponent2\TestModuleComponent2.vcxproj", "{126E9412-E861-47C6-8684-C8F9BF32C0BD}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleConsumerApp", "TestModuleConsumerApp\TestModuleConsumerApp.vcxproj", "{B1B2C3D4-5555-6666-7777-888899990000}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleConsumerApp", "TestModuleConsumerApp\TestModuleConsumerApp.vcxproj", "{FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}" ProjectSection(ProjectDependencies) = postProject - {A1B2C3D4-1111-2222-3333-444455556666} = {A1B2C3D4-1111-2222-3333-444455556666} - {C1C2C3C4-1111-2222-3333-444455556666} = {C1C2C3C4-1111-2222-3333-444455556666} - {C2C2C3C4-1111-2222-3333-444455556666} = {C2C2C3C4-1111-2222-3333-444455556666} + {AEE91B86-AA17-4C22-B0C2-08B2C287E375} = {AEE91B86-AA17-4C22-B0C2-08B2C287E375} + {F54D9A50-84D7-4953-8350-BEFE73CC36F6} = {F54D9A50-84D7-4953-8350-BEFE73CC36F6} + {126E9412-E861-47C6-8684-C8F9BF32C0BD} = {126E9412-E861-47C6-8684-C8F9BF32C0BD} EndProjectSection EndProject Global @@ -294,46 +294,58 @@ Global {98E28FC8-2EB7-4544-9B6A-941462C6D3E2}.Release|x64.Build.0 = Release|x64 {98E28FC8-2EB7-4544-9B6A-941462C6D3E2}.Release|x86.ActiveCfg = Release|Win32 {98E28FC8-2EB7-4544-9B6A-941462C6D3E2}.Release|x86.Build.0 = Release|Win32 - {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Debug|ARM64.ActiveCfg = Debug|x64 - {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Debug|x64.ActiveCfg = Debug|x64 - {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Debug|x64.Build.0 = Debug|x64 - {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Debug|x86.ActiveCfg = Debug|x64 - {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Release|ARM64.ActiveCfg = Release|x64 - {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Release|x64.ActiveCfg = Release|x64 - {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Release|x64.Build.0 = Release|x64 - {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C}.Release|x86.ActiveCfg = Release|x64 - {A1B2C3D4-1111-2222-3333-444455556666}.Debug|ARM64.ActiveCfg = Release|x64 - {A1B2C3D4-1111-2222-3333-444455556666}.Debug|x64.ActiveCfg = Release|x64 - {A1B2C3D4-1111-2222-3333-444455556666}.Debug|x64.Build.0 = Release|x64 - {A1B2C3D4-1111-2222-3333-444455556666}.Debug|x86.ActiveCfg = Release|x64 - {A1B2C3D4-1111-2222-3333-444455556666}.Release|ARM64.ActiveCfg = Release|x64 - {A1B2C3D4-1111-2222-3333-444455556666}.Release|x64.ActiveCfg = Release|x64 - {A1B2C3D4-1111-2222-3333-444455556666}.Release|x64.Build.0 = Release|x64 - {A1B2C3D4-1111-2222-3333-444455556666}.Release|x86.ActiveCfg = Release|x64 - {C1C2C3C4-1111-2222-3333-444455556666}.Debug|ARM64.ActiveCfg = Release|x64 - {C1C2C3C4-1111-2222-3333-444455556666}.Debug|x64.ActiveCfg = Release|x64 - {C1C2C3C4-1111-2222-3333-444455556666}.Debug|x64.Build.0 = Release|x64 - {C1C2C3C4-1111-2222-3333-444455556666}.Debug|x86.ActiveCfg = Release|x64 - {C1C2C3C4-1111-2222-3333-444455556666}.Release|ARM64.ActiveCfg = Release|x64 - {C1C2C3C4-1111-2222-3333-444455556666}.Release|x64.ActiveCfg = Release|x64 - {C1C2C3C4-1111-2222-3333-444455556666}.Release|x64.Build.0 = Release|x64 - {C1C2C3C4-1111-2222-3333-444455556666}.Release|x86.ActiveCfg = Release|x64 - {C2C2C3C4-1111-2222-3333-444455556666}.Debug|ARM64.ActiveCfg = Release|x64 - {C2C2C3C4-1111-2222-3333-444455556666}.Debug|x64.ActiveCfg = Release|x64 - {C2C2C3C4-1111-2222-3333-444455556666}.Debug|x64.Build.0 = Release|x64 - {C2C2C3C4-1111-2222-3333-444455556666}.Debug|x86.ActiveCfg = Release|x64 - {C2C2C3C4-1111-2222-3333-444455556666}.Release|ARM64.ActiveCfg = Release|x64 - {C2C2C3C4-1111-2222-3333-444455556666}.Release|x64.ActiveCfg = Release|x64 - {C2C2C3C4-1111-2222-3333-444455556666}.Release|x64.Build.0 = Release|x64 - {C2C2C3C4-1111-2222-3333-444455556666}.Release|x86.ActiveCfg = Release|x64 - {B1B2C3D4-5555-6666-7777-888899990000}.Debug|ARM64.ActiveCfg = Release|x64 - {B1B2C3D4-5555-6666-7777-888899990000}.Debug|x64.ActiveCfg = Release|x64 - {B1B2C3D4-5555-6666-7777-888899990000}.Debug|x64.Build.0 = Release|x64 - {B1B2C3D4-5555-6666-7777-888899990000}.Debug|x86.ActiveCfg = Release|x64 - {B1B2C3D4-5555-6666-7777-888899990000}.Release|ARM64.ActiveCfg = Release|x64 - {B1B2C3D4-5555-6666-7777-888899990000}.Release|x64.ActiveCfg = Release|x64 - {B1B2C3D4-5555-6666-7777-888899990000}.Release|x64.Build.0 = Release|x64 - {B1B2C3D4-5555-6666-7777-888899990000}.Release|x86.ActiveCfg = Release|x64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|ARM64.Build.0 = Debug|ARM64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|x64.ActiveCfg = Debug|x64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|x64.Build.0 = Debug|x64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|x86.ActiveCfg = Debug|Win32 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|x86.Build.0 = Debug|Win32 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|ARM64.ActiveCfg = Release|ARM64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|ARM64.Build.0 = Release|ARM64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|x64.ActiveCfg = Release|x64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|x64.Build.0 = Release|x64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|x86.ActiveCfg = Release|Win32 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|x86.Build.0 = Release|Win32 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Debug|ARM64.ActiveCfg = Release|ARM64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Debug|x64.ActiveCfg = Release|x64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Debug|x64.Build.0 = Release|x64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Debug|x86.ActiveCfg = Release|Win32 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|ARM64.ActiveCfg = Release|ARM64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|ARM64.Build.0 = Release|ARM64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|x64.ActiveCfg = Release|x64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|x64.Build.0 = Release|x64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|x86.ActiveCfg = Release|Win32 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|x86.Build.0 = Release|Win32 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Debug|ARM64.ActiveCfg = Release|ARM64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Debug|x64.ActiveCfg = Release|x64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Debug|x64.Build.0 = Release|x64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Debug|x86.ActiveCfg = Release|Win32 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|ARM64.ActiveCfg = Release|ARM64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|ARM64.Build.0 = Release|ARM64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|x64.ActiveCfg = Release|x64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|x64.Build.0 = Release|x64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|x86.ActiveCfg = Release|Win32 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|x86.Build.0 = Release|Win32 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Debug|ARM64.ActiveCfg = Release|ARM64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Debug|x64.ActiveCfg = Release|x64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Debug|x64.Build.0 = Release|x64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Debug|x86.ActiveCfg = Release|Win32 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|ARM64.ActiveCfg = Release|ARM64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|ARM64.Build.0 = Release|ARM64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|x64.ActiveCfg = Release|x64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|x64.Build.0 = Release|x64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|x86.ActiveCfg = Release|Win32 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|x86.Build.0 = Release|Win32 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Debug|ARM64.ActiveCfg = Release|ARM64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Debug|x64.ActiveCfg = Release|x64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Debug|x64.Build.0 = Release|x64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Debug|x86.ActiveCfg = Release|Win32 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|ARM64.ActiveCfg = Release|ARM64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|ARM64.Build.0 = Release|ARM64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|x64.ActiveCfg = Release|x64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|x64.Build.0 = Release|x64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|x86.ActiveCfg = Release|Win32 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/test/nuget/TestModuleApp/TestModuleApp.vcxproj b/test/nuget/TestModuleApp/TestModuleApp.vcxproj index 452ff5334..522e0d911 100644 --- a/test/nuget/TestModuleApp/TestModuleApp.vcxproj +++ b/test/nuget/TestModuleApp/TestModuleApp.vcxproj @@ -7,7 +7,7 @@ true Windows;TestModuleApp true - {F3A7B9C1-2D4E-5F6A-8B0C-1D2E3F4A5B6C} + {8679913F-D38D-468F-A8B7-75B187A7A8BC} TestModuleApp TestModuleApp en-US @@ -15,14 +15,30 @@ + + Debug + Win32 + Debug x64 + + Debug + ARM64 + + + Release + Win32 + Release x64 + + Release + ARM64 + Application diff --git a/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj b/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj index 7c9190e15..86a3d7c89 100644 --- a/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj +++ b/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj @@ -5,7 +5,7 @@ true Windows.Foundation true - {A1B2C3D4-1111-2222-3333-444455556666} + {AEE91B86-AA17-4C22-B0C2-08B2C287E375} TestModuleBuilder TestModuleBuilder en-US @@ -13,10 +13,18 @@ + + Release + Win32 + Release x64 + + Release + ARM64 + StaticLibrary diff --git a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj index a5b477d02..669b25ff0 100644 --- a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj +++ b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj @@ -6,7 +6,7 @@ true true true - {C1C2C3C4-1111-2222-3333-444455556666} + {F54D9A50-84D7-4953-8350-BEFE73CC36F6} TestModuleComponent1 TestModuleComponent1 en-US @@ -14,10 +14,18 @@ + + Release + Win32 + Release x64 + + Release + ARM64 + DynamicLibrary diff --git a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj index 6210d38c0..ff4982965 100644 --- a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj +++ b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj @@ -6,7 +6,7 @@ true true true - {C2C2C3C4-1111-2222-3333-444455556666} + {126E9412-E861-47C6-8684-C8F9BF32C0BD} TestModuleComponent2 TestModuleComponent2 en-US @@ -14,10 +14,18 @@ + + Release + Win32 + Release x64 + + Release + ARM64 + DynamicLibrary diff --git a/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj index 6f4ff25fd..910586a0c 100644 --- a/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj +++ b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj @@ -4,7 +4,7 @@ true true - {B1B2C3D4-5555-6666-7777-888899990000} + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9} TestModuleConsumerApp TestModuleConsumerApp en-US @@ -12,10 +12,18 @@ + + Release + Win32 + Release x64 + + Release + ARM64 + Application From 054fdf5cacf184cceb685610f4b97b44fa95b674 Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Mon, 27 Apr 2026 16:42:09 -0700 Subject: [PATCH 28/29] More cleanup of strings. Collected common macros into base_macros.h, generates into winrt/macros.h Co-authored-by: Copilot --- .github/instructions/cppwinrt.instructions.md | 9 +- .github/instructions/modules.instructions.md | 5 +- cppwinrt/file_writers.h | 6 +- docs/modules-design.md | 6 +- strings/base_macros.h | 110 ++---------------- strings/base_module.h | 6 +- strings/base_module_ixx_preamble.h | 2 +- 7 files changed, 26 insertions(+), 118 deletions(-) diff --git a/.github/instructions/cppwinrt.instructions.md b/.github/instructions/cppwinrt.instructions.md index 375163b11..0dc621387 100644 --- a/.github/instructions/cppwinrt.instructions.md +++ b/.github/instructions/cppwinrt.instructions.md @@ -34,8 +34,9 @@ The `strings/*.h` files are embedded as string literals by the prebuild step. If ### Module Guard Macros - `WINRT_IMPL_BUILD_MODULE` — Defined in .ixx global fragment. Makes `WINRT_EXPORT` expand to `export extern "C++"` and suppresses `#include` of dependencies -- `WINRT_IMPORT_MODULE` — Defined by consumers who import modules. Makes namespace headers no-op (types come from module import) -- `WINRT_EXPORT` — Empty in header mode, `export extern "C++"` in module mode +- `WINRT_IMPORT_MODULE` — Defined by consumers who import modules. Makes namespace headers and base.h no-op (types come from module import) +- `WINRT_EXPORT` — Empty in header mode, `export extern "C++"` in module mode. Defined in `winrt/macros.h` +- `WINRT_IMPL_STD_EXPORT` — Empty in header mode, `extern "C++"` (without export) in module mode. Used for `namespace std` specializations ### Generated Header Structure Each namespace produces four header files: @@ -50,7 +51,7 @@ When generating headers with `-modules`, writer.depends is inspected after each ## Common Gotchas - Module IFCs are NOT compatible across toolset versions — always clean rebuild when switching -- PCH and modules can coexist but PCH must NOT contain imports from WinRT headers when using modules, and winrt imports are preferred over textual inclusion +- PCH and modules can coexist but PCH should NOT include winrt headers when using modules - `/ifcSearchDir` works for the module dependency scanner to find IFCs, but cross-component modules may need explicit `/reference "name=path.ifc"` flags - `import std;` requires `BuildStlModules=true` -- Component modules (-opt) encode direct instantiation — they cannot be shared across DLL boundaries +- Shared macros live in `strings/base_module.h` (generates `winrt/macros.h`). `base_macros.h` includes it. New macros should go in `base_module.h` only diff --git a/.github/instructions/modules.instructions.md b/.github/instructions/modules.instructions.md index a4cf7a8d8..3deee2955 100644 --- a/.github/instructions/modules.instructions.md +++ b/.github/instructions/modules.instructions.md @@ -22,11 +22,10 @@ Each WinRT namespace gets its own C++20 named module (`winrt.`). Base ### Critical Invariants -- Module guards are unconditional in codegen — `-modules` only controls .ixx generation -- Component modules use `-opt` (direct instantiation) — NEVER share across projects -- Reference and platform projection modules DON'T use `-opt` (activation factory) — safe for cross-project consumption +- Module guards are unconditional in codegen — `-modules` controls .ixx generation and component codegen (module.g.cpp, stub .cpp) - SCC owner is alphabetically first namespace in the cycle - All .ixx filenames use `winrt` prefix: `winrt.Windows.Foundation.ixx`, `winrt_base.ixx` +- Shared macros live in `strings/base_module.h` → generates `winrt/macros.h`. `base_macros.h` includes it via `#include "winrt/macros.h"` ### Testing Changes diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index 443460a1e..59a53e2ee 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -302,7 +302,7 @@ namespace cppwinrt write_include_guard(w); w.write("#ifdef WINRT_IMPORT_MODULE\n"); - w.write("#include \"winrt/module.h\"\n"); + w.write("#include \"winrt/macros.h\"\n"); for (auto&& depends : w.depends) { w.write("import winrt.%;\n", depends.first); @@ -411,7 +411,7 @@ namespace cppwinrt w.write(strings::base_module_ixx_preamble); } - // Emits $(out)/winrt/module.h + // Emits $(out)/winrt/macros.h // This header provides macros that are needed inside module interface units // but cannot cross module boundaries via 'import'. Each .ixx file includes // this in its global module fragment. It provides: @@ -425,7 +425,7 @@ namespace cppwinrt writer w; write_preamble(w); w.write(strings::base_module); - w.flush_to_file(settings.output_folder + "winrt/module.h"); + w.flush_to_file(settings.output_folder + "winrt/macros.h"); } static void write_base_ixx() diff --git a/docs/modules-design.md b/docs/modules-design.md index bb948ef6d..2748f6fca 100644 --- a/docs/modules-design.md +++ b/docs/modules-design.md @@ -32,7 +32,7 @@ Module guards (`WINRT_IMPL_BUILD_MODULE`, `WINRT_IMPORT_MODULE`) are emitted unc ### WINRT_EXPORT Macro -`WINRT_EXPORT` is defined in `module.h`: +`WINRT_EXPORT` is defined in `macros.h` (generated as `winrt/macros.h`): - When `WINRT_IMPL_BUILD_MODULE` is defined (inside `.ixx` compilation): `export extern "C++"` - Otherwise (header mode): empty @@ -80,7 +80,7 @@ The `module_filter` is populated from these flags and checked against ALL cache | File | When Generated | Purpose | |-|-|-| -| `winrt/module.h` | Always with `-base` | Macros for module builds (WINRT_EXPORT, etc.) | +| `winrt/macros.h` | Always with `-base` | Macros for module builds (WINRT_EXPORT, etc.) | | `winrt/winrt_base.ixx` | `-modules -base` | Core types module | | `winrt/winrt_numerics.ixx` | `-modules -base` | Numerics module | | `winrt/winrt..ixx` | `-modules` | Per-namespace module | @@ -111,7 +111,7 @@ This means `import winrt.Windows.Foundation;` and `import winrt.Windows.Foundati ```cpp module; #define WINRT_IMPL_BUILD_MODULE -#include "winrt/module.h" +#include "winrt/macros.h" // ... // This module is an SCC owner (cycle breaker). The following namespaces diff --git a/strings/base_macros.h b/strings/base_macros.h index 1c19519ce..00307e6b5 100644 --- a/strings/base_macros.h +++ b/strings/base_macros.h @@ -1,105 +1,13 @@ -#ifdef _DEBUG - -#define WINRT_ASSERT _ASSERTE -#define WINRT_VERIFY WINRT_ASSERT -#define WINRT_VERIFY_(result, expression) WINRT_ASSERT(result == expression) - -#else - -#define WINRT_ASSERT(expression) ((void)0) -#define WINRT_VERIFY(expression) (void)(expression) -#define WINRT_VERIFY_(result, expression) (void)(expression) - -#endif - -#define WINRT_IMPL_SHIM(...) (*(abi_t<__VA_ARGS__>**)&static_cast<__VA_ARGS__ const&>(static_cast(*this))) - -#ifdef _MSC_VER -// Note: this is a workaround for a false-positive warning produced by the Visual C++ 15.9 compiler. -#pragma warning(disable : 5046) - -// Note: this is a workaround for a false-positive warning produced by the Visual C++ 16.3 compiler. -#pragma warning(disable : 4268) -#endif - -#if defined(__cpp_lib_coroutine) -#define WINRT_IMPL_COROUTINES -#endif - -#ifndef WINRT_EXPORT -#define WINRT_EXPORT -#endif // WINRT_EXPORT - -#ifndef WINRT_IMPL_STD_EXPORT -#define WINRT_IMPL_STD_EXPORT -#endif // WINRT_IMPL_STD_EXPORT - -#if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) -#ifdef WINRT_IMPL_NUMERICS -#define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics -#define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ WINRT_EXPORT namespace winrt::Windows::Foundation::Numerics -#define _WINDOWS_NUMERICS_END_NAMESPACE_ -#include -#undef _WINDOWS_NUMERICS_NAMESPACE_ -#undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ -#undef _WINDOWS_NUMERICS_END_NAMESPACE_ -#endif -#endif - -#if defined(_MSC_VER) -#define WINRT_IMPL_NOINLINE __declspec(noinline) -#elif defined(__GNUC__) -#define WINRT_IMPL_NOINLINE __attribute__((noinline)) -#else -#define WINRT_IMPL_NOINLINE -#endif - -#if defined(_MSC_VER) -#define WINRT_IMPL_EMPTY_BASES __declspec(empty_bases) -#else -#define WINRT_IMPL_EMPTY_BASES -#endif - -#if defined(_MSC_VER) -#define WINRT_IMPL_NOVTABLE __declspec(novtable) -#else -#define WINRT_IMPL_NOVTABLE -#endif - -#if defined(__clang__) && defined(__has_attribute) -#if __has_attribute(__lto_visibility_public__) -#define WINRT_IMPL_PUBLIC __attribute__((lto_visibility_public)) -#else -#define WINRT_IMPL_PUBLIC -#endif // __has_attribute(__lto_visibility_public__) -#else -#define WINRT_IMPL_PUBLIC -#endif - -#define WINRT_IMPL_ABI_DECL WINRT_IMPL_NOVTABLE WINRT_IMPL_PUBLIC - -#if defined(__clang__) -#define WINRT_IMPL_HAS_DECLSPEC_UUID __has_declspec_attribute(uuid) -#elif defined(_MSC_VER) -#define WINRT_IMPL_HAS_DECLSPEC_UUID 1 -#else -#define WINRT_IMPL_HAS_DECLSPEC_UUID 0 -#endif - -#ifdef __IUnknown_INTERFACE_DEFINED__ -#define WINRT_IMPL_IUNKNOWN_DEFINED -#else -// Forward declare so we can talk about it. -struct IUnknown; -typedef struct _GUID GUID; -#endif - -#if defined(__cpp_consteval) -#define WINRT_IMPL_CONSTEVAL consteval -#else -#define WINRT_IMPL_CONSTEVAL constexpr -#endif +// module.h defines the core macros (WINRT_ASSERT, WINRT_EXPORT, WINRT_IMPL_*, etc.) +// that are shared between module builds and header builds. Including it here +// ensures a single source of truth for these macros. The #ifndef guards in +// module.h prevent double-definition when it was already included in the +// global module fragment of an .ixx file. +#include "winrt/macros.h" + +// The source_location support below is specific to base.h and is NOT needed +// in the global module fragment (module.h), so it remains here only. // The intrinsics (such as __builtin_FILE()) that power std::source_location are also used to power winrt:impl::slim_source_location. // The source location needs to be for the calling code, not cppwinrt itself, so that it is useful to developers building on top of diff --git a/strings/base_module.h b/strings/base_module.h index f8265ba56..1b95b5545 100644 --- a/strings/base_module.h +++ b/strings/base_module.h @@ -1,6 +1,6 @@ #pragma once -#ifndef WINRT_MODULE_H -#define WINRT_MODULE_H +#ifndef WINRT_MACROS_H +#define WINRT_MACROS_H #ifdef _DEBUG @@ -123,4 +123,4 @@ typedef struct _GUID GUID; #define WINRT_IMPL_CONSTEVAL constexpr #endif -#endif // WINRT_MODULE_H +#endif // WINRT_MACROS_H diff --git a/strings/base_module_ixx_preamble.h b/strings/base_module_ixx_preamble.h index 49efb58b4..5f10c1e0f 100644 --- a/strings/base_module_ixx_preamble.h +++ b/strings/base_module_ixx_preamble.h @@ -6,4 +6,4 @@ module; #ifdef _DEBUG #include #endif // _DEBUG -#include "winrt/module.h" +#include "winrt/macros.h" From 93225518d395ee183ca100eb34f54e939e9f2b2f Mon Sep 17 00:00:00 2001 From: Ryan Shepherd Date: Mon, 27 Apr 2026 17:38:07 -0700 Subject: [PATCH 29/29] More strings cleanup. Emit a canonical winrt/base_macros.h Co-authored-by: Copilot --- .github/instructions/cppwinrt.instructions.md | 5 +- cppwinrt/file_writers.h | 24 +- cppwinrt/main.cpp | 2 +- docs/modules-design.md | 6 +- natvis/pch.h | 1 + strings/base_macros.h | 221 ++++++++++-------- strings/base_module.h | 126 ---------- strings/base_module_ixx_preamble.h | 2 +- strings/base_source_location.h | 97 ++++++++ 9 files changed, 236 insertions(+), 248 deletions(-) delete mode 100644 strings/base_module.h create mode 100644 strings/base_source_location.h diff --git a/.github/instructions/cppwinrt.instructions.md b/.github/instructions/cppwinrt.instructions.md index 0dc621387..ba5d66212 100644 --- a/.github/instructions/cppwinrt.instructions.md +++ b/.github/instructions/cppwinrt.instructions.md @@ -35,7 +35,7 @@ The `strings/*.h` files are embedded as string literals by the prebuild step. If ### Module Guard Macros - `WINRT_IMPL_BUILD_MODULE` — Defined in .ixx global fragment. Makes `WINRT_EXPORT` expand to `export extern "C++"` and suppresses `#include` of dependencies - `WINRT_IMPORT_MODULE` — Defined by consumers who import modules. Makes namespace headers and base.h no-op (types come from module import) -- `WINRT_EXPORT` — Empty in header mode, `export extern "C++"` in module mode. Defined in `winrt/macros.h` +- `WINRT_EXPORT` — Empty in header mode, `export extern "C++"` in module mode. Defined in `winrt/base_macros.h` - `WINRT_IMPL_STD_EXPORT` — Empty in header mode, `extern "C++"` (without export) in module mode. Used for `namespace std` specializations ### Generated Header Structure @@ -54,4 +54,5 @@ When generating headers with `-modules`, writer.depends is inspected after each - PCH and modules can coexist but PCH should NOT include winrt headers when using modules - `/ifcSearchDir` works for the module dependency scanner to find IFCs, but cross-component modules may need explicit `/reference "name=path.ifc"` flags - `import std;` requires `BuildStlModules=true` -- Shared macros live in `strings/base_module.h` (generates `winrt/macros.h`). `base_macros.h` includes it. New macros should go in `base_module.h` only +- `strings/base_macros.h` is the single source of truth for shared macros (generated as `winrt/base_macros.h`). New macros go in `base_macros.h` only +- When adding, removing, or heavily refactoring `strings/*.h` files, always rebuild the natvis project (`natvis/cppwinrtvisualizer.sln`) to verify — it includes strings/*.h directly in its pch.h diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index 59a53e2ee..50774a20d 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -15,7 +15,8 @@ namespace cppwinrt auto wrap_includes = wrap_module_aware_includes_guard(w, true); w.write(strings::base_includes); } - w.write(strings::base_macros); + w.write("#include \"winrt/base_macros.h\"\n"); + w.write(strings::base_source_location); w.write(strings::base_types); w.write(strings::base_extern); w.write(strings::base_meta); @@ -302,7 +303,7 @@ namespace cppwinrt write_include_guard(w); w.write("#ifdef WINRT_IMPORT_MODULE\n"); - w.write("#include \"winrt/macros.h\"\n"); + w.write("#include \"winrt/base_macros.h\"\n"); for (auto&& depends : w.depends) { w.write("import winrt.%;\n", depends.first); @@ -411,21 +412,16 @@ namespace cppwinrt w.write(strings::base_module_ixx_preamble); } - // Emits $(out)/winrt/macros.h - // This header provides macros that are needed inside module interface units - // but cannot cross module boundaries via 'import'. Each .ixx file includes - // this in its global module fragment. It provides: - // - WINRT_ASSERT/WINRT_VERIFY macros - // - WINRT_IMPL_COROUTINES detection - // - WINRT_IMPL_SHIM macro - // - WINRT_EXPORT (switches between empty and 'export extern "C++"') - // - MSVC warning suppressions for module-related diagnostics - static void write_module_h() + // Emits $(out)/winrt/base_macros.h + // This header provides the core macros shared between header and module builds. + // In header builds, base.h includes base_macros.h inline (via the prebuild-embedded string). + // In module builds, each .ixx file includes this in its global module fragment. + static void write_macros_h() { writer w; write_preamble(w); - w.write(strings::base_module); - w.flush_to_file(settings.output_folder + "winrt/macros.h"); + w.write(strings::base_macros); + w.flush_to_file(settings.output_folder + "winrt/base_macros.h"); } static void write_base_ixx() diff --git a/cppwinrt/main.cpp b/cppwinrt/main.cpp index 61b6cf894..1ee84c418 100644 --- a/cppwinrt/main.cpp +++ b/cppwinrt/main.cpp @@ -490,7 +490,7 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder if (settings.base) { write_base_h(); - write_module_h(); + write_macros_h(); } if (settings.component) diff --git a/docs/modules-design.md b/docs/modules-design.md index 2748f6fca..8327231a7 100644 --- a/docs/modules-design.md +++ b/docs/modules-design.md @@ -32,7 +32,7 @@ Module guards (`WINRT_IMPL_BUILD_MODULE`, `WINRT_IMPORT_MODULE`) are emitted unc ### WINRT_EXPORT Macro -`WINRT_EXPORT` is defined in `macros.h` (generated as `winrt/macros.h`): +`WINRT_EXPORT` is defined in `base_macros.h` (generated as `winrt/base_macros.h`): - When `WINRT_IMPL_BUILD_MODULE` is defined (inside `.ixx` compilation): `export extern "C++"` - Otherwise (header mode): empty @@ -80,7 +80,7 @@ The `module_filter` is populated from these flags and checked against ALL cache | File | When Generated | Purpose | |-|-|-| -| `winrt/macros.h` | Always with `-base` | Macros for module builds (WINRT_EXPORT, etc.) | +| `winrt/base_macros.h` | Always with `-base` | Macros for module builds (WINRT_EXPORT, etc.) | | `winrt/winrt_base.ixx` | `-modules -base` | Core types module | | `winrt/winrt_numerics.ixx` | `-modules -base` | Numerics module | | `winrt/winrt..ixx` | `-modules` | Per-namespace module | @@ -111,7 +111,7 @@ This means `import winrt.Windows.Foundation;` and `import winrt.Windows.Foundati ```cpp module; #define WINRT_IMPL_BUILD_MODULE -#include "winrt/macros.h" +#include "winrt/base_macros.h" // ... // This module is an SCC owner (cycle breaker). The following namespaces diff --git a/natvis/pch.h b/natvis/pch.h index 95e561971..3de6807b3 100644 --- a/natvis/pch.h +++ b/natvis/pch.h @@ -12,6 +12,7 @@ #include #include "base_includes.h" #include "base_macros.h" +#include "base_source_location.h" #include "base_types.h" #include "base_extern.h" #include "base_meta.h" diff --git a/strings/base_macros.h b/strings/base_macros.h index 00307e6b5..8e74a9b8e 100644 --- a/strings/base_macros.h +++ b/strings/base_macros.h @@ -1,107 +1,126 @@ +#pragma once +#ifndef WINRT_BASE_MACROS_H +#define WINRT_BASE_MACROS_H + +#ifdef _DEBUG + +#define WINRT_ASSERT _ASSERTE +#define WINRT_VERIFY WINRT_ASSERT +#define WINRT_VERIFY_(result, expression) WINRT_ASSERT(result == expression) -// module.h defines the core macros (WINRT_ASSERT, WINRT_EXPORT, WINRT_IMPL_*, etc.) -// that are shared between module builds and header builds. Including it here -// ensures a single source of truth for these macros. The #ifndef guards in -// module.h prevent double-definition when it was already included in the -// global module fragment of an .ixx file. -#include "winrt/macros.h" - -// The source_location support below is specific to base.h and is NOT needed -// in the global module fragment (module.h), so it remains here only. - -// The intrinsics (such as __builtin_FILE()) that power std::source_location are also used to power winrt:impl::slim_source_location. -// The source location needs to be for the calling code, not cppwinrt itself, so that it is useful to developers building on top of -// this library. As a result any public-facing method that can result in an error needs a default-constructed slim_source_location -// argument so that it will collect source information from the application code that is calling into cppwinrt. -// -// We do not directly use std::source_location for two reasons: -// 1) std::source_location::function_name() is unavoidable. These strings end up in the final binary, bloating their size. This -// is particularly impactful for code bases that use templates heavily. Cases of 50% binary size growth have been observed. -// 2) std::source_location is a cpp20 feature, which is above the cpp17 feature floor for cppwinrt. By defining our own version -// we can avoid ODR violations in mixed cpp17/cpp20 builds. cpp17 callers will have an ABI that matches cpp20 callers (they -// will just not have useful file/line/function information). -// -// Some projects may decide that the source information binary size impact is not worth the benefit. Defining WINRT_NO_SOURCE_LOCATION -// will prevent this feature from activating. The slim_source_location type will be forwarded around but it will not include any -// nonzero data. That eliminates the biggest source of binary size overhead. -// -// To help with debugging the __builtin_FUNCTION() intrinsic will be used in _DEBUG builds. This will provide a bit more diagnostic -// value at the cost of binary size. The assumption is that binary size is considered less important in debug builds so this tradeoff -// is acceptable. -// -// The different behavior of the default parameters to winrt::impl::slim_source_location::current() is technically an ODR violation, -// albeit a minor one. There should be no serious consequence to this violation. In practice it means that mixing cpp17/cpp20, -// or mixing WINRT_NO_SOURCE_LOCATION with undefining it, will lead to inconsistent source location information. It may be missing -// when it is expected to be included, or it may be present when it is not expected. The behavior will depend on the linker's choice -// when there are multiple translation units with different options. This violation is tracked by https://github.com/microsoft/cppwinrt/issues/1445. - -#if !defined(__cpp_lib_source_location) || defined(WINRT_NO_SOURCE_LOCATION) -// Case1: cpp17 mode. The source_location intrinsics are not available. -// Case2: The caller has disabled source_location support. Ensure that there is no binary size overhead for line/file/function. -#define WINRT_IMPL_BUILTIN_LINE 0 -#define WINRT_IMPL_BUILTIN_FILE nullptr -#define WINRT_IMPL_BUILTIN_FUNCTION nullptr -#elif _DEBUG -// cpp20 _DEBUG builds include function information, which has a heavy binary size impact, in addition to file/line. -#define WINRT_IMPL_BUILTIN_LINE __builtin_LINE() -#define WINRT_IMPL_BUILTIN_FILE __builtin_FILE() -#define WINRT_IMPL_BUILTIN_FUNCTION __builtin_FUNCTION() #else -// Release builds in cpp20 mode get file and line information but NOT function information. Function strings -// quickly add up to a substantial binary size impact, especially when templates are heavily used. -#define WINRT_IMPL_BUILTIN_LINE __builtin_LINE() -#define WINRT_IMPL_BUILTIN_FILE __builtin_FILE() -#define WINRT_IMPL_BUILTIN_FUNCTION nullptr -#endif -WINRT_EXPORT namespace winrt::impl -{ - // This struct is intended to be highly similar to std::source_location. The key difference is - // that function_name is NOT included. Function names do not fold to identical strings and can - // have heavy binary size overhead when templates cause many permutations to exist. - struct slim_source_location - { - [[nodiscard]] static WINRT_IMPL_CONSTEVAL slim_source_location current( - const std::uint_least32_t line = WINRT_IMPL_BUILTIN_LINE, - const char* const file = WINRT_IMPL_BUILTIN_FILE, - const char* const function = WINRT_IMPL_BUILTIN_FUNCTION) noexcept - { - return slim_source_location{ line, file, function }; - } - - [[nodiscard]] constexpr slim_source_location() noexcept = default; - - [[nodiscard]] constexpr slim_source_location( - const std::uint_least32_t line, - const char* const file, - const char* const function) noexcept : - m_line(line), - m_file(file), - m_function(function) - {} - - [[nodiscard]] constexpr std::uint_least32_t line() const noexcept - { - return m_line; - } - - [[nodiscard]] constexpr const char* file_name() const noexcept - { - return m_file; - } - - [[nodiscard]] constexpr const char* function_name() const noexcept - { - return m_function; - } - - private: - const std::uint_least32_t m_line{}; - const char* const m_file{}; - const char* const m_function{}; - }; -} +#define WINRT_ASSERT(expression) ((void)0) +#define WINRT_VERIFY(expression) (void)(expression) +#define WINRT_VERIFY_(result, expression) (void)(expression) + +#endif // _DEBUG + +#if defined(__cpp_lib_coroutine) +#define WINRT_IMPL_COROUTINES +#endif // __cpp_lib_coroutine + +#define WINRT_IMPL_SHIM(...) (*(abi_t<__VA_ARGS__>**)&static_cast<__VA_ARGS__ const&>(static_cast(*this))) #ifdef _MSC_VER -#pragma detect_mismatch("WINRT_SOURCE_LOCATION", "slim") +// Note: this is a workaround for a false-positive warning produced by the Visual C++ 15.9 compiler. +#pragma warning(disable : 5046) + +// Note: this is a workaround for a false-positive warning produced by the Visual C++ 16.3 compiler. +#pragma warning(disable : 4268) + +// C++ module warnings by /W4 +#pragma warning(disable : 4499) +#pragma warning(disable : 4630) #endif // _MSC_VER + +#ifndef WINRT_EXPORT +#ifdef WINRT_IMPL_BUILD_MODULE +#define WINRT_EXPORT export extern "C++" +#else +#define WINRT_EXPORT +#endif // WINRT_IMPL_BUILD_MODULE +#endif // WINRT_EXPORT + +// Template specializations in namespace std (hash, coroutine_traits) need extern "C++" +// linkage in module builds for proper merging with the std module, but must NOT be +// exported — exporting namespace std would make all of std transitively visible. +#ifndef WINRT_IMPL_STD_EXPORT +#ifdef WINRT_IMPL_BUILD_MODULE +#define WINRT_IMPL_STD_EXPORT extern "C++" +#else +#define WINRT_IMPL_STD_EXPORT +#endif // WINRT_IMPL_BUILD_MODULE +#endif // WINRT_IMPL_STD_EXPORT + +// pulls in large, hard-to-control legacy headers. In header builds we keep the +// existing behavior, but in module builds it's provided by the winrt_numerics module. +#if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) + +#ifdef WINRT_IMPL_NUMERICS +#define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ WINRT_EXPORT namespace winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_END_NAMESPACE_ +#include +#undef _WINDOWS_NUMERICS_NAMESPACE_ +#undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ +#undef _WINDOWS_NUMERICS_END_NAMESPACE_ +#endif // WINRT_IMPL_NUMERICS + +#endif // !(WINRT_IMPL_BUILD_MODULE || WINRT_IMPORT_MODULE) + +#if defined(_MSC_VER) +#define WINRT_IMPL_NOINLINE __declspec(noinline) +#elif defined(__GNUC__) +#define WINRT_IMPL_NOINLINE __attribute__((noinline)) +#else +#define WINRT_IMPL_NOINLINE +#endif + +#if defined(_MSC_VER) +#define WINRT_IMPL_EMPTY_BASES __declspec(empty_bases) +#else +#define WINRT_IMPL_EMPTY_BASES +#endif + +#if defined(_MSC_VER) +#define WINRT_IMPL_NOVTABLE __declspec(novtable) +#else +#define WINRT_IMPL_NOVTABLE +#endif + +#if defined(__clang__) && defined(__has_attribute) +#if __has_attribute(__lto_visibility_public__) +#define WINRT_IMPL_PUBLIC __attribute__((lto_visibility_public)) +#else +#define WINRT_IMPL_PUBLIC +#endif // __has_attribute(__lto_visibility_public__) +#else +#define WINRT_IMPL_PUBLIC +#endif + +#define WINRT_IMPL_ABI_DECL WINRT_IMPL_NOVTABLE WINRT_IMPL_PUBLIC + +#if defined(__clang__) +#define WINRT_IMPL_HAS_DECLSPEC_UUID __has_declspec_attribute(uuid) +#elif defined(_MSC_VER) +#define WINRT_IMPL_HAS_DECLSPEC_UUID 1 +#else +#define WINRT_IMPL_HAS_DECLSPEC_UUID 0 +#endif + +#if defined(__IUnknown_INTERFACE_DEFINED__) || defined(WINRT_ENABLE_LEGACY_COM) +#define WINRT_IMPL_IUNKNOWN_DEFINED +#else +// Forward declare so we can talk about it. +struct IUnknown; +typedef struct _GUID GUID; +#endif + +#if defined(__cpp_consteval) +#define WINRT_IMPL_CONSTEVAL consteval +#else +#define WINRT_IMPL_CONSTEVAL constexpr +#endif + +#endif // WINRT_BASE_MACROS_H diff --git a/strings/base_module.h b/strings/base_module.h deleted file mode 100644 index 1b95b5545..000000000 --- a/strings/base_module.h +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once -#ifndef WINRT_MACROS_H -#define WINRT_MACROS_H - -#ifdef _DEBUG - -#define WINRT_ASSERT _ASSERTE -#define WINRT_VERIFY WINRT_ASSERT -#define WINRT_VERIFY_(result, expression) WINRT_ASSERT(result == expression) - -#else - -#define WINRT_ASSERT(expression) ((void)0) -#define WINRT_VERIFY(expression) (void)(expression) -#define WINRT_VERIFY_(result, expression) (void)(expression) - -#endif // _DEBUG - -#if defined(__cpp_lib_coroutine) -#define WINRT_IMPL_COROUTINES -#endif // __cpp_lib_coroutine - -#define WINRT_IMPL_SHIM(...) (*(abi_t<__VA_ARGS__>**)&static_cast<__VA_ARGS__ const&>(static_cast(*this))) - -#ifdef _MSC_VER -// Note: this is a workaround for a false-positive warning produced by the Visual C++ 15.9 compiler. -#pragma warning(disable : 5046) - -// Note: this is a workaround for a false-positive warning produced by the Visual C++ 16.3 compiler. -#pragma warning(disable : 4268) - -// C++ module warnings by /W4 -#pragma warning(disable : 4499) -#pragma warning(disable : 4630) -#endif // _MSC_VER - -#ifndef WINRT_EXPORT -#ifdef WINRT_IMPL_BUILD_MODULE -#define WINRT_EXPORT export extern "C++" -#else -#define WINRT_EXPORT -#endif // WINRT_IMPL_BUILD_MODULE -#endif // WINRT_EXPORT - -// Template specializations in namespace std (hash, coroutine_traits) need extern "C++" -// linkage in module builds for proper merging with the std module, but must NOT be -// exported — exporting namespace std would make all of std transitively visible. -#ifndef WINRT_IMPL_STD_EXPORT -#ifdef WINRT_IMPL_BUILD_MODULE -#define WINRT_IMPL_STD_EXPORT extern "C++" -#else -#define WINRT_IMPL_STD_EXPORT -#endif // WINRT_IMPL_BUILD_MODULE -#endif // WINRT_IMPL_STD_EXPORT - -// pulls in large, hard-to-control legacy headers. In header builds we keep the -// existing behavior, but in module builds it's provided by the winrt_numerics module. -#if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) - -#ifdef WINRT_IMPL_NUMERICS -#define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics -#define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ WINRT_EXPORT namespace winrt::Windows::Foundation::Numerics -#define _WINDOWS_NUMERICS_END_NAMESPACE_ -#include -#undef _WINDOWS_NUMERICS_NAMESPACE_ -#undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ -#undef _WINDOWS_NUMERICS_END_NAMESPACE_ -#endif // WINRT_IMPL_NUMERICS - -#endif // !(WINRT_IMPL_BUILD_MODULE || WINRT_IMPORT_MODULE) - -#if defined(_MSC_VER) -#define WINRT_IMPL_NOINLINE __declspec(noinline) -#elif defined(__GNUC__) -#define WINRT_IMPL_NOINLINE __attribute__((noinline)) -#else -#define WINRT_IMPL_NOINLINE -#endif - -#if defined(_MSC_VER) -#define WINRT_IMPL_EMPTY_BASES __declspec(empty_bases) -#else -#define WINRT_IMPL_EMPTY_BASES -#endif - -#if defined(_MSC_VER) -#define WINRT_IMPL_NOVTABLE __declspec(novtable) -#else -#define WINRT_IMPL_NOVTABLE -#endif - -#if defined(__clang__) && defined(__has_attribute) -#if __has_attribute(__lto_visibility_public__) -#define WINRT_IMPL_PUBLIC __attribute__((lto_visibility_public)) -#else -#define WINRT_IMPL_PUBLIC -#endif // __has_attribute(__lto_visibility_public__) -#else -#define WINRT_IMPL_PUBLIC -#endif - -#define WINRT_IMPL_ABI_DECL WINRT_IMPL_NOVTABLE WINRT_IMPL_PUBLIC - -#if defined(__clang__) -#define WINRT_IMPL_HAS_DECLSPEC_UUID __has_declspec_attribute(uuid) -#elif defined(_MSC_VER) -#define WINRT_IMPL_HAS_DECLSPEC_UUID 1 -#else -#define WINRT_IMPL_HAS_DECLSPEC_UUID 0 -#endif - -#if defined(__IUnknown_INTERFACE_DEFINED__) || defined(WINRT_ENABLE_LEGACY_COM) -#define WINRT_IMPL_IUNKNOWN_DEFINED -#else -// Forward declare so we can talk about it. -struct IUnknown; -typedef struct _GUID GUID; -#endif - -#if defined(__cpp_consteval) -#define WINRT_IMPL_CONSTEVAL consteval -#else -#define WINRT_IMPL_CONSTEVAL constexpr -#endif - -#endif // WINRT_MACROS_H diff --git a/strings/base_module_ixx_preamble.h b/strings/base_module_ixx_preamble.h index 5f10c1e0f..6ae29d059 100644 --- a/strings/base_module_ixx_preamble.h +++ b/strings/base_module_ixx_preamble.h @@ -6,4 +6,4 @@ module; #ifdef _DEBUG #include #endif // _DEBUG -#include "winrt/macros.h" +#include "winrt/base_macros.h" diff --git a/strings/base_source_location.h b/strings/base_source_location.h new file mode 100644 index 000000000..c0fd81f9f --- /dev/null +++ b/strings/base_source_location.h @@ -0,0 +1,97 @@ + +// The intrinsics (such as __builtin_FILE()) that power std::source_location are also used to power winrt:impl::slim_source_location. +// The source location needs to be for the calling code, not cppwinrt itself, so that it is useful to developers building on top of +// this library. As a result any public-facing method that can result in an error needs a default-constructed slim_source_location +// argument so that it will collect source information from the application code that is calling into cppwinrt. +// +// We do not directly use std::source_location for two reasons: +// 1) std::source_location::function_name() is unavoidable. These strings end up in the final binary, bloating their size. This +// is particularly impactful for code bases that use templates heavily. Cases of 50% binary size growth have been observed. +// 2) std::source_location is a cpp20 feature, which is above the cpp17 feature floor for cppwinrt. By defining our own version +// we can avoid ODR violations in mixed cpp17/cpp20 builds. cpp17 callers will have an ABI that matches cpp20 callers (they +// will just not have useful file/line/function information). +// +// Some projects may decide that the source information binary size impact is not worth the benefit. Defining WINRT_NO_SOURCE_LOCATION +// will prevent this feature from activating. The slim_source_location type will be forwarded around but it will not include any +// nonzero data. That eliminates the biggest source of binary size overhead. +// +// To help with debugging the __builtin_FUNCTION() intrinsic will be used in _DEBUG builds. This will provide a bit more diagnostic +// value at the cost of binary size. The assumption is that binary size is considered less important in debug builds so this tradeoff +// is acceptable. +// +// The different behavior of the default parameters to winrt::impl::slim_source_location::current() is technically an ODR violation, +// albeit a minor one. There should be no serious consequence to this violation. In practice it means that mixing cpp17/cpp20, +// or mixing WINRT_NO_SOURCE_LOCATION with undefining it, will lead to inconsistent source location information. It may be missing +// when it is expected to be included, or it may be present when it is not expected. The behavior will depend on the linker's choice +// when there are multiple translation units with different options. This violation is tracked by https://github.com/microsoft/cppwinrt/issues/1445. + +#if !defined(__cpp_lib_source_location) || defined(WINRT_NO_SOURCE_LOCATION) +// Case1: cpp17 mode. The source_location intrinsics are not available. +// Case2: The caller has disabled source_location support. Ensure that there is no binary size overhead for line/file/function. +#define WINRT_IMPL_BUILTIN_LINE 0 +#define WINRT_IMPL_BUILTIN_FILE nullptr +#define WINRT_IMPL_BUILTIN_FUNCTION nullptr +#elif _DEBUG +// cpp20 _DEBUG builds include function information, which has a heavy binary size impact, in addition to file/line. +#define WINRT_IMPL_BUILTIN_LINE __builtin_LINE() +#define WINRT_IMPL_BUILTIN_FILE __builtin_FILE() +#define WINRT_IMPL_BUILTIN_FUNCTION __builtin_FUNCTION() +#else +// Release builds in cpp20 mode get file and line information but NOT function information. Function strings +// quickly add up to a substantial binary size impact, especially when templates are heavily used. +#define WINRT_IMPL_BUILTIN_LINE __builtin_LINE() +#define WINRT_IMPL_BUILTIN_FILE __builtin_FILE() +#define WINRT_IMPL_BUILTIN_FUNCTION nullptr +#endif + +WINRT_EXPORT namespace winrt::impl +{ + // This struct is intended to be highly similar to std::source_location. The key difference is + // that function_name is NOT included. Function names do not fold to identical strings and can + // have heavy binary size overhead when templates cause many permutations to exist. + struct slim_source_location + { + [[nodiscard]] static WINRT_IMPL_CONSTEVAL slim_source_location current( + const std::uint_least32_t line = WINRT_IMPL_BUILTIN_LINE, + const char* const file = WINRT_IMPL_BUILTIN_FILE, + const char* const function = WINRT_IMPL_BUILTIN_FUNCTION) noexcept + { + return slim_source_location{ line, file, function }; + } + + [[nodiscard]] constexpr slim_source_location() noexcept = default; + + [[nodiscard]] constexpr slim_source_location( + const std::uint_least32_t line, + const char* const file, + const char* const function) noexcept : + m_line(line), + m_file(file), + m_function(function) + {} + + [[nodiscard]] constexpr std::uint_least32_t line() const noexcept + { + return m_line; + } + + [[nodiscard]] constexpr const char* file_name() const noexcept + { + return m_file; + } + + [[nodiscard]] constexpr const char* function_name() const noexcept + { + return m_function; + } + + private: + const std::uint_least32_t m_line{}; + const char* const m_file{}; + const char* const m_function{}; + }; +} + +#ifdef _MSC_VER +#pragma detect_mismatch("WINRT_SOURCE_LOCATION", "slim") +#endif // _MSC_VER