Skip to main content
Hosting legacy Win32 content/window in XAML? Seems impossible, right? NO. The story started when I noticed IDCompositionDevice::CreateSurfaceFromHwnd function in the public docs of DirectComposition. But I realized that we have a problem, this is for DirectComposition while XAML only publicly exposes Windows.UI.Composition objects After some investigations on Windows.UI.Xaml.dll with IDA Pro, I noticed that XAML thankfully creates the Windows.UI.Composition device using InteropCompositor which allows you to cast Windows.UI.Composition objects into DirectComposition objects and vice versa. GREAT!
You can read more about InteropCompositor in this great blogpost by ADeltaX: InteropCompositor (and CoreDispatcher)

So the next problem was that the window has to be a layered (WS_EX_LAYERED) child (WS_CHILD) window for “cloak”ing to work but we can’t set our legacy window as a child window to the ApplicationFrameWindow window because it’s not owned by our process but ApplicationFrameHost.exe. But thankfully (again!) the CoreWindow window is owned by our process so we can use it to be the parent of our legacy child window. GREAT! So let’s create our child window…
IntPtr hwnd = IntPtr.Zero;
List<IntPtr> childHwnds = new List<IntPtr>();

...

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct WindowCompositionAttribData
{
    public _WINDOWCOMPOSITIONATTRIB Attribute;
    public int * Data;
    public int SizeOfData;
}

[DllImport("ext-ms-win-ntuser-private-l1-1-1.dll", SetLastError = true)]
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttribData data);

public static unsafe void SetWindowCloak(IntPtr handle)
{
    int flag = 1;

    var data = new WindowCompositionAttribData();
    data.Attribute = _WINDOWCOMPOSITIONATTRIB.WCA_CLOAK; //17
    data.SizeOfData = sizeof(int);
    data.Data = & flag;

    SetWindowCompositionAttribute(handle, ref data);
    int result = Marshal.GetLastWin32Error();
    if (result != 0)
    {
        throw new Win32Exception(result);
    }
}

public static IntPtr EnableVisualStyles()
{
    StringBuilder sbSystemDir = new StringBuilder(256);
    GetSystemDirectory(sbSystemDir, 256);

    ACTCTX actCtx = new ACTCTX();
    actCtx.cbSize = Marshal.SizeOf(typeof(ACTCTX));
    actCtx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID |
				ACTCTX_FLAG_SET_PROCESS_DEFAULT |
				ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID;
    actCtx.lpSource = "shell32.dll";
    actCtx.wProcessorArchitecture = 0;
    actCtx.wLangId = 0;
    actCtx.lpAssemblyDirectory = sbSystemDir.ToString();
    actCtx.lpResourceName = (IntPtr) 124;
    IntPtr AcCtx = CreateActCtx(ref actCtx);
    ActivateActCtx(AcCtx, out IntPtr cookie);
    return cookie;
}

...

IntPtr coreWnd = ((ICoreWindowInterop) (object) CoreWindow.GetForCurrentThread()).WindowHandle;
IntPtr hInstance = Process.GetCurrentProcess().Handle;
string szAppName = "HelloWin";
WNDCLASS wndclass = new WNDCLASS();
wndclass.style = (uint)(ClassStyles.HorizontalRedraw | ClassStyles.VerticalRedraw);
wndclass.lpfnWndProc = (WndProc)((hWnd, message, wParam, lParam) =>
{
    IntPtr hdc;
    PAINTSTRUCT ps;
    RECT rect;
    switch ((WM) message)
    {
        case WM.PAINT:
        {
            hdc = BeginPaint(hWnd, out ps);
            GetClientRect(hWnd, out rect);
            FillRect(hdc, ref rect, GetStockObject(StockObjects.WHITE_BRUSH));
            EndPaint(hWnd, ref ps);
            return IntPtr.Zero;
            break;
        }
        case WM.DESTROY:
        {
            return IntPtr.Zero;
            break;
        }
        case WM.CREATE:
        {
            return IntPtr.Zero;
            break;
        }
    }
    return DefWindowProc(hWnd, (WM) message, wParam, lParam);
});

wndclass.hInstance = hInstance;
wndclass.lpszMenuName = null;
wndclass.lpszClassName = szAppName;
var regResult = RegisterClass(ref wndclass);

EnableVisualStyles();

hwnd = CreateWindowEx(
    (uint)(WindowStylesEx.WS_EX_LAYERED | WindowStylesEx.WS_EX_COMPOSITED),
    (ushort) new IntPtr((int)(uint) regResult),
    "The Hello Program", // window caption
    (uint) WindowStyles.WS_CHILD, // window style
    0, // initial x position
    0, // initial y position
    500, // initial x size
    500, // initial y size
    coreWnd, // parent window handle
    IntPtr.Zero, // window menu handle
    hInstance, // program instance handle
    IntPtr.Zero); // creation parameters
	
var ctls = new INITCOMMONCONTROLSEX(CommonControls.ICC_STANDARD_CLASSES | CommonControls.ICC_PROGRESS_CLASS);
InitCommonControlsEx(ref ctls);
ShowWindow(hwnd, (int) ShowWindowCommands.Normal);

SetWindowCloak(hwnd);

IntPtr hwndButton = CreateWindow(
    "BUTTON", // Predefined class
    "Hello from Comctl32!", // Button text 
    (uint) WindowStyles.WS_VISIBLE | (uint) WindowStyles.WS_CHILD, // Styles 
    0, // x position 
    0, // y position 
    200, // Button width
    100, // Button height
    hwnd, // Parent window
    IntPtr.Zero, // No menu.
    hInstance, 
    IntPtr.Zero); // Pointer not needed.
	
IntPtr hwndProg = CreateWindow(
    "msctls_progress32", // Predefined class
    null, // text 
    (uint) WindowStyles.WS_VISIBLE | (uint) WindowStyles.WS_CHILD | 0x08, // Styles 
    0, // x position 
    110, // y position 
    200, // width
    35, // height
    hwnd, // Parent window
    IntPtr.Zero, // No menu.
    hInstance, 
    IntPtr.Zero); // Pointer not needed.
	
SendMessage(hwndProg, (int)(WM.USER + 10), 1, 0); //Starts ProgressBar animation
childHwnds.Add(hwndButton);
Notes:
  • WS_EX_COMPOSITED extended style is required for the window to render
  • ICoreWindowInterop is defined in the public Windows SDK, you can copy the definition from there
  • CreateWindowEx, InitCommonControlsEx, ShowWindow, SendMessage, … are native Windows functions, you can DllImport them from the corresponding dlls for them, I got most of definitions from pinvoke.net, same with structs used in the code
Now, let’s create our Visual…
Compositor comp = Window.Current.Compositor;
IDCompositionDevice dcomp = (IDCompositionDevice)(object)comp;

IDCompositionVisual visualRaw = dcomp.CreateVisual();
visualRaw.SetOffsetX(0);
visualRaw.SetOffsetY(0);
visualRaw.SetContent(dcomp.CreateSurfaceFromHwnd(hwnd));

Visual visual = (Visual)visualRaw;
visual.Size = new Vector2(500, 500);

// "TheBox" is just a Viewbox control defined in page's XAML, you can use any XAML UIElement
ElementCompositionPreview.SetElementChildVisual(TheBox, visual);
Now, let’s handle input (the code sucks, I don’t recommend using it)
// We have to use CoreWindow pointer events because UIElement pointer events didn't work for some reasons...
CoreWindow coreWindow = CoreWindow.GetForCurrentThread();
coreWindow.PointerPressed += CoreWindow_PointerPressed;
coreWindow.PointerReleased += CoreWindow_PointerReleased;

...

private static int MAKELPARAM(int p, int p2)
{
	return ((p2 << 16) | (p & 0xFFFF));
}

...

private void CoreWindow_PointerReleased(CoreWindow sender, PointerEventArgs args)
{
	var ttv = this.TransformToVisual(TheBox);
	Point point = ttv.TransformPoint(args.CurrentPoint.Position);
	if (point.X > 0 && point.Y > 0)
	{
		SendMessage(hwnd, (uint) WM.LBUTTONUP, 0, MAKELPARAM((int) point.X, (int) point.Y));

		foreach(IntPtr wnd in childHwnds)
		{
			SendMessage(wnd, (uint) WM.LBUTTONUP, 0, MAKELPARAM((int) point.X, (int) point.Y));
		}
	}
}

private void CoreWindow_PointerPressed(CoreWindow sender, PointerEventArgs args)
{
	var ttv = this.TransformToVisual(TheBox);
	Point point = ttv.TransformPoint(args.CurrentPoint.Position);
	if (point.X > 0 && point.Y > 0)
	{
		SendMessage(hwnd, (uint) WM.LBUTTONDOWN, 0x0001, MAKELPARAM((int) point.X, (int) point.Y));

		foreach(IntPtr wnd in childHwnds)
		{
			SendMessage(wnd, (uint) WM.LBUTTONDOWN, 0x0001, MAKELPARAM((int) point.X, (int) point.Y));
		}
	}
}
We can use CoreIndependentInputSource or Windows::UI::Internal::Input::IInputSite to receive input on the Visual itself so we don’t need to use CoreWindow pointer events or transforming the Point, but let’s leave that for another blogpost…
And here’s the result!
Last modified on February 20, 2026