1

Trying to create a WPF caliburn micro application that creates a separate desktop and shows its main window there.

Problem: after the desktop is created and i switch there, no window is shown.

namespace WpfThreads
{
   class AppBootstrapper : Bootstrapper<WpfThreads.ViewModels.WpfThreadsViewModel>
   {
      protected override void OnStartup(object sender, StartupEventArgs e)
      {
          var desktop = Native.CreateDesktop("NewDesktop", 0, 0, 0, DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD | DESKTOP_JOURNALPLAYBACK | DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP, 0);
          Native.SetThreadDesktop(desktop);
          Native.SwitchDesktop(desktop);
          base.OnStartup(sender, e);
      }
   }
}

SetThreadDesktop() fails, other calls are successful. The OnStartup() method does run on main thread (which is also the UI thread).

Yury Korobkin
  • 105
  • 1
  • 8
  • 3
    Did `SetThreadDesktop` succeed? Things as seemingly unrelated as `STAThreadAttribute` can create windows, and ["The SetThreadDesktop function will fail if the calling thread has any windows or hooks on its current desktop"](https://msdn.microsoft.com/en-us/library/windows/desktop/ms686250%28v=vs.85%29.aspx). – Ben Voigt Mar 03 '15 at 23:43
  • Actually "If the function succeeds, the return value is nonzero." So it fails. Tnanks. That means i have to call it even earlier. Any idea where do i get an opportunity to make these calls before any windowing taking place? – Yury Korobkin Mar 04 '15 at 07:16
  • After moving the SetThreadDesktop() call to the beginning of Main() i discovered something really discouraging. If Main is marked with [System.STAThreadAttribute()] then SetThreadDesktop() fails with error code 170 ("The requested resource is in use"). If i remove this attribute then it succeeds, but later on WPF throws System.InvalidOperationException ("The calling thread must be STA, because many UI components require this"). So, only solutions that i can see are: either use a launcher app that will create a new desktop and create my WPF process there, or write a c++ app with no COM... – Yury Korobkin Mar 04 '15 at 14:57
  • 1
    You can set STA manually using `CurrentThread.ApartmentState`, after changing desktop, but before creating any windows. See http://stackoverflow.com/q/6373645/103167 – Ben Voigt Mar 04 '15 at 14:58
  • 1
    "New threads are initialized as ApartmentState.MTA if their apartment state has not been set before they are started. Apartment state must be set before a thread is started." https://msdn.microsoft.com/en-us/library/system.threading.thread.setapartmentstate(v=vs.110).aspx – Yury Korobkin Mar 04 '15 at 15:51
  • [Raymond's blog post today](Raymond's blog post today is somewhat related: http://blogs.msdn.com/b/oldnewthing/archive/2015/03/16/10600388.aspx) gives some further details that explain that because p/invoke can't guarantee that `SetThreadDesktop` won't use COM, it calls `CoInitializeEx` to set the apartment model... thus creating a window at just the wrong time and ensuring that `SetThreadDesktop` fails. – Ben Voigt Mar 16 '15 at 17:02

1 Answers1

2

The SetThreadDesktop function will fail if the calling thread has any windows or hooks on the current desktop.

It's very unusual for a C# UI thread to not have any windows, because the project wizards mark Main() with [STAThreadAttribute], which causes a window to be created before any of your code runs. This window then gives your thread permanent affinity to the current desktop, preventing you from switching.

What you need to do to have both STA threading model and use a security desktop is to activate STA by hand.

First, create the desktop and activate it with SetThreadDesktop. This does not use COM or create any windows.

Then, set the COM threading model to STA:

Threading.Thread.CurrentThread.TrySetApartmentState(Threading.ApartmentState.STA);

This creates a window, permanently binding your UI thread to the desktop you just switched to.

After performing both these steps, you can safely start using other UI classes such as WinForms and WPF windows and widgets.

Unfortunately, this was only possible in .NET 1.x, which left COM uninitialized in a new thread until you set the threading model. Since 2.0, lack of STAThreadAttribute on Main() is interpreted as a request for MTA, and although that won't create a window that interferes with SetThreadDesktop, it does prevent changing to STA later. And new threads inherit their desktop from the process-creation options, not from the thread that spawns them, so you can't use an MTA thread to create and set the desktop and then spawn a STA thread to perform the UI work -- the STA thread won't end up in the new desktop.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Did this. InvalidApartmentStateChange is thrown. Additional Information: An attempt was made to change the apartment state of the thread to STA, but it has already been set to MTA. When creating a new thread the apartment state shouldbe set before the thread is started. – Yury Korobkin Mar 04 '15 at 15:40
  • I have even tried to call native CoInitializeEx. It failed. – Yury Korobkin Mar 04 '15 at 15:42
  • 1
    @YuryKorobkin: Right, it can't be called twice in the same thread. I wonder if it is possible to create a new thread with Win32 CreateThread (not Threading.Thread class), call SetThreadDesktop and CoInitializeEx, and then run .NET UI code in it. Probably not. – Ben Voigt Mar 04 '15 at 16:16
  • This actually worked :) At least partially. The GUI draws slowly, refresh rate of 3-4 fps when dragging a window. The window won't redraw when resized etc. But, damn! – Yury Korobkin Mar 05 '15 at 09:08