1

I am fairly new with WPF and am trying to recreate the function of the EmbeddedWindow example from the unity docs, that explain how to launch a standalone unity window inside a WPF window using the commandline and -parentHWND.

the example uses this line (in WinForm?): process.StartInfo.Arguments = "-parentHWND " + panel1.Handle.ToInt32() + " " + Environment.CommandLine;

I've tried to replace this with:

process.StartInfo.Arguments = "-parentHWND " + canvas1.Parent + " " + Environment.CommandLine;

as im not sure how else to do it.

When i try to launch the program in WPF, i recieve the error: "failed to create window"

can someone give me a code example of how to use this command line correctly in WPF? How do i call the SetParent from the MDL as the docs require?

fogx
  • 1,749
  • 2
  • 16
  • 38
  • Unity is expecting a Win32 handle (hWnd). Getting one of those for a WPF control requires some trickery. See [this thread](https://social.msdn.microsoft.com/Forums/vstudio/en-US/4f0f9987-7cc9-4492-a6a8-641611cf39ca/hwnd-or-handle-to-a-control-in-wpf?forum=wpf) for some options. [This post](http://www.abhisheksur.com/2010/12/win32-handle-hwnd-wpf-objects-note.html) (and the comments) may also help. – Basic May 13 '17 at 18:05
  • thanks alot for the links. The hWnd is just WindowInteropHelper windowHwnd = new WindowInteropHelper(this); IntPtr hWnd = windowHwnd.Handle; Now the Unity Scene starts. However! it will not be embedded inside the Parent Window unless i add the event Handler SizeChanged. But no matter what i do, unless the Parent window width and height is auto (-> NaN on pass), the window is not embedded and opens on its own. Do you have any ideas on how i can embed the Window or what im doing wrong? – fogx May 18 '17 at 16:36
  • I've never tried embedding Unity, I just have a passing familiarity with it, so I'm coming from a Wpf/Winforms background. It wouldn't surprise me if Unity used non-standard ways to interact with its window (just look at the rest of the architecture), but as to what those techniques might be and why they don't work in your scenario, I'm sorry I don't know. – Basic May 19 '17 at 07:31

1 Answers1

2

You have to use the HwndHost class. Here is the code I'm using for this:

class UnityHwndHost : HwndHost
{
    internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
    [DllImport("user32.dll")]
    internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
    [DllImport("user32.dll", SetLastError = true)]
    internal static extern uint GetWindowThreadProcessId(IntPtr hwnd, out uint processId);
    [DllImport("user32.dll", EntryPoint = "GetWindowLong")] // TODO: 32/64?
    internal static extern IntPtr GetWindowLongPtr(IntPtr hWnd, Int32 nIndex);
    internal const Int32 GWLP_USERDATA = -21;
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    internal static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    internal static extern IntPtr PostMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
    internal const UInt32 WM_CLOSE = 0x0010;

    private string programName;
    private string arguments;
    private Process process = null;
    private IntPtr unityHWND = IntPtr.Zero;

    public UnityHwndHost(string programName, string arguments = "")
    {
        this.programName = programName;
        this.arguments = arguments;
    }

    protected override HandleRef BuildWindowCore(HandleRef hwndParent)
    {
        Debug.WriteLine("Going to launch Unity at: " + this.programName + " " + this.arguments);
        process = new Process();
        process.StartInfo.FileName = programName;
        process.StartInfo.Arguments = arguments + (arguments.Length == 0 ? "" : " ") + "-parentHWND " + hwndParent.Handle;
        process.StartInfo.UseShellExecute = true;
        process.StartInfo.CreateNoWindow = true;

        process.Start();
        process.WaitForInputIdle();

        int repeat = 50;
        while (unityHWND == IntPtr.Zero && repeat-- > 0)
        {
            Thread.Sleep(100);
            EnumChildWindows(hwndParent.Handle, WindowEnum, IntPtr.Zero);
        }
        if (unityHWND == IntPtr.Zero)
            throw new Exception("Unable to find Unity window");
        Debug.WriteLine("Found Unity window: " + unityHWND);

        repeat += 150;
        while ((GetWindowLongPtr(unityHWND, GWLP_USERDATA).ToInt32() & 1) == 0 && --repeat > 0)
        {
            Thread.Sleep(100);
            Debug.WriteLine("Waiting for Unity to initialize... "+repeat);
        }
        if (repeat == 0)
        {
            Debug.WriteLine("Timed out while waiting for Unity to initialize");
        }
        else
        {
            Debug.WriteLine("Unity initialized!");
        }

        return new HandleRef(this, unityHWND);
    }

    private int WindowEnum(IntPtr hwnd, IntPtr lparam)
    {
        if (unityHWND != IntPtr.Zero)
            throw new Exception("Found multiple Unity windows");
        unityHWND = hwnd;
        return 0;
    }

    protected override void DestroyWindowCore(HandleRef hwnd)
    {
        Debug.WriteLine("Asking Unity to exit...");
        PostMessage(unityHWND, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);

        int i = 30;
        while (!process.HasExited)
        {
            if (--i < 0)
            {
                Debug.WriteLine("Process not dead yet, killing...");
                process.Kill();
            }
            Thread.Sleep(100);
        }
    }
}

The way you would use this is by first creating some container in XML, then whenever you want to start Unity add it as a child:

UnityDisplay.Child = new UnityHwndHost("UnityApp.exe", "-possibly -other -arguments");

Also see the docs about Win32 and WPF interoperation.

krzys_h
  • 123
  • 8