【UWP】让 UWP 自己托管自己 —— Windows SDK 篇

众所周知,UWP 使用的窗口模型是 CoreWindow,但是 UWP 本身只是一个应用模型,所以完全可以创建 win32 窗口,那么我们可以不可以创建一个 win32 窗口,然后像 XAML 岛 (XAML Islands) 一样把 XAML 托管上去呢?本篇将讲述如何在 UWP 创建一个 XAML 岛窗口。

示例

首先,XAML 岛会判断当前的应用模型是否为ClassicDesktop,所以我们需要利用Detours劫持AppPolicyGetWindowingModel方法。具体内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#r "nuget:Detours.Win32Metadata"
#r "nuget:Microsoft.Windows.CsWin32"

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Storage.Packaging.Appx;
using Detours = Microsoft.Detours.PInvoke;

/// <summary>
/// Represents a hook for the <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function.
/// </summary>
public sealed partial class HookWindowingModel : IDisposable
{
/// <summary>
/// The value that indicates whether the class has been disposed.
/// </summary>
private bool disposed;

/// <summary>
/// The reference count for the hook.
/// </summary>
private static int refCount;

/// <summary>
/// The value that represents the current process token.
/// </summary>
private const int currentProcessToken = -6;

/// <remarks>The original <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function.</remarks>
/// <inheritdoc cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/>
private static unsafe delegate* unmanaged[Stdcall]<HANDLE, AppPolicyWindowingModel*, WIN32_ERROR> AppPolicyGetWindowingModel;

/// <summary>
/// Initializes a new instance of the <see cref="HookWindowingModel"/> class.
/// </summary>
public HookWindowingModel()
{
refCount++;
StartHook();
}

/// <summary>
/// Finalizes this instance of the <see cref="HookWindowingModel"/> class.
/// </summary>
~HookWindowingModel()
{
Dispose();
}

/// <summary>
/// Gets the value that indicates whether the hook is active.
/// </summary>
public static bool IsHooked { get; private set; }

/// <summary>
/// Gets or sets the windowing model to use when the hooked <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function is called.
/// </summary>
internal static AppPolicyWindowingModel WindowingModel { get; set; } = AppPolicyWindowingModel.AppPolicyWindowingModel_ClassicDesktop;

/// <summary>
/// Starts the hook for the <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function.
/// </summary>
private static unsafe void StartHook()
{
if (!IsHooked)
{
using FreeLibrarySafeHandle library = PInvoke.GetModuleHandle("KERNEL32.dll");
if (!library.IsInvalid && NativeLibrary.TryGetExport(library.DangerousGetHandle(), nameof(PInvoke.AppPolicyGetWindowingModel), out nint appPolicyGetWindowingModel))
{
void* appPolicyGetWindowingModelPtr = (void*)appPolicyGetWindowingModel;
delegate* unmanaged[Stdcall]<HANDLE, AppPolicyWindowingModel*, WIN32_ERROR> overrideAppPolicyGetWindowingModel = &OverrideAppPolicyGetWindowingModel;

_ = Detours.DetourRestoreAfterWith();

_ = Detours.DetourTransactionBegin();
_ = Detours.DetourUpdateThread(PInvoke.GetCurrentThread());
_ = Detours.DetourAttach(ref appPolicyGetWindowingModelPtr, overrideAppPolicyGetWindowingModel);
_ = Detours.DetourTransactionCommit();

AppPolicyGetWindowingModel = (delegate* unmanaged[Stdcall]<HANDLE, AppPolicyWindowingModel*, WIN32_ERROR>)appPolicyGetWindowingModelPtr;
IsHooked = true;
}
}
}

/// <summary>
/// Ends the hook for the <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function.
/// </summary>
private static unsafe void EndHook()
{
if (--refCount == 0 && IsHooked)
{
void* appPolicyGetWindowingModelPtr = AppPolicyGetWindowingModel;
delegate* unmanaged[Stdcall]<HANDLE, AppPolicyWindowingModel*, WIN32_ERROR> overrideAppPolicyGetWindowingModel = &OverrideAppPolicyGetWindowingModel;

_ = Detours.DetourTransactionBegin();
_ = Detours.DetourUpdateThread(PInvoke.GetCurrentThread());
_ = Detours.DetourDetach(&appPolicyGetWindowingModelPtr, overrideAppPolicyGetWindowingModel);
_ = Detours.DetourTransactionCommit();

AppPolicyGetWindowingModel = null;
IsHooked = false;
}
}

/// <param name="policy">A pointer to a variable of the <a href="https://docs.microsoft.com/windows/win32/api/appmodel/ne-appmodel-apppolicywindowingmodel">AppPolicyWindowingModel</a> enumerated type.
/// When the function returns successfully, the variable contains the <see cref="WindowingModel"/> when the identified process is current; otherwise, the windowing model of the identified process.</param>
/// <remarks>The overridden <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function.</remarks>
/// <inheritdoc cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
private static unsafe WIN32_ERROR OverrideAppPolicyGetWindowingModel(HANDLE processToken, AppPolicyWindowingModel* policy)
{
if ((int)processToken.Value == currentProcessToken)
{
*policy = WindowingModel;
return WIN32_ERROR.ERROR_SUCCESS;
}
return AppPolicyGetWindowingModel(processToken, policy);
}

/// <inheritdoc/>
public void Dispose()
{
if (!disposed && IsHooked)
{
EndHook();
}
GC.SuppressFinalize(this);
disposed = true;
}
}

准备工作完成,接下来我们就可以创建窗口了,首先我们需要新创建一个线程,CoreWindow 线程无法新建 XAML 岛,新建线程只需要用Thread就行了。

1
new Thread(() => { ... });

首先我们需要创建 XAML 岛,这时我们就需要利用上面劫持器来劫持获取应用模型的方法了。

1
2
3
4
5
DesktopWindowXamlSource source;
using (HookWindowingModel hook = new())
{
source = new DesktopWindowXamlSource();
}

微软并没有单独提供一个 Win32 窗口管理的轮子,如果引用 Windows Forms 就太臃肿了,于是我们需要手动制作一个 Window 类:

由于 C#/Win32 不支持源生成 COM 以支持 AOT,所以我们需要手写IDesktopWindowXamlSourceNative定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
namespace Windows.Win32.System.WinRT.Xaml
{
[GeneratedComInterface]
[Guid("3CBCF1BF-2F76-4E9C-96AB-E84B37972554")]
internal partial interface IDesktopWindowXamlSourceNative
{
/// <summary>
/// Attaches the current **IDesktopWindowXamlSourceNative** instance to a parent UI element in your desktop app that is associated with a window handle.
/// </summary>
/// <param name="parentWnd">
/// <para>Type: **HWND** The window handle of the parent UI element in which you want to host a WinRT XAML control.</para>
/// <para><see href="https://learn.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.desktopwindowxamlsource/nf-windows-ui-xaml-hosting-desktopwindowxamlsource-idesktopwindowxamlsourcenative-attachtowindow#parameters">Read more on docs.microsoft.com</see>.</para>
/// </param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an **HRESULT** error code.</returns>
/// <remarks>
/// <para>For a code example that demonstrates how to use this method, see [XamlBridge.cpp](https://github.com/microsoft/Xaml-Islands-Samples/blob/master/Samples/Win32/SampleCppApp/XamlBridge.cpp) in the SampleCppApp sample in the XAML Island samples repo. > [!IMPORTANT] > Make sure that your code calls the **AttachToWindow** method only once per [DesktopWindowXamlSource](/uwp/api/windows.ui.xaml.hosting.desktopwindowxamlsource) object. Calling this method more than once for a **DesktopWindowXamlSource** object could result in a memory leak.</para>
/// <para><see href="https://learn.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.desktopwindowxamlsource/nf-windows-ui-xaml-hosting-desktopwindowxamlsource-idesktopwindowxamlsourcenative-attachtowindow#">Read more on docs.microsoft.com</see>.</para>
/// </remarks>
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int AttachToWindow(nint parentWnd);

/// <summary>
/// Gets the window handle of the parent UI element that is associated with the current IDesktopWindowXamlSourceNative instance.
/// </summary>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an **HRESULT** error code.</returns>
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int get_WindowHandle(out nint hWnd);
}

file static class Extensions
{
/// <summary>
/// Gets the window handle of the parent UI element that is associated with the current IDesktopWindowXamlSourceNative instance.
/// </summary>
public static HWND WindowHandle(this IDesktopWindowXamlSourceNative source)
{
_ = source.get_WindowHandle(out nint hWnd);
return new HWND(hWnd);
}
}
}

然后就可以制作 Window 类了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/// <summary>
/// Represents a system-managed container for the content of an app.
/// </summary>
public partial class DesktopWindow
{
private bool m_bIsClosed = false;
private DesktopWindowXamlSource m_source;
private IDesktopWindowXamlSourceNative m_native;

private readonly HWND m_hwnd;
private readonly WNDCLASSEXW m_wndClassEx;

/// <summary>
/// Initializes a new instance of the <see cref="DesktopWindow"/> class.
/// </summary>
public DesktopWindow()
{
m_wndClassEx = RegisterDesktopWindowClass(WNDPROC);
m_hwnd = CreateDesktopWindow();
}

/// <summary>
/// Gets the event dispatcher for the window.
/// </summary>
public CoreDispatcher Dispatcher { get; private set; }

/// <summary>
/// Gets the <see cref="DesktopWindowXamlSource"/> to provide XAML for this window.
/// </summary>
public DesktopWindowXamlSource WindowXamlSource
{
get => m_source;
private init
{
if (m_source != value)
{
m_source = value;
if (value != null)
{
Dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
m_native = value.As<IDesktopWindowXamlSourceNative>();
m_native.AttachToWindow(m_hwnd);
ResizeWindowToDesktopWindowXamlSourceWindowDimensions();
}
else
{
m_native = null;
}
}
}
}

/// <summary>
/// Shows the window and activates it.
/// </summary>
public void Show() => _ = PInvoke.ShowWindow(m_hwnd, SHOW_WINDOW_CMD.SW_NORMAL);

private LRESULT WNDPROC(HWND hWnd, uint message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case PInvoke.WM_PAINT:
HDC hdc = PInvoke.BeginPaint(hWnd, out PAINTSTRUCT ps);
_ = PInvoke.GetClientRect(hWnd, out RECT rect);
_ = PInvoke.FillRect(hdc, rect, new DefaultSafeHandle(PInvoke.GetStockObject(GET_STOCK_OBJECT_FLAGS.WHITE_BRUSH)));
_ = PInvoke.EndPaint(hWnd, ps);
return new LRESULT();
case PInvoke.WM_CLOSE when m_bIsClosed:
goto default;
case PInvoke.WM_CLOSE:
m_bIsClosed = true;
goto default;
case PInvoke.WM_SIZE:
ResizeWindowToDesktopWindowXamlSourceWindowDimensions();
return new LRESULT();
case PInvoke.WM_CREATE:
return new LRESULT();
case PInvoke.WM_DESTROY:
PInvoke.PostQuitMessage(0);
return new LRESULT();
default:
return PInvoke.DefWindowProc(hWnd, message, wParam, lParam);
}
}

private void ResizeWindowToDesktopWindowXamlSourceWindowDimensions()
{
if (m_bIsClosed) return;
_ = PInvoke.GetClientRect(m_hwnd, out RECT rect);
_ = PInvoke.SetWindowPos(
m_native.WindowHandle(),
new HWND(),
0, 0,
rect.Width, rect.Height,
SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW);
}
}

public partial class DesktopWindow
{
private static readonly unsafe HINSTANCE g_hInstance = new((void*)Process.GetCurrentProcess().Handle);

// win32 window class name for top-level WinUI desktop windows
private const string s_windowClassName = "WinUIDesktopWin32WindowClass";

// Default window title for top-level WinUI desktop windows
private const string s_defaultWindowTitle = "WinUI Desktop";

private static unsafe WNDCLASSEXW RegisterDesktopWindowClass(WNDPROC lpfnWndProc)
{
if (!PInvoke.GetClassInfoEx(new DefaultSafeHandle(g_hInstance), s_windowClassName, out WNDCLASSEXW wndClassEx))
{
wndClassEx.cbSize = (uint)Marshal.SizeOf(wndClassEx);
wndClassEx.style = WNDCLASS_STYLES.CS_HREDRAW | WNDCLASS_STYLES.CS_VREDRAW;
wndClassEx.cbClsExtra = 0;
wndClassEx.cbWndExtra = 0;
wndClassEx.hCursor = PInvoke.LoadCursor(new HINSTANCE(), PInvoke.IDC_ARROW);
wndClassEx.hbrBackground = (HBRUSH)((nint)SYS_COLOR_INDEX.COLOR_WINDOW + 1);
wndClassEx.hInstance = g_hInstance;

fixed (char* lps_windowClassName = s_windowClassName)
{
wndClassEx.lpszClassName = lps_windowClassName;
}

wndClassEx.lpfnWndProc = lpfnWndProc;
_ = PInvoke.RegisterClassEx(wndClassEx);

return wndClassEx;
}
return default;
}

private static unsafe HWND CreateDesktopWindow() =>
PInvoke.CreateWindowEx(
0, // Extended Style
s_windowClassName, // name of window class
s_defaultWindowTitle, // title-bar string
WINDOW_STYLE.WS_OVERLAPPEDWINDOW | WINDOW_STYLE.WS_VISIBLE, // top-level window
int.MinValue, // default horizontal position
(int)SHOW_WINDOW_CMD.SW_HIDE, // If the y parameter is some other value,
// then the window manager calls ShowWindow with that value as the nCmdShow parameter
int.MinValue, // default width
int.MinValue, // default height
new HWND(), // no owner window
null, // use class menu
new DefaultSafeHandle(g_hInstance),
null);

private partial class DefaultSafeHandle(nint invalidHandleValue, bool ownsHandle) : SafeHandle(invalidHandleValue, ownsHandle)
{
public DefaultSafeHandle(nint handle) : this(handle, true) => SetHandle(handle);

public override bool IsInvalid => handle != nint.Zero;

protected override bool ReleaseHandle() => true;
}
}

然后我们就可以初始化一个窗口了。

1
DesktopWindow window = new() { WindowXamlSource = source };

最后不要忘了用消息循环保持当前线程,不然这里跑完了窗口就退出了。

1
2
3
4
5
6
7
8
MSG msg = new();
while (msg.message != PInvoke.WM_QUIT)
{
if (PInvoke.PeekMessage(out msg, new HWND(), 0, 0, PEEK_MESSAGE_REMOVE_TYPE.PM_REMOVE))
{
_ = PInvoke.DispatchMessage(msg);
}
}

最后把之前的东西组合起来,再加点东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
/// <summary>
/// Represents a system-managed container for the content of an app.
/// </summary>
public partial class DesktopWindow
{
private bool m_bIsClosed = false;
private DesktopWindowXamlSource m_source;
private IDesktopWindowXamlSourceNative m_native;

private readonly HWND m_hwnd;
private readonly WNDCLASSEXW m_wndClassEx;

/// <summary>
/// Initializes a new instance of the <see cref="DesktopWindow"/> class.
/// </summary>
public DesktopWindow()
{
m_wndClassEx = RegisterDesktopWindowClass(WNDPROC);
m_hwnd = CreateDesktopWindow();
}

/// <summary>
/// Get the handle of the window.
/// </summary>
public nint Hwnd => m_hwnd;

/// <summary>
/// Gets the event dispatcher for the window.
/// </summary>
public CoreDispatcher Dispatcher { get; private set; }

/// <summary>
/// Gets or sets the visual root of an application window.
/// </summary>
public UIElement Content
{
get => WindowXamlSource.Content;
set => WindowXamlSource.Content = value;
}

/// <summary>
/// Gets or sets the XamlRoot in which this element is being viewed.
/// </summary>
[SupportedOSPlatform("Windows10.0.18362.0")]
public XamlRoot XamlRoot
{
get => WindowXamlSource.Content.XamlRoot;
set => WindowXamlSource.Content.XamlRoot = value;
}

/// <summary>
/// Gets or sets a string used for the window title.
/// </summary>
public unsafe string Title
{
get
{
int windowTextLength = PInvoke.GetWindowTextLength(m_hwnd);
char* windowText = stackalloc char[windowTextLength + 1];
_ = PInvoke.GetWindowText(m_hwnd, windowText, windowTextLength + 1);
return new string(windowText);
}
set => _ = PInvoke.SetWindowText(m_hwnd, value);
}

/// <summary>
/// Gets the <see cref="DesktopWindowXamlSource"/> to provide XAML for this window.
/// </summary>
public DesktopWindowXamlSource WindowXamlSource
{
get => m_source;
private init
{
if (m_source != value)
{
m_source = value;
if (value != null)
{
Dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
m_native = value.As<IDesktopWindowXamlSourceNative>();
m_native.AttachToWindow(m_hwnd);
ResizeWindowToDesktopWindowXamlSourceWindowDimensions();
}
else
{
m_native = null;
}
}
}
}

/// <summary>
/// Occurs when the window has closed.
/// </summary>
public event TypedEventHandler<DesktopWindow, object> Closed;

/// <summary>
/// Shows the window and activates it.
/// </summary>
public void Show() => _ = PInvoke.ShowWindow(m_hwnd, SHOW_WINDOW_CMD.SW_NORMAL);

/// <summary>
/// Sets the icon for the window, using the specified icon path.
/// </summary>
/// <param name="iconPath">The path of the icon.</param>
public unsafe void SetIcon(string iconPath)
{
fixed (char* ptr = iconPath)
{
HANDLE icon = PInvoke.LoadImage(new HINSTANCE(), ptr, GDI_IMAGE_TYPE.IMAGE_ICON, 0, 0, IMAGE_FLAGS.LR_LOADFROMFILE);
_ = PInvoke.SendMessage(m_hwnd, PInvoke.WM_SETICON, PInvoke.ICON_BIG, new LPARAM((nint)icon.Value));
}
}

private LRESULT WNDPROC(HWND hWnd, uint message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case PInvoke.WM_PAINT:
HDC hdc = PInvoke.BeginPaint(hWnd, out PAINTSTRUCT ps);
_ = PInvoke.GetClientRect(hWnd, out RECT rect);
_ = PInvoke.FillRect(hdc, rect, new DefaultSafeHandle(PInvoke.GetStockObject(GET_STOCK_OBJECT_FLAGS.WHITE_BRUSH)));
_ = PInvoke.EndPaint(hWnd, ps);
return new LRESULT();
case PInvoke.WM_CLOSE when m_bIsClosed:
goto default;
case PInvoke.WM_CLOSE:
m_bIsClosed = true;
Closed?.Invoke(this, null);
goto default;
case PInvoke.WM_SIZE:
ResizeWindowToDesktopWindowXamlSourceWindowDimensions();
return new LRESULT();
case PInvoke.WM_CREATE:
return new LRESULT();
case PInvoke.WM_DESTROY:
PInvoke.PostQuitMessage(0);
return new LRESULT();
default:
return PInvoke.DefWindowProc(hWnd, message, wParam, lParam);
}
}

private void ResizeWindowToDesktopWindowXamlSourceWindowDimensions()
{
if (m_bIsClosed) return;
_ = PInvoke.GetClientRect(m_hwnd, out RECT rect);
_ = PInvoke.SetWindowPos(
m_native.WindowHandle(),
new HWND(),
0, 0,
rect.Width, rect.Height,
SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW);
}
}

public partial class DesktopWindow
{
private static readonly unsafe HINSTANCE g_hInstance = new((void*)Process.GetCurrentProcess().Handle);

// win32 window class name for top-level WinUI desktop windows
private const string s_windowClassName = "WinUIDesktopWin32WindowClass";

// Default window title for top-level WinUI desktop windows
private const string s_defaultWindowTitle = "WinUI Desktop";

private static unsafe WNDCLASSEXW RegisterDesktopWindowClass(WNDPROC lpfnWndProc)
{
if (!PInvoke.GetClassInfoEx(new DefaultSafeHandle(g_hInstance), s_windowClassName, out WNDCLASSEXW wndClassEx))
{
wndClassEx.cbSize = (uint)Marshal.SizeOf(wndClassEx);
wndClassEx.style = WNDCLASS_STYLES.CS_HREDRAW | WNDCLASS_STYLES.CS_VREDRAW;
wndClassEx.cbClsExtra = 0;
wndClassEx.cbWndExtra = 0;
wndClassEx.hCursor = PInvoke.LoadCursor(new HINSTANCE(), PInvoke.IDC_ARROW);
wndClassEx.hbrBackground = (HBRUSH)((nint)SYS_COLOR_INDEX.COLOR_WINDOW + 1);
wndClassEx.hInstance = g_hInstance;

fixed (char* lps_windowClassName = s_windowClassName)
{
wndClassEx.lpszClassName = lps_windowClassName;
}

wndClassEx.lpfnWndProc = lpfnWndProc;
_ = PInvoke.RegisterClassEx(wndClassEx);

return wndClassEx;
}
return default;
}

private static unsafe HWND CreateDesktopWindow() =>
PInvoke.CreateWindowEx(
0, // Extended Style
s_windowClassName, // name of window class
s_defaultWindowTitle, // title-bar string
WINDOW_STYLE.WS_OVERLAPPEDWINDOW | WINDOW_STYLE.WS_VISIBLE, // top-level window
int.MinValue, // default horizontal position
(int)SHOW_WINDOW_CMD.SW_HIDE, // If the y parameter is some other value,
// then the window manager calls ShowWindow with that value as the nCmdShow parameter
int.MinValue, // default width
int.MinValue, // default height
new HWND(), // no owner window
null, // use class menu
new DefaultSafeHandle(g_hInstance),
null);

private partial class DefaultSafeHandle(nint invalidHandleValue, bool ownsHandle) : SafeHandle(invalidHandleValue, ownsHandle)
{
public DefaultSafeHandle(nint handle) : this(handle, true) => SetHandle(handle);

public override bool IsInvalid => handle != nint.Zero;

protected override bool ReleaseHandle() => true;
}
}

public partial class DesktopWindow
{
/// <summary>
/// Create a new <see cref="DesktopWindow"/> instance.
/// </summary>
/// <param name="launched">Do something after <see cref="DesktopWindowXamlSource"/> created.</param>
/// <returns>The new instance of <see cref="DesktopWindow"/>.</returns>
public static Task<DesktopWindow> CreateAsync(Action<DesktopWindowXamlSource> launched)
{
TaskCompletionSource<DesktopWindow> taskCompletionSource = new();

new Thread(() =>
{
try
{
DesktopWindowXamlSource source;
using (HookWindowingModel hook = new())
{
source = new DesktopWindowXamlSource();
}

DesktopWindow window = new() { WindowXamlSource = source };

launched(source);
taskCompletionSource.SetResult(window);

MSG msg = new();
while (msg.message != PInvoke.WM_QUIT)
{
if (PInvoke.PeekMessage(out msg, new HWND(), 0, 0, PEEK_MESSAGE_REMOVE_TYPE.PM_REMOVE))
{
_ = PInvoke.DispatchMessage(msg);
}
}
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
})
{
Name = nameof(DesktopWindowXamlSource)
}.Start();

return taskCompletionSource.Task;
}
}

namespace Windows.Win32.System.WinRT.Xaml
{
[GeneratedComInterface]
[Guid("3CBCF1BF-2F76-4E9C-96AB-E84B37972554")]
internal partial interface IDesktopWindowXamlSourceNative
{
/// <summary>
/// Attaches the current **IDesktopWindowXamlSourceNative** instance to a parent UI element in your desktop app that is associated with a window handle.
/// </summary>
/// <param name="parentWnd">
/// <para>Type: **HWND** The window handle of the parent UI element in which you want to host a WinRT XAML control.</para>
/// <para><see href="https://learn.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.desktopwindowxamlsource/nf-windows-ui-xaml-hosting-desktopwindowxamlsource-idesktopwindowxamlsourcenative-attachtowindow#parameters">Read more on docs.microsoft.com</see>.</para>
/// </param>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an **HRESULT** error code.</returns>
/// <remarks>
/// <para>For a code example that demonstrates how to use this method, see [XamlBridge.cpp](https://github.com/microsoft/Xaml-Islands-Samples/blob/master/Samples/Win32/SampleCppApp/XamlBridge.cpp) in the SampleCppApp sample in the XAML Island samples repo. > [!IMPORTANT] > Make sure that your code calls the **AttachToWindow** method only once per [DesktopWindowXamlSource](/uwp/api/windows.ui.xaml.hosting.desktopwindowxamlsource) object. Calling this method more than once for a **DesktopWindowXamlSource** object could result in a memory leak.</para>
/// <para><see href="https://learn.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.desktopwindowxamlsource/nf-windows-ui-xaml-hosting-desktopwindowxamlsource-idesktopwindowxamlsourcenative-attachtowindow#">Read more on docs.microsoft.com</see>.</para>
/// </remarks>
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int AttachToWindow(nint parentWnd);

/// <summary>
/// Gets the window handle of the parent UI element that is associated with the current IDesktopWindowXamlSourceNative instance.
/// </summary>
/// <returns>If this method succeeds, it returns S_OK. Otherwise, it returns an **HRESULT** error code.</returns>
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
int get_WindowHandle(out nint hWnd);
}

file static class Extensions
{
/// <summary>
/// Gets the window handle of the parent UI element that is associated with the current IDesktopWindowXamlSourceNative instance.
/// </summary>
public static HWND WindowHandle(this IDesktopWindowXamlSourceNative source)
{
_ = source.get_WindowHandle(out nint hWnd);
return new HWND(hWnd);
}
}
}

这样创建的窗口还存在一些问题,但是我不知道该怎么解决,所以该方法还是仅供参考。

最后附上示例应用:https://github.com/wherewhere/CoreAppUWP/tree/wuxc

MUXC 篇:【UWP】让 UWP 自己托管自己 —— Windows App SDK 篇

【UWP】让 UWP 自己托管自己 —— Windows SDK 篇 作者 @where-where 2025年1月17日 发表于 博客园,转载请注明出处