Conversation
|
Thanks for looking into this! |
Apparently it is supposed to work without needing to be exported (specializations are visible if their declarations are reachable), MSVC has an issue filled with it, but I'm not sure if there's a DevCom feedback entry. |
|
It doesn't look like there's much that can be done until a future version of the C++ compiler arrives with the necessary fixes. I'd rather not keep PRs open indefinitely. Shall we revisit this once support arrives in the compiler? |
|
Yep, I'll wait until these are all fixed. |
|
Hey so I may be way out of my depth here, but could we workaround this on the cppwinrt side, by slightly adjusting the definition of If I'm understanding this correctly, the error comes from the last two lines in the com_ptr<IContextCallback> m_context = try_capture<IContextCallback>(WINRT_IMPL_CoGetObjectContext);
int32_t m_context_type = get_apartment_type().first;However, I think this can be fixed by replacing the default initialization of the members of The important bit is @@ -8661,15 +9013,22 @@ namespace winrt::impl
return true;
case 2: /* APTTYPE_NA */
return type.second == 3 /* APTTYPEQUALIFIER_NA_ON_STA */ ||
type.second == 5 /* APTTYPEQUALIFIER_NA_ON_MAINSTA */;
}
return false;
}
struct resume_apartment_context
{
- resume_apartment_context() = default;
-
+ com_ptr<IContextCallback> m_context;
+ int32_t m_context_type;
+ resume_apartment_context()
+ {
+ m_context = try_capture<IContextCallback>(WINRT_IMPL_CoGetObjectContext);
+ m_context_type = get_apartment_type().first;
+ };
resume_apartment_context(std::nullptr_t) : m_context(nullptr), m_context_type(-1) {}
resume_apartment_context(resume_apartment_context const&) = default;
resume_apartment_context(resume_apartment_context&& other) noexcept :
m_context(std::move(other.m_context)), m_context_type(std::exchange(other.m_context_type, -1)) {}
@@ -8684,9 +9043,6 @@ namespace winrt::impl
{
return m_context_type >= 0;
}
-
- com_ptr<IContextCallback> m_context = try_capture<IContextCallback>(WINRT_IMPL_CoGetObjectContext);
- int32_t m_context_type = get_apartment_type().first;
};
inline int32_t __stdcall resume_apartment_callback(com_callback_args* args) noexceptWould that end up being effectively the same code? I tried making that change locally in a test solution, and that seemed to allow the code to compile with a cppwinrt module. Thoughts? |
|
That's not the only issue. There's the |
|
True, I saw that too. Just figured that some incremental progress might be better than nothing at all. Do we have any idea what specifically is causing the (also /cc @cdacamar as an fyi) |
|
Good news - Use of modules with C++/WinRT winrt::impl::name_v causes ICEs and/or runtime errors has been fixed with Version 17.3.0 Preview 2.0 [32530.156.main] |
|
Sweet! I've been working recently on support for having one module partition per namespace (well more like 4 because we still need The linker complains it can't find If we can get this working though, it has significant advantages over bringing all headers in a single massive module:
I wonder if VS 17.3 preview 2 would help - it doesn't appear to be out to the public yet FWIW, the internal compiler error is the following: |
|
I have a branch with a couple more modules-specific fixes for the massive single ixx approach in here: https://github.com/sylveon/cppwinrt/tree/user/sylveon/modules |
|
Okay, it appears the linker ICE is part of the issue that is fixed in preview 2! For the undefined symbol issue, it's this: https://developercommunity.visualstudio.com/t/c20-modules-unresolved-external-symbol/10049210 Once these are resolved, if I'm lucky enough my solution will work! |
|
@sylveon, great - I was hoping all the ICE issues were fixed for you as well. One question I have is around structure. It sounds like you're advocating a fine-grained approach, based on namespace. I could see that, especially if higher level modules pulled in lower levels, like:
Is it necessary to create modules for the *.0.h, *.1.h, *.2.h groups as well? That structure was needed to prevent circular references - may still be needed with modules. |
|
Do C++ modules support circular references? |
|
No idea - some experimentation needed. If so, we probably don't need the fine-grained structure. |
|
Yes, it's still necessary to create modules for *.0.h, *.1.h, *.2.h. Modules do not support circular references, and if the compiler finds one, it will simply complain and exit (which is, mind you, a much better error message than what'd you get for circular includes in headers): Right now the structure is to use module partition units (4 per namespace - 0, 1, 2 and the main header) and then have a single Having one module per namespace is a bit trickier (if not outright impossible), due to the aforementioned circular reference issue. We'd need to have separate modules (not just module partition units, because you can't import a module's partition units outside of the module) for 0, 1 and 2 (so that modules for a specific namespace can refer to another without introducing cyclic dependencies), but this introduces the issue that a class forward declared in module This isn't an issue when using module partition units for a massive single |
|
I've only experimented with having the entire projection in a single module. We should clearly identify the pros and cons of the two approaches. At a glance, one module per namespace and layer seems pretty complex. |
|
I'm using partition units ultimately exposed in a single module because the other approach (one module per namespace) wouldn't work as far as I can see (due to reasons described above), but a single massive ixx that includes everything takes forever to compile due to not being able to take advantage of multi-core processors, and also requires to be rebuilt entirely for every change. Using partition units allows the compiler to compile them in parallel (it builds up a dependency graph and then compiles from the leaves all the way to the root using one core per source file), and also allows it to lazily recompile (e.g. a change in the API for Another point for using the single |
|
Yes, I found that the monolithic ixx was 3-4 times the compile time of the PCH, so I like the finer-grained approach. But are partitions necessary for the 0, 1, 2 files? Can we create these as modules instead and just export-import them as needed into top-level namespace modules? Does that run into having to export impl namespace artifacts? How close are you to pushing your changes? I found I had to export several impl artifacts for component projections. |
No. At the interface-level, you cannot create a circular reference; the compiler and standard will not let you: Speaking to splitting the implementation up into multiple partitions: there are pros and cons to it, with more pros than cons. Many of the pros involve better inner-loop development testing. If you need to rebuild a single partition then there's a good chance that you will not spend time building the others which do not directly depend on it. It provides better component isolation in that you can more easily reason about the contents of individual module partition files than you can navigating the single .ixx. Some cons are: they complicate the build graph. The build system needs to organize all of your partitions in a way that they are efficient to build and semantically correct for the purposes of interface dependency. There is a small performance hit in loading several .ifcs which compose the entire interface (the compiler needs to read X files on disk as opposed to the single artifact in the monolithic .ixx case. If you ask me, I will almost always go for the partition-based approach. I like the hygiene of having my components logically separated and it's easier for me to locally reason about individual files if they're much smaller, even if they drag in extra interfaces. |
We can't really make the independent modules, because 0 forward declares much of the interface which is then implemented in 1 and 2 and the main header. If they where separate modules, then a class declared in 0 is not the same as the one implemented in 2.
Yes, because they are separate modules. You can only use non-exported symbols within the same module's partition units.
I'm mostly blocked on this https://developercommunity.visualstudio.com/t/c20-modules-unresolved-external-symbol/10049210 before getting a MVP. There are a few other things that I needed to remove/disable so far to get it compiling, and it needs some general cleanup (lots of hardcoded strings that should be moved to the strings folder). I can do some general cleanup and push the changes on my fork. |
|
@sylveon any updates to report? Can we make progress with the workarounds suggested by Cameron DaCamara for https://developercommunity.visualstudio.com/t/c20-modules-unresolved-external-symbol/10049210? It might be a while before a proper fix is available. |
|
While some of the issues have been fixed (the name_v runtime error and linker ICE), that specific one hasn't, and the suggested workaround (out-of-class definitions being in the same translation unit/module partition) is not viable for cppwinrt because it would introduce a circular dependency between module partitions, which is not supported. @cdacamar maybe knows if there's any work going to fix the issue. I would love to progress further as well but this is a blocker. |
|
@sylveon You should be able to define any member function out-of-class as a template workaround. One such example: struct U {
void g();
};
template <typename T>
struct S {
void f(U u);
};
template <typename T>
void S<T>::f(U u) {
u.g();
}
void use_S_f() {
S<int> s;
s.f({ });
}The snippet above has a strong dependency on the struct U;
template <typename T>
struct S {
template <typename X = U>
void f(X u);
};
template <typename T>
template <typename X>
void S<T>::f(X u) {
u.g();
}
struct U {
void g();
};
void use_S_f() {
S<int> s;
s.f({ });
}So you essentially decouple the definition of Does the above help workaround your problem or is the C++/WinRT sample substantially different from the above? |
|
Unfortunately the recommended solution "solves" the issue by introducing another one: it allows passing unexpected types to WinRT functions, and it will expose itself as runtime failures because of an unexpected vtable layout. Since cppwinrt immediately type erases it as a I have committed whatever work I have in my personal fork (hasn't been touched since June): https://github.com/sylveon/cppwinrt/tree/user/sylveon/modules However, it is very much a WIP: it has some hardcoded things (for example, I forcibly removed all of the Once the blocking compiler bug is fixed, I would be very happy to resume work but as it stands the modules compile but any consumer fails linking so it's the farthest I can go. |
|
Note that my work focuses purely on consuming WinRT types, both system and user ones. I haven't looked at producing yet, I imagine this might be a much more complex endeavor (and we also have the thorny issue that the XAML compiler would almost certainly need an update - probably doable for WinUI 3, whenever that goes open source ;), but hard to imagine it happening for system XAML). |
|
Hey I was just curious if there had ever been any movement on this in the last few months. I kinda lost track of this when I went on leave - did we ever figure out a way to resolve this (XAML issues notwithstanding)? If there's a list of outstanding compiler bugs that need resolving, I could forward those along to our compiler team friends. |
|
Last time I looked at this it was blocked by compiler bugs - I will take a look again and see if they've been resolved. |
|
There should be far fewer compiler bugs now, but the primary bug c++20 modules unresolved external symbol |
|
@sylveon just wanted to confirm that Cameron's workaround is indeed not viable? i.e., we can't employ a static_assert or concept to constrain the templated method back to its original type? |
|
It sounds plausible, but I'm not sure if it has further impacts, e.g. source compatibility, compilation times, binary size, etc. |
This is a WIP PR which would resolve #951. It also solves warnings and errors with the existing ixx file.
It's not fully ready yet, and has several pitfalls
winrt::name_of<Uri>();inmain, weirdly, solves it and makes the example run as expected. This might be a codegen bug but I haven't taken time to reduce it yet.IAsyncOperationandIAsyncOperationWithProgressfail to build due to a compiler bug