Search Unity

Bug Keyboard input is not registered when Unity is embedded in a WPF application

Discussion in 'Input System' started by Chrueschsch1e, Sep 2, 2020.

  1. Chrueschsch1e

    Chrueschsch1e

    Joined:
    Jan 28, 2020
    Posts:
    16
    Hello!

    We embed the Unity process into a Windows Forms Panel, which is the child of a WindowsFormsHost, which again is the child of a WPF Grid Control.
    The Unity process is started with the -parentHWND and the aforementioned Panel as arguments.

    The old Input System worked fine.
    But since we switched to the new Input System key press events are not fired for any key on the keyboard. Mouse input is still fine however.

    Is this a bug? Is this intentional? Is there a way to make it work?
    Help is greatly appreciated. Thank you!


    (The same question has been asked in the general forum in April, but has never gotten any replies:
    https://forum.unity.com/threads/new...aunching-with-the-parenthwnd-argument.868732/)
     
  2. joachimally

    joachimally

    Joined:
    Jan 22, 2020
    Posts:
    2
    Hello,

    I am experiencing exactly the same problem and am still wondering how to fix it.

    I tried e.g. "WindowsFormsHost.EnableWindowsFormsInterop();" but that did not do the trick.
     
  3. Chrueschsch1e

    Chrueschsch1e

    Joined:
    Jan 28, 2020
    Posts:
    16
    It's been almost a year and the problem with the "new" input system and an embedded Unity process still persists.
    Is there any update / tip / magic trick that could finally get us around this problem?
     
  4. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    In case you haven't already, please file a ticket with the Unity bug reporter. You can post the ticket number here and I will check on the status.
     
  5. ConanB

    ConanB

    Joined:
    Jun 25, 2019
    Posts:
    18
    Was a bug ever filed for this? I'm also facing the same issue and would love to know the status of it.
     
  6. poprev-3d

    poprev-3d

    Joined:
    Apr 12, 2019
    Posts:
    72
    Facing the same issue here as well. Any update on this issue @Rene-Damm ?
     
  7. dmytro_at_unity

    dmytro_at_unity

    Unity Technologies

    Joined:
    Feb 12, 2021
    Posts:
    212
    @ConanB @poprev-3d please file a bug report for this, from top of my head I don't remember anything in our bug tracker related to UWP embedding breaking keyboard input, it should work so if it doesn't it's most likely a bug, it would be awesome to have a repro case for our UWP folks.
     
  8. poprev-3d

    poprev-3d

    Joined:
    Apr 12, 2019
    Posts:
    72
    This is not specifically related to UWP but to windows in general :) I have created a bug report and the steps to reproduce it (ticket 1372661).
     
    dmytro_at_unity likes this.
  9. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
  10. poprev-3d

    poprev-3d

    Joined:
    Apr 12, 2019
    Posts:
    72
    @Tautvydas-Zilys thanks for your answer! In the case we have no control over the parent context (I embed Unity in an Electron window, Electron has little to no control on the win32 api), is there a way to forward those messages? I was thinking about making a side c++ process that forwards the WM_INPUT messages, but it seems a bit overkill for something as simple as inputs...
     
  11. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    I don't really know how Electron works, but this is how you can do it from C# code:

    Code (csharp):
    1.  
    2. [StructLayout(LayoutKind.Sequential)]
    3. public struct RAWINPUTDEVICE
    4. {
    5.    public ushort usUsagePage;
    6.    public ushort usUsage;
    7.    public uint dwFlags;
    8.    public IntPtr hwndTarget;
    9. }
    10.  
    11. const ushort HID_USAGE_PAGE_GENERIC = 0x01;
    12. const ushort HID_USAGE_GENERIC_KEYBOARD = 0x06;
    13.  
    14. [DllImport("user32.dll", SetLastError = true)]
    15. public static extern bool RegisterRawInputDevices([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] RAWINPUTDEVICE[] pRawInputDevices, int uiNumDevices, int cbSize);
    16.  
    17. [DllImport("user32.dll", SetLastError = true)]
    18. private static extern IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
    19.  
    20. private new delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    21.  
    22. private MulticastDelegate originalWndProc;
    23. private WndProc myWndProc;
    24.  
    25. private IntPtr HookWndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
    26. {
    27.    if (msg == WM_INPUT)
    28.    {
    29.        SendMessage(unityHWND, msg, wParam, lParam);
    30.        return IntPtr.Zero;
    31.    }
    32.  
    33.    return (IntPtr)originalWndProc.DynamicInvoke(new object[] { hwnd, msg, wParam, lParam });
    34. }
    35.  
    36. private void SetupRawInput(IntPtr hostHWND)
    37. {
    38.     myWndProc = HookWndProc;
    39.  
    40.    var originalWndProcPtr = SetWindowLongPtrW(hostHWND, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(myWndProc));
    41.    if (originalWndProcPtr == null)
    42.    {
    43.        var errorCode = Marshal.GetLastWin32Error();
    44.        throw new Win32Exception(errorCode, "Failed to overwrite the original wndproc");
    45.    }
    46.  
    47.    originalWndProc = Marshal.GetDelegateForFunctionPointer<MulticastDelegate>(originalWndProcPtr);
    48.  
    49.    var rawInputDevices = new[]
    50.    {
    51.        new RAWINPUTDEVICE()
    52.        {
    53.            usUsagePage = HID_USAGE_PAGE_GENERIC,
    54.            usUsage = HID_USAGE_GENERIC_KEYBOARD,
    55.            dwFlags = 0,
    56.            hwndTarget = hostHWND
    57.        }
    58.    };
    59.  
    60.    if (!RegisterRawInputDevices(rawInputDevices, 1, Marshal.SizeOf(typeof(RAWINPUTDEVICE))))
    61.    {
    62.        var lastError = Marshal.GetLastWin32Error();
    63.        throw new Win32Exception(lastError, "Failed to register raw input devices");
    64.    }
    65. }
    What you need to basically do is call RegisterRawInputDevices() on the parent window, and then forward WM_INPUT messages to the Unity child window.
     
  12. poprev-3d

    poprev-3d

    Joined:
    Apr 12, 2019
    Posts:
    72
    @Tautvydas-Zilys thank you! I managed to make it work with the Window form example found in the documentation of -parentHWND.
    Although, I'm afraid this still doesn't solve the issue for non C# integrations, especially for Electron integrations in which you can hardly call RegisterRawInputDevices or any win32 api.
    Do you know if there's a workaround to achieve this without having to call win32 bindings from the parent window (maybe by doing it directly from the Unity child app, or something else ?).

    Just for those coming after me, here's the full code (Form1.cs) of the doc example to update for the new input system to work, you'll also have to compile to x64 (i think SetWindowLongPtrW doesn't work on 32 bit systems). It would be nice to update the doc zip file :).

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Data;
    5. using System.Drawing;
    6. using System.Linq;
    7. using System.Runtime.InteropServices;
    8. using System.Text;
    9. using System.Threading;
    10. using System.Windows.Forms;
    11. using System.Diagnostics;
    12. using System.Windows.Forms.VisualStyles;
    13.  
    14. namespace Container
    15. {
    16.     public partial class Form1 : Form
    17.     {
    18.         [DllImport("User32.dll")]
    19.         static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
    20.  
    21.         internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
    22.         [DllImport("user32.dll")]
    23.         internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
    24.  
    25.         [DllImport("user32.dll")]
    26.         static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    27.  
    28.         private Process process;
    29.         private IntPtr unityHWND = IntPtr.Zero;
    30.  
    31.         private const int WM_ACTIVATE = 0x0006;
    32.         private const int WM_INPUT = 0x00FF;
    33.         private const int GWLP_WNDPROC = -4;
    34.         private readonly IntPtr WA_ACTIVE = new IntPtr(1);
    35.         private readonly IntPtr WA_INACTIVE = new IntPtr(0);
    36.  
    37.         [StructLayout(LayoutKind.Sequential)]
    38.         public struct RAWINPUTDEVICE
    39.         {
    40.             public ushort usUsagePage;
    41.             public ushort usUsage;
    42.             public uint dwFlags;
    43.             public IntPtr hwndTarget;
    44.         }
    45.  
    46.         const ushort HID_USAGE_PAGE_GENERIC = 0x01;
    47.         const ushort HID_USAGE_GENERIC_KEYBOARD = 0x06;
    48.  
    49.         [DllImport("user32.dll", SetLastError = true)]
    50.         public static extern bool RegisterRawInputDevices([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] RAWINPUTDEVICE[] pRawInputDevices, int uiNumDevices, int cbSize);
    51.  
    52.         [DllImport("user32.dll", SetLastError = true)]
    53.         private static extern IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
    54.  
    55.         private new delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    56.  
    57.         private MulticastDelegate originalWndProc;
    58.         private WndProc myWndProc;
    59.  
    60.         public Form1()
    61.         {
    62.             InitializeComponent();
    63.  
    64.             try
    65.             {
    66.                 process = new Process();
    67.                 process.StartInfo.FileName = "Child.exe";
    68.                 process.StartInfo.Arguments = "-parentHWND " + panel1.Handle.ToInt32() + " " + Environment.CommandLine;
    69.                 process.StartInfo.UseShellExecute = true;
    70.                 process.StartInfo.CreateNoWindow = true;
    71.  
    72.                 process.Start();
    73.  
    74.                 process.WaitForInputIdle();
    75.                 EnumChildWindows(panel1.Handle, WindowEnum, IntPtr.Zero);
    76.                 SetupRawInput(panel1.Handle);
    77.                 unityHWNDLabel.Text = "Unity HWND: 0x" + unityHWND.ToString("X8");
    78.             }
    79.             catch (Exception ex)
    80.             {
    81.                 MessageBox.Show(ex.Message + ".\nCheck if Container.exe is placed next to Child.exe.");
    82.             }
    83.  
    84.         }
    85.  
    86.         private void ActivateUnityWindow()
    87.         {
    88.             SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
    89.         }
    90.  
    91.         private void DeactivateUnityWindow()
    92.         {
    93.             SendMessage(unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
    94.         }
    95.  
    96.         private int WindowEnum(IntPtr hwnd, IntPtr lparam)
    97.         {
    98.             unityHWND = hwnd;
    99.             ActivateUnityWindow();
    100.             return 0;
    101.         }
    102.  
    103.         private void panel1_Resize(object sender, EventArgs e)
    104.         {
    105.             MoveWindow(unityHWND, 0, 0, panel1.Width, panel1.Height, true);
    106.             ActivateUnityWindow();
    107.         }
    108.  
    109.         // Close Unity application
    110.         private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    111.         {
    112.             try
    113.             {
    114.                 process.CloseMainWindow();
    115.  
    116.                 Thread.Sleep(1000);
    117.                 while (process.HasExited == false)
    118.                     process.Kill();
    119.             }
    120.             catch (Exception)
    121.             {
    122.              
    123.             }
    124.         }
    125.  
    126.         private void Form1_Activated(object sender, EventArgs e)
    127.         {
    128.             ActivateUnityWindow();
    129.         }
    130.  
    131.         private void Form1_Deactivate(object sender, EventArgs e)
    132.         {
    133.             DeactivateUnityWindow();
    134.         }
    135.         private IntPtr HookWndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
    136.         {
    137.             if (msg == WM_INPUT)
    138.             {
    139.                 SendMessage(unityHWND, msg, wParam, lParam);
    140.                 return IntPtr.Zero;
    141.             }
    142.  
    143.             return (IntPtr)originalWndProc.DynamicInvoke(new object[] { hwnd, msg, wParam, lParam });
    144.         }
    145.  
    146.         private void SetupRawInput(IntPtr hostHWND)
    147.         {
    148.             myWndProc = HookWndProc;
    149.  
    150.             var originalWndProcPtr = SetWindowLongPtrW(hostHWND, GWLP_WNDPROC , Marshal.GetFunctionPointerForDelegate(myWndProc));
    151.             if (originalWndProcPtr == null)
    152.             {
    153.                 var errorCode = Marshal.GetLastWin32Error();
    154.                 throw new Win32Exception(errorCode, "Failed to overwrite the original wndproc");
    155.             }
    156.  
    157.             Type lel = typeof(MulticastDelegate);
    158.             originalWndProc = (MulticastDelegate)Marshal.GetDelegateForFunctionPointer(originalWndProcPtr, lel);
    159.  
    160.             var rawInputDevices = new[]
    161.             {
    162.        new RAWINPUTDEVICE()
    163.        {
    164.            usUsagePage = HID_USAGE_PAGE_GENERIC,
    165.            usUsage = HID_USAGE_GENERIC_KEYBOARD,
    166.            dwFlags = 0,
    167.            hwndTarget = hostHWND
    168.        }
    169.    };
    170.  
    171.             if (!RegisterRawInputDevices(rawInputDevices, 1, Marshal.SizeOf(typeof(RAWINPUTDEVICE))))
    172.             {
    173.                 var lastError = Marshal.GetLastWin32Error();
    174.                 throw new Win32Exception(lastError, "Failed to register raw input devices");
    175.             }
    176.         }
    177.     }
    178. }
    179.  
     
    NiklasMollerChalmers likes this.
  13. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    Starting Unity 2021.2, perhaps you could try using this technique? https://docs.unity3d.com/2021.2/Documentation/ScriptReference/Windows.Input.ForwardRawInput.html

    I believe raw input messages don't arrive in -parentHwnd case due to Unity window becoming a child window. But if you create another window that's in invisible in the Unity process and set up raw input events for that, you should be able to receive them.
     
  14. poprev-3d

    poprev-3d

    Joined:
    Apr 12, 2019
    Posts:
    72
    @Tautvydas-Zilys alright I got this working with a bit of change! Initially, the code in the Windows.Input.ForwardRawInput.html documentation wasn't working with my electron integration, but by adding the RIDEV_INPUTSINK flag on the RAWINPUTDEVICE it works like a charm!
    RIDEV_INPUTSINK allows the created window (which is invisible) to receive input even when it's in the foreground (which happens when you focus on the Unity window).

    Essentially, this consists in modifying the example code to add the dwFlags (to make this work you also have to enable unsafe code in the player settings and add the doc example Monobehaviour on a gameobject):

    Code (CSharp):
    1. ref RAWINPUTDEVICE keyboard = ref m_Devices[1];
    2. (...
    3. keyboard.dwFlags = 0x00000100;
    With this technique, inputs will always be detected and forwarded to the unity child window. You have to control focus yourself by allowing or not the Update loop to run (I personally do this when focusing inside/outside of HTML using a custom IPC between Electron and Unity).

    @Tautvydas-Zilys do you know if this will be backported to the stable versions of Unity (2020 etc.)?

    In any case, this opens the door to native integrations of Unity with several UI toolkits (WPF, Qt, Electron) and brings a lot of value. Thanks a lot to all of those who helped!
     
    Last edited: Oct 19, 2021
  15. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    No, but on older releases you can just forward WM_INPUT event directly using "SendMessage" function. It doesn't work as of 2021.2 due to this: https://forum.unity.com/threads/windows-mouse-raw-input-mouse-handling-broken-in-rewired.1172786/

    I'm surprised you need RIDEV_INPUTSINK flag. It shouldn't be needed if the focused window belongs to the same process as the one you register raw input on. Also be careful with that flag. AV software likes to flag it as a keylogger.
     
  16. poprev-3d

    poprev-3d

    Joined:
    Apr 12, 2019
    Posts:
    72
    @Tautvydas-Zilys good to know for the AV flagging, I've changed nothing in the doc example except RIDEV_INPUTSINK because I noticed that the inputs were only working when focusing on the created window (after adding the WS_VISIBLE flag to CreateWindowEx at first, to make the window visible and focusable for tests purposes).

    Regarding making this work with Unity 2020, I have tried with SendMessage and it works like a charm (still need RIDEV_INPUTSINK though). The only tricky thing is to gather the unityHWND from Unity itself (see this post).

    For those coming after me, you'll have to replace this chunk of code to have it work in Unity 2020:
    Code (CSharp):
    1.     [DllImport("user32.dll")]
    2.     static extern int SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
    3.     static IntPtr unityHWND = IntPtr.Zero;
    4.  
    5. ....
    6.  
    7. Awake(){
    8.  // Here write some code to gather the unity HWND, there are several ways to achieve this
    9.  // This is mandatory for this code to work
    10.  // Personally I've done it using EnumThreadWindows native method, but I'm not sure this is recommended, therefore I have not included it there
    11.  // See https://forum.unity.com/threads/how-do-you-reliably-the-hwnd-window-handle-of-the-games-own-window.528444/
    12. unityHWND = ...;
    13. }
    14.  
    15. ...
    16.  
    17. // Update the example method with SendMessage
    18.     [MonoPInvokeCallback(typeof(WndProcDelegate))]
    19.     static IntPtr WndProc(IntPtr window, uint message, IntPtr wParam, IntPtr lParam)
    20.     {
    21.         try
    22.         {
    23.             if (message == WM_INPUT)
    24.             {
    25.                 SendMessage(unityHWND, message, wParam, lParam);
    26.                 // ProcessRawInputMessage(lParam);
    27.             }
    28.  
    29.             return DefWindowProcW(window, message, wParam, lParam);
    30.         }
    31.         catch (Exception e)
    32.         {
    33.             // Never let exception escape to native code as that will crash the app
    34.             Debug.LogException(e);
    35.             return IntPtr.Zero;
    36.         }
    37.     }
    Once again @Tautvydas-Zilys thanks for your help, it's really appreciated :)
     
    Last edited: Oct 19, 2021
  17. dyu25

    dyu25

    Joined:
    Oct 29, 2021
    Posts:
    11

    Hi,
    I had the same issue of the keyboard inputs not being detected and tried adding this code but I get the following error on line 168 of the attached code below:

    Code (CSharp):
    1. System.ArgumentException: 'Object of type 'System.Int32' cannot be converted to type 'Interop+User32+WM'.'
    Attached code (unity integrated into WPF app):
    Code (CSharp):
    1. using System;
    2. using System.ComponentModel;
    3. using System.Diagnostics;
    4. using System.Runtime.InteropServices;
    5. using System.Threading;
    6. using System.Windows;
    7. using System.Windows.Interop;
    8.  
    9. namespace Telekinesis.UnityApp
    10. {
    11.     /// <summary>
    12.     /// Interaction logic for MainWindow.xaml
    13.     /// </summary>
    14.     public partial class MainWindow : Window
    15.     {
    16.         [DllImport("User32.dll")]
    17.         static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
    18.  
    19.         internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
    20.         [DllImport("user32.dll")]
    21.         internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
    22.  
    23.         [DllImport("user32.dll")]
    24.         static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    25.  
    26.         private Process _process;
    27.         private IntPtr _unityHWND = IntPtr.Zero;
    28.         bool initialized = false;
    29.  
    30.         private const int GWLP_WNDPROC = -4;
    31.         private const int WM_INPUT = 0x00FF;
    32.         private const int WM_ACTIVATE = 0x0006;
    33.         private readonly IntPtr WA_ACTIVE = new IntPtr(1);
    34.         private readonly IntPtr WA_INACTIVE = new IntPtr(0);
    35.  
    36.         // To capture key presses and send to unity
    37.         [StructLayout(LayoutKind.Sequential)]
    38.         public struct RAWINPUTDEVICE
    39.         {
    40.             public ushort usUsagePage;
    41.             public ushort usUsage;
    42.             public uint dwFlags;
    43.             public IntPtr hwndTarget;
    44.         }
    45.         const ushort HID_USAGE_PAGE_GENERIC = 0x01;
    46.         const ushort HID_USAGE_GENERIC_KEYBOARD = 0x06;
    47.  
    48.         [DllImport("user32.dll", SetLastError = true)]
    49.         public static extern bool RegisterRawInputDevices([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] RAWINPUTDEVICE[] pRawInputDevices, int uiNumDevices, int cbSize);
    50.         [DllImport("user32.dll", SetLastError = true)]
    51.         private static extern IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
    52.  
    53.         private new delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    54.  
    55.         private MulticastDelegate originalWndProc;
    56.         private WndProc myWndProc;
    57.  
    58.  
    59.         public MainWindow()
    60.         {
    61.             InitializeComponent();
    62.  
    63.             System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
    64.             dispatcherTimer.Tick += attemptInit;
    65.             dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
    66.             dispatcherTimer.Start();
    67.  
    68.         }
    69.  
    70.         void attemptInit(object sender, EventArgs e)
    71.         {
    72.  
    73.             if (initialized)
    74.                 return;
    75.  
    76.             //HwndSource source = (HwndSource)HwndSource.FromVisual(UnityGrid);
    77.  
    78.             //IntPtr hWnd = source.Handle;
    79.             IntPtr unityHandle = UnityGrid.Handle;
    80.  
    81.             try
    82.             {
    83.                 _process = new Process();
    84.                 _process.StartInfo.FileName = "UnityWindow.exe";
    85.                 _process.StartInfo.Arguments = "-parentHWND " + unityHandle.ToInt32() + " " + Environment.CommandLine;
    86.                 _process.StartInfo.UseShellExecute = true;
    87.                 _process.StartInfo.CreateNoWindow = true;
    88.  
    89.                 _process.Start();
    90.  
    91.                 _process.WaitForInputIdle();
    92.                 // Doesn't work for some reason ?!
    93.                 //hWnd = _process.MainWindowHandle;
    94.                 EnumChildWindows(unityHandle, WindowEnum, IntPtr.Zero);
    95.                 SetupRawInput(unityHandle);
    96.  
    97.                 Debug.WriteLine("Unity HWND: 0x" + _unityHWND.ToString("X8"));
    98.  
    99.                 UnityContentResize(this, EventArgs.Empty);
    100.                 PresentationSource s = PresentationSource.FromVisual(Application.Current.MainWindow);
    101.  
    102.                 initialized = true;
    103.             }
    104.             catch (Exception ex)
    105.             {
    106.                 MessageBox.Show(ex.Message + ".\nCheck if Container.exe is placed next to UnityGame.exe.");
    107.             }
    108.         }
    109.  
    110.         private void ActivateUnityWindow()
    111.         {
    112.             SendMessage(_unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
    113.         }
    114.  
    115.         private void DeactivateUnityWindow()
    116.         {
    117.             SendMessage(_unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
    118.         }
    119.  
    120.         private int WindowEnum(IntPtr hwnd, IntPtr lparam)
    121.         {
    122.             _unityHWND = hwnd;
    123.             ActivateUnityWindow();
    124.             return 0;
    125.         }
    126.  
    127.         private void UnityContentResize(object sender, EventArgs e)
    128.         {
    129.             MoveWindow(_unityHWND, 0, 0, (int)UnityGrid.Width, (int)UnityGrid.Height, true);
    130.             Debug.WriteLine("RESIZED UNITY WINDOW TO: " + (int)UnityGrid.Width + "x" + (int)UnityGrid.Height);
    131.             ActivateUnityWindow();
    132.         }
    133.  
    134.         // Close Unity application
    135.         private void ApplicationExit(object sender, EventArgs e)
    136.         {
    137.             try
    138.             {
    139.                 _process.CloseMainWindow();
    140.  
    141.                 Thread.Sleep(1000);
    142.                 while (!_process.HasExited)
    143.                     _process.Kill();
    144.             }
    145.             catch (Exception)
    146.             {
    147.             }
    148.         }
    149.  
    150.         private void UnityContentActivate(object sender, EventArgs e)
    151.         {
    152.             ActivateUnityWindow();
    153.         }
    154.  
    155.         private void UnityContentDeactivate(object sender, EventArgs e)
    156.         {
    157.             DeactivateUnityWindow();
    158.         }
    159.  
    160.         private IntPtr HookWndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
    161.         {
    162.             if (msg == WM_INPUT)
    163.             {
    164.                 SendMessage(_unityHWND, msg, wParam, lParam);
    165.                 return IntPtr.Zero;
    166.             }
    167.  
    168.             return (IntPtr)originalWndProc.DynamicInvoke(new object[] { hwnd, msg, wParam, lParam });
    169.         }
    170.  
    171.         private void SetupRawInput(IntPtr hostHWND)
    172.         {
    173.             myWndProc = HookWndProc;
    174.  
    175.             var originalWndProcPtr = SetWindowLongPtrW(hostHWND, GWLP_WNDPROC, Marshal.GetFunctionPointerForDelegate(myWndProc));
    176.             if (originalWndProcPtr == null)
    177.             {
    178.                 var errorCode = Marshal.GetLastWin32Error();
    179.                 throw new Win32Exception(errorCode, "Failed to overwrite the original wndproc");
    180.             }
    181.  
    182.             Type lel = typeof(MulticastDelegate);
    183.             originalWndProc = (MulticastDelegate)Marshal.GetDelegateForFunctionPointer(originalWndProcPtr, lel);
    184.  
    185.             var rawInputDevices = new[]
    186.             {
    187.                new RAWINPUTDEVICE()
    188.                {
    189.                    usUsagePage = HID_USAGE_PAGE_GENERIC,
    190.                    usUsage = HID_USAGE_GENERIC_KEYBOARD,
    191.                    dwFlags = 0,
    192.                    hwndTarget = hostHWND
    193.                }
    194.             };
    195.  
    196.             if (!RegisterRawInputDevices(rawInputDevices, 1, Marshal.SizeOf(typeof(RAWINPUTDEVICE))))
    197.             {
    198.                 var lastError = Marshal.GetLastWin32Error();
    199.                 throw new Win32Exception(lastError, "Failed to register raw input devices");
    200.             }
    201.         }
    202.     }
    203. }
    204.  

    @Tautvydas-Zilys Do you have any insights as to what may be going wrong?
     
    Last edited: Apr 8, 2022
  18. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    Yeah you don't define your own WndProc delegate so it ends up using the wrong definition.
     
  19. dyu25

    dyu25

    Joined:
    Oct 29, 2021
    Posts:
    11
    I'm a little new to this, how would I go about defining my WndProc delegate? Could you provide a small code example?
     
  20. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    The example I posted had this line:

    Code (csharp):
    1. private new delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    You seem to have removed it.
     
  21. dyu25

    dyu25

    Joined:
    Oct 29, 2021
    Posts:
    11
    It's there on line 53, I didn't remove it. So what is causing the problem?
     
  22. dyu25

    dyu25

    Joined:
    Oct 29, 2021
    Posts:
    11
    @Tautvydas-Zilys Is the line 53 correctly placed in the code? Otherwise could you help out with what could be causing the problem?
    Thanks.
     
  23. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    Sorry, I missed your reply. And right, I somehow missed that line when reading the code.

    Could you throw together a small repro showing the problem? The sample I have on my machine seems to work fine. Something is going wrong with that delegate invocation but I can't tell what just looking at the code.
     
  24. dyu25

    dyu25

    Joined:
    Oct 29, 2021
    Posts:
    11
    So I get the error:
    Code (CSharp):
    1. System.ArgumentException: 'Object of type 'System.Int32' cannot be converted to type 'Interop+User32+WM'.'
    2.  
    Which pops up for this line (line 168 in full code):
    Code (CSharp):
    1. return (IntPtr)originalWndProc.DynamicInvoke(new object[] { hwnd, msg, wParam, lParam });
    Here is my full code:
    Code (CSharp):
    1. using System;
    2. using System.ComponentModel;
    3. using System.Diagnostics;
    4. using System.Runtime.InteropServices;
    5. using System.Threading;
    6. using System.Windows;
    7. using System.Windows.Interop;
    8.  
    9. namespace Telekinesis.UnityApp
    10. {
    11.     /// <summary>
    12.     /// Interaction logic for MainWindow.xaml
    13.     /// </summary>
    14.     public partial class MainWindow : Window
    15.     {
    16.         [DllImport("User32.dll")]
    17.         static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
    18.  
    19.         internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
    20.         [DllImport("user32.dll")]
    21.         internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
    22.  
    23.         [DllImport("user32.dll")]
    24.         static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    25.  
    26.         private Process _process;
    27.         private IntPtr _unityHWND = IntPtr.Zero;
    28.         bool initialized = false;
    29.  
    30.         private const int GWLP_WNDPROC = -4;
    31.         private const int WM_INPUT = 0x00FF;
    32.         private const int WM_ACTIVATE = 0x0006;
    33.         private readonly IntPtr WA_ACTIVE = new IntPtr(1);
    34.         private readonly IntPtr WA_INACTIVE = new IntPtr(0);
    35.  
    36.         // To capture key presses and send to unity
    37.         [StructLayout(LayoutKind.Sequential)]
    38.         public struct RAWINPUTDEVICE
    39.         {
    40.             public ushort usUsagePage;
    41.             public ushort usUsage;
    42.             public uint dwFlags;
    43.             public IntPtr hwndTarget;
    44.         }
    45.         const ushort HID_USAGE_PAGE_GENERIC = 0x01;
    46.         const ushort HID_USAGE_GENERIC_KEYBOARD = 0x06;
    47.  
    48.         [DllImport("user32.dll", SetLastError = true)]
    49.         public static extern bool RegisterRawInputDevices([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] RAWINPUTDEVICE[] pRawInputDevices, int uiNumDevices, int cbSize);
    50.         [DllImport("user32.dll", SetLastError = true)]
    51.         private static extern IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
    52.  
    53.         private new delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    54.  
    55.         private MulticastDelegate originalWndProc;
    56.         private WndProc myWndProc;
    57.  
    58.  
    59.         public MainWindow()
    60.         {
    61.             InitializeComponent();
    62.  
    63.             System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
    64.             dispatcherTimer.Tick += attemptInit;
    65.             dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
    66.             dispatcherTimer.Start();
    67.  
    68.         }
    69.  
    70.         void attemptInit(object sender, EventArgs e)
    71.         {
    72.  
    73.             if (initialized)
    74.                 return;
    75.  
    76.             //HwndSource source = (HwndSource)HwndSource.FromVisual(UnityGrid);
    77.  
    78.             //IntPtr hWnd = source.Handle;
    79.             IntPtr unityHandle = UnityGrid.Handle;
    80.  
    81.             try
    82.             {
    83.                 _process = new Process();
    84.                 _process.StartInfo.FileName = "UnityWindow.exe";
    85.                 _process.StartInfo.Arguments = "-parentHWND " + unityHandle.ToInt32() + " " + Environment.CommandLine;
    86.                 _process.StartInfo.UseShellExecute = true;
    87.                 _process.StartInfo.CreateNoWindow = true;
    88.  
    89.                 _process.Start();
    90.  
    91.                 _process.WaitForInputIdle();
    92.                 // Doesn't work for some reason ?!
    93.                 //hWnd = _process.MainWindowHandle;
    94.                 EnumChildWindows(unityHandle, WindowEnum, IntPtr.Zero);
    95.                 SetupRawInput(unityHandle);
    96.  
    97.                 Debug.WriteLine("Unity HWND: 0x" + _unityHWND.ToString("X8"));
    98.  
    99.                 UnityContentResize(this, EventArgs.Empty);
    100.                 PresentationSource s = PresentationSource.FromVisual(Application.Current.MainWindow);
    101.  
    102.                 initialized = true;
    103.             }
    104.             catch (Exception ex)
    105.             {
    106.                 MessageBox.Show(ex.Message + ".\nCheck if Container.exe is placed next to UnityGame.exe.");
    107.             }
    108.         }
    109.  
    110.         private void ActivateUnityWindow()
    111.         {
    112.             SendMessage(_unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
    113.         }
    114.  
    115.         private void DeactivateUnityWindow()
    116.         {
    117.             SendMessage(_unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
    118.         }
    119.  
    120.         private int WindowEnum(IntPtr hwnd, IntPtr lparam)
    121.         {
    122.             _unityHWND = hwnd;
    123.             ActivateUnityWindow();
    124.             return 0;
    125.         }
    126.  
    127.         private void UnityContentResize(object sender, EventArgs e)
    128.         {
    129.             MoveWindow(_unityHWND, 0, 0, (int)UnityGrid.Width, (int)UnityGrid.Height, true);
    130.             Debug.WriteLine("RESIZED UNITY WINDOW TO: " + (int)UnityGrid.Width + "x" + (int)UnityGrid.Height);
    131.             ActivateUnityWindow();
    132.         }
    133.  
    134.         // Close Unity application
    135.         private void ApplicationExit(object sender, EventArgs e)
    136.         {
    137.             try
    138.             {
    139.                 _process.CloseMainWindow();
    140.  
    141.                 Thread.Sleep(1000);
    142.                 while (!_process.HasExited)
    143.                     _process.Kill();
    144.             }
    145.             catch (Exception)
    146.             {
    147.             }
    148.         }
    149.  
    150.         private void UnityContentActivate(object sender, EventArgs e)
    151.         {
    152.             ActivateUnityWindow();
    153.         }
    154.  
    155.         private void UnityContentDeactivate(object sender, EventArgs e)
    156.         {
    157.             DeactivateUnityWindow();
    158.         }
    159.  
    160.         private IntPtr HookWndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
    161.         {
    162.             if (msg == WM_INPUT)
    163.             {
    164.                 SendMessage(_unityHWND, msg, wParam, lParam);
    165.                 return IntPtr.Zero;
    166.             }
    167.  
    168.             return (IntPtr)originalWndProc.DynamicInvoke(new object[] { hwnd, msg, wParam, lParam });
    169.         }
    170.  
    171.         private void SetupRawInput(IntPtr hostHWND)
    172.         {
    173.             myWndProc = HookWndProc;
    174.  
    175.             var originalWndProcPtr = SetWindowLongPtrW(hostHWND, GWLP_WNDPROC, Marshal.GetFunctionPointerForDelegate(myWndProc));
    176.             if (originalWndProcPtr == null)
    177.             {
    178.                 var errorCode = Marshal.GetLastWin32Error();
    179.                 throw new Win32Exception(errorCode, "Failed to overwrite the original wndproc");
    180.             }
    181.  
    182.             Type lel = typeof(MulticastDelegate);
    183.             originalWndProc = (MulticastDelegate)Marshal.GetDelegateForFunctionPointer(originalWndProcPtr, lel);
    184.  
    185.             var rawInputDevices = new[]
    186.             {
    187.                new RAWINPUTDEVICE()
    188.                {
    189.                    usUsagePage = HID_USAGE_PAGE_GENERIC,
    190.                    usUsage = HID_USAGE_GENERIC_KEYBOARD,
    191.                    dwFlags = 0,
    192.                    hwndTarget = hostHWND
    193.                }
    194.             };
    195.  
    196.             if (!RegisterRawInputDevices(rawInputDevices, 1, Marshal.SizeOf(typeof(RAWINPUTDEVICE))))
    197.             {
    198.                 var lastError = Marshal.GetLastWin32Error();
    199.                 throw new Win32Exception(lastError, "Failed to register raw input devices");
    200.             }
    201.         }
    202.     }
    203. }
    204.  
     
  25. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    That does not seem like a complete sample, and does not compile. Specifically, the definition for "UnityGrid" variable is missing. Now, having realized that you are building this in MainWindow class instead of HwndHost class, I suspect that the issue is due to UnityGrid not being a HwndHost.
     
  26. dyu25

    dyu25

    Joined:
    Oct 29, 2021
    Posts:
    11
    Sorry about the incomplete information, here is the code in MainWindow.xaml:
    Code (CSharp):
    1. <!-- Unity Grid -->
    2.         <Grid x:Name="UnityGrid1" Grid.Row="1" Grid.Column="0">
    3.             <WindowsFormsHost>
    4.                 <wf:Panel x:Name="UnityGrid" Resize="UnityContentResize"/>
    5.             </WindowsFormsHost>
    6.         </Grid>
    7.     </Grid>
    This is a WPF application and this was the only way that worked to integrate unity into WPF. What can I change here to get it to work?
     
  27. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    Try this: https://docs.microsoft.com/en-us/do...2-control-in-wpf?view=netframeworkdesktop-4.8

    Launch Unity and find its window from within the "BuildWindowCore" method. This is the preferred way to host native application within WPF.

    Alternatively, you'll need to somehow box your wndproc forwarding arguments so that reflection invocation is happy - that might involve using more reflection to convert "int32" to "Interop.User32.WM" type.
     
  28. terryfarnham

    terryfarnham

    Joined:
    Apr 28, 2022
    Posts:
    1
    I am using an HwndHost from WPF to host my Unity application. Everything is working quite well. I did have to implement forwarding WM_INPUT messages from the host container window directly to the unity window in order to get inputs to work properly. However, I have one pesky problem. If I hold down a key on the keyboard, then change focus to a separate WPF window, the key within the Unity window remains "stuck" in the down position. I don't know what could be the proper solution for this. I have tried sending a number of different windows messages to unity without success. My latest attempt has been to directly send a key up message using:

    Code (CSharp):
    1. public static void SendKeyUp(IntPtr hwnd, RAWINPUT down)
    2. {
    3.     RAWINPUT up = down;
    4.     up.Data.Keyboard.Message = WM_KEYUP;
    5.     up.Data.Keyboard.Flags = 3;
    6.  
    7.     byte[] data = new byte[Marshal.SizeOf(up)];
    8.     GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
    9.     Marshal.StructureToPtr(up, handle.AddrOfPinnedObject(), false);
    10.     SendMessage(hwnd, WM_INPUT, IntPtr.Zero, handle.AddrOfPinnedObject());
    11.     handle.Free();
    12. }
    Where "down" is a copy of the key down structure that was originally received. However, Unity reports the following in response to this message:

    <Raw Input> Failed to get raw input data: The handle is invalid.

    I am hoping someone can give me some advice on what options I might have to solve this, or better options for solving the overall problem.