microsoft / WindowsAppSDK

The Windows App SDK empowers all Windows desktop apps with modern Windows UI, APIs, and platform features, including back-compat support, shipped via NuGet.
https://docs.microsoft.com/windows/apps/windows-app-sdk/
MIT License
3.85k stars 322 forks source link

Announcement: Proposed approach for windowing in Project Reunion #157

Closed rkarman closed 3 years ago

rkarman commented 4 years ago

Windowing in Reunion

We are excited to share some news with you today around the future of windowing for Windows developers. We have been listening to your feedback and concerns for this space over the years and we believe the approach we have chosen will address these in a positive way. This area has a lot of history (some of the features you use today, such as sizing and positioning windows, has been around since the first version of Win32) and a lot of investment in the developer community (your multi-windowed apps, your customized title bars, your customized dialogs and tool windows to create unique experiences, etc.) - we want to acknowledge and honor that as we go forward.

This post will outline our approach from a high-level perspective, our guiding principles, and our goals for the windowing space for Project Reunion. We are eager to get your feedback here, and we are open and ready to adjust our plans based on what you tell us. Nothing is set in stone - we are starting this journey here today, together with you.

A unified windowing space

In the past we have had two very different ways of "doing windowing" for Win32 and UWP - one very powerful but complex to work with even for basic windowing scenarios; one very limited in capabilities but easy to achieve the basics with.

For UWP we have been in a constant state of "catching up" on core functionality, and never being able to. While for Win32 we have been in a state of non-innovation, leaving developers behind because we have focused on bringing new features only to UWP where we can guarantee that guardrails are in place.

We heard you - this situation is not making anyone happy and moving between the two worlds is hard.

With Project Reunion we are taking a bold stance - we want all Windows developers to have the power of Win32 windowing at their disposal, but we also want to provide easy to adopt APIs that can provide consistent experiences across apps, as well as easy to use APIs that lower the bar of entry for new developers.

We also want the windowing model to be fundamentally the same, so that we create a familiar way of working for developers regardless if you chose UWP or Win32 as your application model.

Our approach in order to achieve this? A layered set of APIs.

Layers of APIs

We acknowledge that if you started with CreateWindowEx and used HWNDs for your app, you want to be able to continue to do so going forward. A re-write of your main window proc is just not going to be feasible. Therefore, we are bringing most of the windowing primitives from USER32 to Reunion. This gives you access to powerful APIs when you need them.

We also acknowledge that some things are really hard to do, or to get right for all situations, with the USER32 APIs, so we bring you AppWindow - a high-level windowing API surface that gives you access to a modern Windowing Model with an easier to use surface for areas where we have gotten a lot of feedback in the past, or where we want to help drive innovation, consistency in experience, or make it easier to adopt new features.

How is this different from the old Win32 vs UWP situation?

First, all these APIs are accessible to you regardless of process model - both UWP and Win32 have access to all the layers of the APIs. There might be some behavioral differences when calling the low-level APIs due to the security context of your app and whether it is running in a container or not. For example, the fact that we give you access to lower level APIs from UWP does not mean we give you access to other processes or their windows, you’ll only be able to modify your own windows - the security context for UWP is not changed.

Secondly, we are giving you the ability to freely move from one layer to the other regardless of where you started from.

Existing apps and their adoption to Reunion

For Win32 we touched on the approach already, by moving most of USER32 to Reunion we hope that you will have a straight-forward and easy path to adopt Reunion. Note that we are not saying that everything in USER32 will be available in Reunion and that there will be no work needed to move your Win32 app to Reunion - there will be parts of USER32 that will not being carried forward, but we will try to keep this to a minimum and limit the apps impacted.

For UWP the story is a little bit more nuanced. UWP have multiple windowing currencies, each with different limitations and life-time management. We are not going to preserve them all and move them to Reunion. As we mentioned earlier we are unifying the windowing model, this means changes to UWP that will require work. We are working through the details of the migration story for UWP depending on where you start from (CoreWindow, ApplicationView, AppWindow), and will start sharing that with you over the coming months. Our aim is to make it easy to migrate most UWP projects to Reunion for the windowing space, and we will do everything we can to help you come with us on the Project Reunion journey. If you have worked with AppWindow in UWP, you should be familiar with what we have in mind.

More details please!

We know that this post is light on technical details and completely lacks API shape information. This is intentional. We first wanted to share our approach for this space with you, to allow you to give feedback on this and influence it. As we align on the approach and start to move forward on the details, we want to design the APIs in the open together with you, not hand down a ready-made solution that we've designed in a vacuum. We hope you agree that this is the right approach, and we look forward to work out the details together with you for the initial release and for many years to come as Reunion grows.

Starting the journey together with you

Over the coming months, we will create functional and API specs here on GitHub and we are looking forward to a great partnership with the community. As a first step of this partnership we'd like to share our guiding principles and high-level goals for the windowing area as these will help inform everyone where we're coming from and what we're aiming for.

Guiding principles for Reunion windowing

We will support developers "where they are" – we will aim to support existing Win32 app developers with limited rewrite of their existing code; we will aim to support existing UWP developers with an easy transition from AppWindow and ApplicationView to Reunion windowing APIs.

We will design our new APIs with existing developers in mind, such that we do not leave existing Windows developers behind or alienate them with "yet another solution"; for developers new to the Windows ecosystem we will strive to have a low barrier of entry.

We will provide a full stack of APIs – powerful low-level primitives, targeted at expert developers that allow them to innovate and create experiences beyond our own roadmap and imagination; high-level scenario APIs that allow for any developer to light up our new features and take advantage of new form factors at a low cost.

Goals

  1. We will provide a global windowing currency across Win32 and UWP.
  2. Win32 apps shall be able to recompile to Reunion with only limited code changes for the windowing space.
  3. The windowing API for new projects, whether they be Win32 or UWP, will be a high-level, easy to use, API that follows modern development principles and patterns.
  4. We will provide a migration path from UWP AppWindow API onto the high-level Reunion Windowing APIs that is mostly automatable.
  5. Developers shall have access to the full spectrum of windowing APIs (both high- and low-level) with as few restrictions as possible. Ex. there may be a need to avoid certain window messages, events, or calling patterns when mixing between APIs of different "levels".

Non-Goals / Negative Goals

Open Source

It is our goal to provide as much of the windowing stack for Project Reunion as Open Source as possible. We will listen to your feedback on what you need in this space and adapt as we move forward.

Poopooracoocoo commented 4 years ago

have they also seen the issue of https://github.com/File-New-Project/EarTrumpet/issues/349 and https://github.com/sourcechord/FluentWPF/issues/42 (among many others) or the 1903 snap bug?

sylveon commented 4 years ago

See this comment: https://github.com/File-New-Project/EarTrumpet/issues/349#issuecomment-647871336

rkarman commented 4 years ago

Adding #203 to this thread. We will incorporate this into the requirements for window positioning and behavior for FullScreen for Reunion windowing.

Also an update on this thread in general: Thank you all for the valuable input and feedback on this topic. We are now working on incorporating this and writing up the initial API proposal for this area.

Next step will be to get the feature specs in order and post them here in GitHub (they may end up coming from other PMs than me, so I'll make sure to update this post with links to PRs). As part of getting the specs in place we will continue to monitor this thread, but once the PRs are out we hope that you will engage there on the particular feature areas as well.

mdtauk commented 4 years ago

Is there a compiled list of windowing problems from Win32 to WPF and UWP?

Can Reunion offer fixes to long standing windowing and window painting problems?

Reunion is essentially opt in, so if some of these fixes require code alterations - that is a sensible time to introduce it.

Rather than just using Win32 as a baseline, and not trying to build on what is already there.

AzAgarampur commented 4 years ago

@mdtauk We should make one. We can start with things like WPF's & UWP's incorrect handling & drawing of the nonclient-area, WPF per-monitor v2 dpi, WPF's incorrect WindowChrome default values, UWP's missing window borders, Win32's very inconsistent window border(s) (for some strange reason this doesn't happen in dark mode), and add to the list.

Poopooracoocoo commented 4 years ago

but will microsoft actually do anything about those issues?

pjmlp commented 4 years ago

After all the code rewrites we went through since Windows 8, and .NET Core introduction, I am very much opposed to anything that Microsoft ask us to rewrite, unless it is for a 100% from scratch application.

So however these bugs get fixed, hopefully it isn't by asking us to do yet another rewrite.

benstevens48 commented 4 years ago

I'd like to draw attention to a flaw in the implementation of some of the existing ApplicationView APIs. I don't entirely understand the reasons why, but apparently some of the APIs can cause a nested message pump, which has the potential to cause all sorts of problems - see this comment for details https://github.com/microsoft/microsoft-ui-xaml/issues/3297#issuecomment-694646359. It would be good if the new implementation avoided this if possible, or if not possible perhaps it could provide both sync and async alternatives for such APIs (where the async version would not have this problem).

sylveon commented 4 years ago

Reentrancy is normal and expected in Win32

weltkante commented 4 years ago

Reentrancy is normal and expected in Win32

Reentrancy is something way different from a nested message pump, reentrancy can happen for something as simple as an event or callback. Nested message pumps in general are evil because they can silently drop messages. Also, the huge majority of the win32 API will just do some computation and return a result, not call back into user code, and certainly not run a message pump. You probably had some specific scenario in mind, but as a statement about "the win32 API" this is simply wrong.

As far as the topic of nested message pumps is concerned, some applications have the requirement that the message pump be consistently provided by the hosting application, for example because it uses WM_APP style messages (sent to the application message loop instead of targeting a HWND) or does other processing before blocking for the next message. For example WPF interop also requires cooperation from the message loop, as do many low level frameworks, requiring a way to peek at messages - local message pumps usually don't provide that and cause lot of annoyance in interop scenarios.

Async APIs are the proper way to implement this because they allow the caller to decide whether he needs a message pump, and if so, how to implement it. Feel free to provide a helper method implementing a "DoEvents until the IAsyncAction completes" style message loop for convenience for people who do not care, but if Project Reunion is serious about bridging different frameworks it should avoid implicit nested message loops wherever it can, they only cause problems.

rickbrew commented 4 years ago

I've seen re-entrancy in desktop apps, but it was because .NET synchronization primitives (e.g. Monitor / lock) will start pumping the message queue so that inter-thread COM marshaling doesn't get starved (which can result in deadlocks). This can be solved with a custom SynchronizationContext that can be told to disabled pumping for certain regions of code. I was getting reentrant WM_PAINT messages which was not working out well, especially when mixed with stateful Direct2D render targets (you can't nest BeginDraw).

sylveon commented 4 years ago

COM is full of inner message loops and it has caused me a lot of trouble in development. Never had dropped messages but it caused me unexpected reentrancy (which lead to use-after-free in some scenarios) I had to guard against.

SetLayeredWindowAttributes has also triggered an inner message loop.

sylveon commented 4 years ago

Not to mention that dialog boxes, file browsing dialogs, OLE functions, etc. also all have an inner message loop

weltkante commented 4 years ago

COM is not the win32 API, it is beyond that, and yes, often reentrant. However it also has API to cooperate with the hosting application, should it need to. Just like rickbrew above mentioned that .NET has SynchronizationContext to opt out of nested message loops. Naive nested message loops often do not provide something to work around the shortcomings of nested message loops.

Some win32 APIs around modal dialogs may have message loops, but they are either isolated (you need to intentionally call something which implies showing a modal dialog) or provide ways to work around them.

SetLayeredWindowAttributes has also triggered an inner message loop.

Thats the first I hear of that, maybe you just mean reentrancy (it may very well send window messages, which does not require a message loop, these are entirely different things! message loops are bad, sending window messages is normal callback behavior, which can cause reentrancy but doesn't cause all the bad stuff nested message loops do)

From your response I see you are mixing up a few things, which are conceptually clearly separated in the implementation of Windows. Sending window message doesn't require a message loop, it is equivalent to what modern languages use events or callbacks for. This can cause reentrancy but is not equivalent to a message loop. Message loops wait for something and are used for modal behavior (like a message box, task dialog or resizing behavior - which are all very isolated scenarios and not arbitrary win32 APIs, often with dedicated ways to work around them - for example you explicitely call the message box or task dialog yourself and could use your own implementation if you chose to; for resizing there are dedicated messages to warn you about it; COM has a dedicated API, etc.)

What I'm saying is that future design should not just stick a nested message loop somewhere waiting for some condition and assume its fine because everyone else is doing it - because everyone else is not doing it - its very specialized behavior, which needs dedicated API to work around its shortcomings (like COM provides with IMessageFilter or .NET provides with SynchronizationContext). For new APIs the simplest and best solution is just not doing it, returning an IAsyncAction or something semantically equivalent, leaving full control at the application to provide the message loop.

sylveon commented 4 years ago

My bad, it was SetWindowLong, and not actually an inner loop, but that was still very much unexpected since the message I was receiving from that is entirely unrelated to the operation I was doing (it triggered a map clear, but the caller had an iterator to the map still, leading to use after free): image

I am not a .NET programmer, so can't "just use" a SynchronizationContext. Async APIs are a pain in C++ (ignoring lack of IDE support), because you can't inspect local variables with the debugger (neither VS nor WinDBG), and stack traces with them are essentially useless. At least inner message loop stack traces make sense, and you can use regular debugging features.

image

weltkante commented 4 years ago

Thats actually not a "message loop" - there is no waiting and pumping messages until an exit condition is met. It is just sending a window message, aka callback or event. Yes this is reentrancy, but this is not a message loop or message pump. No other messages will be delivered besides the ones being part of the implementation of SetWindowLong.

I am not a .NET programmer, so can't "just use" a SynchronizationContext.

Then it wouldn't help you anyways, it only suppresses the message pump in the .NET framework, nothing in the win32 API. And it wouldn't save you from reentrancy present in the win32 API.

I'm standing by my point, implicit message pumps which deliver arbitrary queued messages (and drop/delay some others) while waiting for an exit condition to become true are a bad thing and should be avoided in any framework wherever possible, to save people having to work around them when they have their own top level message loop in their application.

ClosetBugSlayer commented 4 years ago

This whole battle comes down to: will each framework's controls respect each others' airspace?

ChrisGuzak commented 3 years ago

The earlier disucssion of unexpected reentrancy between @sylveon and @weltkante raises a good topic, what threading model should the reunion windowing design support? ASTA, STA, BSTA, all 3?

@MikeHillberg what will WinUI 3 support?

JaiganeshKumaran commented 3 years ago

The current Sticky Notes app is pure UWP with no full-trust but still can draw custom title bar without system icons and can also disable the system title bar context menu yet still works on 10x and non-Desktop platforms. Microsoft should open up this functionality to other developers.