Skip to content

Commit 01f5f03

Browse files
committed
PR feedback: split includes into std and non-std, add base_module_macros.h, export extern handlers
1 parent 546491d commit 01f5f03

File tree

8 files changed

+186
-157
lines changed

8 files changed

+186
-157
lines changed

cppwinrt/file_writers.h

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ namespace cppwinrt
1111
auto wrap_file_guard = wrap_open_file_guard(w, "BASE");
1212

1313
w.write(strings::base_includes);
14+
w.write(R"(
15+
#if defined(__cpp_lib_modules) && defined(WINRT_IMPORT_STD)
16+
import std;
17+
#else
18+
)");
19+
w.write(strings::base_std_includes);
20+
w.write(R"(#endif
21+
)");
1422
w.write(strings::base_macros);
1523
w.write(strings::base_types);
1624
w.write(strings::base_extern);
@@ -55,45 +63,14 @@ namespace cppwinrt
5563
{
5664
writer w;
5765
write_preamble(w);
58-
w.write(R"(#pragma once
59-
60-
// This header provides the preprocessor macros needed by generated C++/WinRT
61-
// headers when the full base.h is not #included (e.g., when using 'import winrt;').
62-
// Macros are not exported across C++20 module boundaries, so this lightweight
63-
// header must be included textually.
66+
auto format = R"(#pragma once
6467
6568
#ifndef CPPWINRT_VERSION
6669
#define CPPWINRT_VERSION "%"
6770
#endif
68-
69-
#ifndef WINRT_EXPORT
70-
#define WINRT_EXPORT
71-
#endif
72-
73-
#if defined(_MSC_VER)
74-
#define WINRT_IMPL_EMPTY_BASES __declspec(empty_bases)
75-
#else
76-
#define WINRT_IMPL_EMPTY_BASES
77-
#endif
78-
79-
#if defined(_MSC_VER)
80-
#define WINRT_IMPL_NOVTABLE __declspec(novtable)
81-
#else
82-
#define WINRT_IMPL_NOVTABLE
83-
#endif
84-
85-
#if defined(__clang__) && defined(__has_attribute)
86-
#if __has_attribute(__lto_visibility_public__)
87-
#define WINRT_IMPL_PUBLIC __attribute__((lto_visibility_public))
88-
#else
89-
#define WINRT_IMPL_PUBLIC
90-
#endif
91-
#else
92-
#define WINRT_IMPL_PUBLIC
93-
#endif
94-
95-
#define WINRT_IMPL_ABI_DECL WINRT_IMPL_NOVTABLE WINRT_IMPL_PUBLIC
96-
)", CPPWINRT_VERSION_STRING);
71+
)";
72+
w.write(format, CPPWINRT_VERSION_STRING);
73+
w.write(strings::base_module_macros);
9774
w.flush_to_file(settings.output_folder + "winrt/base_macros.h");
9875
}
9976

cppwinrt/main.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -353,11 +353,8 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder
353353
writer ixx;
354354
write_preamble(ixx);
355355
ixx.write("module;\n");
356-
// In the global module fragment, 'import' is not allowed.
357-
// Suppress the 'import std;' path in base_includes so we get raw #includes.
358-
ixx.write("#define WINRT_IMPL_GLOBAL_MODULE_FRAGMENT\n");
359356
ixx.write(strings::base_includes);
360-
ixx.write("#undef WINRT_IMPL_GLOBAL_MODULE_FRAGMENT\n");
357+
ixx.write(strings::base_std_includes);
361358
ixx.write("\nexport module winrt;\n#define WINRT_EXPORT export\n\n");
362359

363360
for (auto&&[ns, members] : c.namespaces())

docs/modules-internals.md

Lines changed: 83 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ them, and the interactions between the various moving pieces.
88

99
- [File Generation Pipeline](#file-generation-pipeline)
1010
- [winrt.ixx Generation](#winrtixx-generation)
11-
- [The Global Module Fragment Problem](#the-global-module-fragment-problem)
11+
- [Split Standard Library Includes](#split-standard-library-includes)
1212
- [base_macros.h: Why Macros Need Special Handling](#base_macrosh-why-macros-need-special-handling)
1313
- [WINRT_IMPL_SKIP_INCLUDES: Guarded Cross-Namespace Includes](#winrt_impl_skip_includes-guarded-cross-namespace-includes)
14+
- [WINRT_EXPORT on Extern Handlers](#winrt_export-on-extern-handlers)
1415
- [The -module Flag: Component Code Generation](#the--module-flag-component-code-generation)
1516
- [The Combined-ixx Approach](#the-combined-ixx-approach)
1617
- [The windowsnumerics.impl.h Warning](#the-windowsnumericsh-warning)
@@ -49,9 +50,8 @@ The ixx is built by writing directly to a `writer ixx` object:
4950
writer ixx;
5051
write_preamble(ixx);
5152
ixx.write("module;\n"); // global module fragment
52-
ixx.write("#define WINRT_IMPL_GLOBAL_MODULE_FRAGMENT\n");
53-
ixx.write(strings::base_includes); // standard library #includes
54-
ixx.write("#undef WINRT_IMPL_GLOBAL_MODULE_FRAGMENT\n");
53+
ixx.write(strings::base_includes); // platform includes (intrin.h, version, directxmath)
54+
ixx.write(strings::base_std_includes); // standard library #includes
5555
ixx.write("\nexport module winrt;\n#define WINRT_EXPORT export\n\n");
5656

5757
for (auto ns : namespaces)
@@ -61,46 +61,51 @@ ixx.flush_to_file("winrt/winrt.ixx");
6161
```
6262
6363
Key points:
64-
- `strings::base_includes` is the *literal content* of `base_includes.h` (embedded
65-
at compile time by the prebuild step). It's not an `#include` directive — it's
66-
the raw text of the standard library includes.
67-
- `WINRT_EXPORT` is defined as `export` inside the module purview. Every namespace
68-
declaration in the generated headers uses `WINRT_EXPORT namespace winrt::...` so
69-
types are properly exported.
70-
- The namespace headers are `#include`d inside the module purview, making all their
71-
content (types, template specializations) part of the module.
64+
- `strings::base_includes` contains platform-specific headers (`<intrin.h>`,
65+
`<version>`, `<directxmath.h>`) — NOT standard library headers.
66+
- `strings::base_std_includes` contains all standard library `#include`
67+
directives (`<algorithm>`, `<string>`, etc.). These are always written
68+
as raw `#include`s in the global module fragment where `import` is prohibited.
69+
- `WINRT_EXPORT` is defined as `export` inside the module purview.
70+
- The namespace headers are `#include`d inside the module purview, making all
71+
their content (types, template specializations) part of the module.
7272
73-
## The Global Module Fragment Problem
73+
## Split Standard Library Includes
7474
75-
**Problem**: The C++ standard prohibits `import` declarations in the global module
76-
fragment (the section between `module;` and `export module winrt;`).
75+
**Problem**: The C++ standard prohibits `import` declarations in the global
76+
module fragment (the section between `module;` and `export module winrt;`).
77+
The ixx needs raw `#include` directives there, but `base.h` consumers may
78+
want `import std;` instead.
7779
78-
**Context**: `base_includes.h` conditionally emits `import std;` when
79-
`WINRT_IMPORT_STD` is defined. If this triggers in the global module fragment,
80-
compilation fails.
80+
**Solution**: The standard library includes are split into a separate file:
8181
82-
**Solution**: The ixx writer wraps the base_includes content with a guard macro:
82+
| File | Content | Used by |
83+
|------|---------|--------|
84+
| `strings/base_includes.h` | Platform headers: `<intrin.h>`, `<version>`, `<directxmath.h>` | Both ixx and base.h |
85+
| `strings/base_std_includes.h` | Standard library: `<algorithm>`, `<string>`, `<coroutine>`, etc. | ixx (always), base.h (conditional) |
8386
84-
```cpp
85-
ixx.write("#define WINRT_IMPL_GLOBAL_MODULE_FRAGMENT\n");
86-
ixx.write(strings::base_includes); // import std; is suppressed
87-
ixx.write("#undef WINRT_IMPL_GLOBAL_MODULE_FRAGMENT\n");
88-
```
87+
In `write_base_h()`, the std includes are written conditionally:
8988
90-
Inside `base_includes.h`:
9189
```cpp
92-
#if defined(__cpp_lib_modules) && defined(WINRT_IMPORT_STD) \
93-
&& !defined(WINRT_IMPL_GLOBAL_MODULE_FRAGMENT)
90+
w.write(strings::base_includes); // platform includes (always)
91+
w.write(R"(
92+
#if defined(__cpp_lib_modules) && defined(WINRT_IMPORT_STD)
9493
import std;
9594
#else
96-
// ... individual #include directives
97-
#endif
95+
)");
96+
w.write(strings::base_std_includes); // std includes (fallback)
97+
w.write(R"(#endif
98+
)");
9899
```
99100

100-
The three-way condition ensures `import std;` only fires when:
101-
1. The compiler supports it (`__cpp_lib_modules`)
102-
2. The user/build opted in (`WINRT_IMPORT_STD`)
103-
3. We're not in the global module fragment (`!WINRT_IMPL_GLOBAL_MODULE_FRAGMENT`)
101+
In the ixx writer, both are written unconditionally as raw includes:
102+
103+
```cpp
104+
ixx.write(strings::base_includes); // platform
105+
ixx.write(strings::base_std_includes); // std (always #include in GMF)
106+
```
107+
108+
This eliminates the need for any guard macros like `WINRT_IMPL_GLOBAL_MODULE_FRAGMENT`.
104109

105110
## base_macros.h: Why Macros Need Special Handling
106111

@@ -117,7 +122,8 @@ preprocessor definitions needed by generated code. It's safe to `#include`
117122
alongside `import winrt;` because it has no type declarations that could
118123
conflict with module-exported symbols.
119124

120-
**Generated by**: `write_base_macros_h()` in `file_writers.h`
125+
**Generated by**: `write_base_macros_h()` in `file_writers.h`, using content from
126+
`strings/base_module_macros.h`
121127

122128
**Used by**: `.g.h` files in module mode:
123129
```cpp
@@ -161,6 +167,26 @@ remains useful for the scenario of consuming a component's projection header
161167
separately after `import winrt;`, should MSVC add support for cross-module
162168
template specialization in the future.
163169

170+
## WINRT_EXPORT on Extern Handlers
171+
172+
**Source**: `strings/base_extern.h`
173+
174+
The global function pointer variables used for error handling customization
175+
(`winrt_to_hresult_handler`, `winrt_to_message_handler`,
176+
`winrt_throw_hresult_handler`, `winrt_activation_handler`) are prefixed with
177+
`WINRT_EXPORT`:
178+
179+
```cpp
180+
WINRT_EXPORT __declspec(selectany) std::int32_t(__stdcall* winrt_to_hresult_handler)(...) noexcept {};
181+
WINRT_EXPORT __declspec(selectany) winrt::hstring(__stdcall* winrt_to_message_handler)(...) {};
182+
// etc.
183+
```
184+
185+
This ensures correct linkage when mixing modules and non-modules translation
186+
units in the same DLL/EXE. Without `WINRT_EXPORT`, the module-compiled TUs
187+
and the header-compiled TUs would see these as separate symbols, leading to
188+
one side's customizations being invisible to the other.
189+
164190
## The -module Flag: Component Code Generation
165191
166192
**Source**: `settings.h` (`component_module`), `main.cpp` (arg parsing)
@@ -247,14 +273,21 @@ matching the approach from the sylveon fork.
247273

248274
## import std Integration
249275

250-
**Source**: `base_includes.h`
276+
**Source**: `strings/base_includes.h`, `strings/base_std_includes.h`,
277+
`file_writers.h` (`write_base_h`)
278+
279+
The standard library includes are split into two files:
251280

252-
The standard library `#include` directives in `base_includes.h` are conditionally
253-
replaced with `import std;`. The conditions are:
281+
- **`strings/base_includes.h`**: Platform headers (`<intrin.h>`, `<version>`,
282+
`<directxmath.h>`). Always written as `#include`.
283+
- **`strings/base_std_includes.h`**: Standard library headers (`<algorithm>`,
284+
`<string>`, `<coroutine>`, etc.).
285+
286+
In `write_base_h()`, the std includes are conditional:
254287

255288
```cpp
256-
#if defined(__cpp_lib_modules) && defined(WINRT_IMPORT_STD) \
257-
&& !defined(WINRT_IMPL_GLOBAL_MODULE_FRAGMENT)
289+
// In generated base.h:
290+
#if defined(__cpp_lib_modules) && defined(WINRT_IMPORT_STD)
258291
import std;
259292
#else
260293
#include <algorithm>
@@ -263,6 +296,9 @@ import std;
263296
#endif
264297
```
265298

299+
In the ixx global module fragment, both files are written as raw `#include`s
300+
with no conditional — `import` is not permitted there.
301+
266302
`WINRT_IMPORT_STD` is:
267303
- Defined automatically by NuGet targets when `CppWinRTModule=true` and
268304
`BuildStlModules=true`
@@ -286,8 +322,11 @@ would break existing users whose build systems don't compile the std module.
286322
| `component_writers.h` | Component template writers: `write_module_g_cpp`, `write_component_g_cpp` |
287323
| `code_writers.h` | Low-level writers: `write_version_assert`, `write_parent_depends`, `write_open_file_guard` |
288324
| `type_writers.h` | Writer class: `write_root_include`, `write_root_include_guarded`, `write_depends`, `write_depends_guarded` |
289-
| `strings/base_includes.h` | Standard library includes / `import std;` conditional |
290-
| `strings/base_macros.h` | `WINRT_EXPORT`, `windowsnumerics.impl.h` suppress |
325+
| `strings/base_includes.h` | Platform includes (`<intrin.h>`, `<version>`, `<directxmath.h>`) |
326+
| `strings/base_std_includes.h` | Standard library includes (`<algorithm>`, `<string>`, `<coroutine>`, etc.) |
327+
| `strings/base_macros.h` | `WINRT_EXPORT` definition, `windowsnumerics.impl.h` suppress |
328+
| `strings/base_module_macros.h` | Lightweight macros for module consumers (`WINRT_EXPORT`, `WINRT_IMPL_EMPTY_BASES`, etc.) |
329+
| `strings/base_extern.h` | `WINRT_EXPORT`-decorated extern handler variables |
291330

292331
### Key NuGet targets
293332

@@ -309,12 +348,11 @@ User sets CppWinRTModule=true + BuildStlModules=true
309348
310349
winrt.ixx compilation:
311350
module;
312-
#define WINRT_IMPL_GLOBAL_MODULE_FRAGMENT ← blocks import std;
313-
<base_includes content> ← falls through to #include path
314-
#undef WINRT_IMPL_GLOBAL_MODULE_FRAGMENT
351+
<intrin.h, version, directxmath.h> ← platform includes (base_includes)
352+
<algorithm, array, string, coroutine, ...> ← std library includes (base_std_includes)
315353
export module winrt;
316354
#define WINRT_EXPORT export
317-
#include "winrt/base.h" ← WINRT_IMPORT_STD not checked (pragma once)
355+
#include "winrt/base.h" ← base.h (import std; not checked, pragma once)
318356
#include "winrt/Windows.Foundation.h" ← SDK types + specializations
319357
...
320358
#include "winrt/MyComponent.h" ← component specializations in purview

docs/modules.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ The NuGet targets automatically:
7676
| Property | Type | Default | Description |
7777
|----------|------|---------|-------------|
7878
| `CppWinRTModule` | bool | `false` | Enable C++20 module mode. Adds `winrt.ixx` to compilation, folds component projections into the module, and passes `-module` to cppwinrt.exe for component generation. |
79-
| `BuildStlModules` | ClCompile metadata | `false` | Enables building `std.ixx`/`std.compat.ixx` so `import std;` works. Set inside `<ItemDefinitionGroup><ClCompile>`. |
79+
| `BuildStlModules` | ClCompile metadata | `false` | Enables building `std.ixx`/`std.compat.ixx` so `import std;` works. Set inside `<ItemDefinitionGroup><ClCompile>`. This is the same project property set by "Build ISO C++23 Standard Library Modules" (https://learn.microsoft.com/en-us/cpp/build/reference/c-cpp-prop-page?view=msvc-180#cc-language-properties)|
8080

8181
When `CppWinRTModule=true`, the NuGet targets also automatically:
8282
- Define `WINRT_IMPORT_STD` as a preprocessor definition when `BuildStlModules`
@@ -205,10 +205,8 @@ These are used by the code generator and should not be set directly by users:
205205

206206
| Macro | Purpose |
207207
|-------|---------|
208-
| `WINRT_IMPL_GLOBAL_MODULE_FRAGMENT` | Set by the generated `winrt.ixx` around the `base_includes` content in the global module fragment (before `export module winrt;`). Prevents `import std;` from being emitted in the global module fragment where `import` declarations are not permitted by the C++ standard. |
209-
| `WINRT_IMPL_IMPORT_STD` | Internal flag set when `WINRT_IMPORT_STD`, `__cpp_lib_modules`, and not-in-global-fragment conditions are all met. Guards the `#include` fallback path. |
210208
| `WINRT_IMPL_SKIP_INCLUDES` | When defined, generated namespace headers skip their cross-namespace `#include` dependencies (e.g., `#include "winrt/base.h"`, `#include "winrt/impl/Windows.Foundation.2.h"`). Used when those dependencies are already available from the `winrt` module. The component's own impl headers are never skipped. |
211-
| `WINRT_EXPORT` | Expands to `export` inside `winrt.ixx` (module purview), empty in header mode. Used on namespace declarations so types are properly exported from the module. Defined in `base_macros.h` (as empty) for use in generated component files that operate alongside the module. |
209+
| `WINRT_EXPORT` | Expands to `export` inside `winrt.ixx` (module purview), empty in header mode. Used on namespace declarations so types are properly exported from the module. Also applied to `winrt_to_hresult_handler` and related extern handler variables in `base_extern.h` for correct linkage when mixing modules and non-modules code in the same binary. Defined in `base_macros.h` (as empty) for use in generated component files that operate alongside the module. |
212210
| `WINRT_IMPL_EMPTY_BASES` | MSVC `__declspec(empty_bases)` optimization. Defined in both `base.h` and `base_macros.h`. |
213211
| `WINRT_IMPL_ABI_DECL` | Combines `WINRT_IMPL_NOVTABLE` and `WINRT_IMPL_PUBLIC` for ABI interface declarations. |
214212
| `CPPWINRT_VERSION` | Version string for header compatibility checking. Defined in `base.h` and `base_macros.h`. |
@@ -238,10 +236,8 @@ winrt/
238236

239237
```
240238
module; ← Global module fragment
241-
#define WINRT_IMPL_GLOBAL_MODULE_FRAGMENT
242-
<standard library includes> ← Raw #includes (import not allowed here)
243-
<directxmath.h, intrin.h> ← Platform headers
244-
#undef WINRT_IMPL_GLOBAL_MODULE_FRAGMENT
239+
<intrin.h, version, directxmath.h> ← Platform includes (base_includes)
240+
<algorithm, array, atomic, ...> ← Standard library includes (base_std_includes)
245241
246242
export module winrt; ← Module purview begins
247243
#define WINRT_EXPORT export

strings/base_extern.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11

2-
__declspec(selectany) std::int32_t(__stdcall* winrt_to_hresult_handler)(void* address) noexcept {};
3-
__declspec(selectany) winrt::hstring(__stdcall* winrt_to_message_handler)(void* address) {};
4-
__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 {};
5-
__declspec(selectany) std::int32_t(__stdcall* winrt_activation_handler)(void* classId, winrt::guid const& iid, void** factory) noexcept {};
2+
WINRT_EXPORT __declspec(selectany) std::int32_t(__stdcall* winrt_to_hresult_handler)(void* address) noexcept {};
3+
WINRT_EXPORT __declspec(selectany) winrt::hstring(__stdcall* winrt_to_message_handler)(void* address) {};
4+
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 {};
5+
WINRT_EXPORT __declspec(selectany) std::int32_t(__stdcall* winrt_activation_handler)(void* classId, winrt::guid const& iid, void** factory) noexcept {};
66

77
#if defined(_MSC_VER)
88
#ifdef _M_HYBRID

0 commit comments

Comments
 (0)