unity发布exe嵌入WPF(鼠标键盘可用)
Visual Studio 版本 | 2019 |
---|---|
操作系统 | Windows10 |
一. 新建WPF项目
- 打开VS新建项目
- 新建好以后,想要将unity发布的exe嵌入到WPF,并且鼠标键盘可用的话,需要在WPF项目中使用winform的UserControl控件作为载体,所以需要引用下图两个DLL。如果WindowsFormsIntegration.dll不能引用,请看文章:如何在WPF中引用Windows.System.Forms.Integration
二. 在WPF中的.xaml文件中添加以下内容
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
<WindowsFormsHost Margin="67,0,0,0" Grid.RowSpan="2">
<wf:Panel x:Name="host"/>
</WindowsFormsHost>
三. 将unity的exe程序嵌入到winform控件中
- 添加一个实体类。造函数会把WinForm控件以参数的形式传入,然后此控件需要订阅Resize事件,该事件处理器进行了exe窗体的重新激活
public class AppContainer
{
private System.Windows.Forms.Panel _hostPanel;
private readonly ManualResetEvent _eventDone = new ManualResetEvent(false);
private Process _process = null;
internal IntPtr _embededWindowHandle;
public AppContainer(System.Windows.Forms.Panel panel)
{
this._hostPanel = panel;
this._hostPanel.Resize += _hostPanel_Resize;
}
private void _hostPanel_Resize(object sender, EventArgs e)
{
SetBounds();
}
public void ActivateWindow()
{
if (_process == null)
return;
if (_process.MainWindowHandle == IntPtr.Zero)
return;
Win32Api.SendMessage(_process.MainWindowHandle, Win32Api.WM_ACTIVATE, Win32Api.WA_ACTIVE, IntPtr.Zero);
}
public void SetBounds()
{
SetBounds(_hostPanel.Width, _hostPanel.Height);
}
public void SetBounds(int width, int height)
{
if (_process == null)
return;
if (_process.MainWindowHandle == IntPtr.Zero)
return;
if (width <= 0 || height <= 0)
return;
Win32Api.MoveWindow(_process.MainWindowHandle, 0, 0, width, height, true);
ActivateWindow();//激活
}
public bool StartAndEmbedProcess(string processPath)
{
if (null != _process)
return true;
var isStartAndEmbedSuccess = false;
_eventDone.Reset();
// Start the process
ProcessStartInfo info = new ProcessStartInfo(processPath);
info.WindowStyle = ProcessWindowStyle.Maximized;//默认最大化,不弹出界面。
info.Arguments = $"-popupwindow";//Unity的命令行参数
_process = Process.Start(info);
if (_process == null)
{
return false;
}
// Wait for process to be created and enter idle condition
_process.WaitForInputIdle();
// Get the main handle
var thread = new Thread(() =>
{
while (true)
{
if (_process.MainWindowHandle != (IntPtr)0)
{
_eventDone.Set();
break;
}
Thread.Sleep(10);
}
});
thread.Start();
//嵌入进程
if (_eventDone.WaitOne(10000))
{
isStartAndEmbedSuccess = EmbedApp(_process);
if (!isStartAndEmbedSuccess)
{
CloseApp(_process);
}
}
return isStartAndEmbedSuccess;
}
public bool EmbedExistProcess(Process process)
{
_process = process;
return EmbedApp(process);
}
/// <summary>
/// 将外进程嵌入到当前程序
/// </summary>
/// <param name="process"></param>
private bool EmbedApp(Process process)
{
//是否嵌入成功标志,用作返回值
var isEmbedSuccess = false;
//外进程句柄
var processHwnd = process.MainWindowHandle;
//容器句柄
var panelHwnd = _hostPanel.Handle;
if (processHwnd != (IntPtr)0 && panelHwnd != (IntPtr)0)
{
//把本窗口句柄与目标窗口句柄关联起来
var setTime = 0;
while (!isEmbedSuccess && setTime < 50)
{
// Put it into this form
isEmbedSuccess = Win32Api.SetParent(processHwnd, panelHwnd) != 0;
Thread.Sleep(10);
setTime++;
}
// Remove border and whatnot
//Win32Api.SetWindowLong(processHwnd, Win32Api.GWL_STYLE, Win32Api.WS_CHILDWINDOW | Win32Api.WS_CLIPSIBLINGS | Win32Api.WS_CLIPCHILDREN | Win32Api.WS_VISIBLE);
SetBounds();
//Move the window to overlay it on this window
//Win32Api.MoveWindow(_process.MainWindowHandle, 0, 0, (int)ActualWidth, (int)ActualHeight, true);
}
if (isEmbedSuccess)
{
_embededWindowHandle = _process.MainWindowHandle;
}
return isEmbedSuccess;
}
/// <summary>
/// 关闭进程
/// </summary>
/// <param name="process"></param>
private void CloseApp(Process process)
{
if (process != null && !process.HasExited)
{
process.Kill();
}
}
public void CloseProcess()
{
CloseApp(_process);
_process = null;
}
}
- 从本质上来讲,这是一个Win32应用程序嵌入到WPF应用程序的问题,由于两者窗口绘制原理的差异,就必需依靠Win32API,也就是大家常常会提到的user32.dll。C#中使用Win32Api与C++略有不同,需要使用DllInput,对引用user32.dll进行了简单封装,代码如下:
public class Win32Api
{
public const int WM_KEYDOWN = 0x0100;
[DllImport("User32.dll", EntryPoint = "PostMessage")]
public static extern int PostMessage(
IntPtr hWnd, // handle to destination window
int Msg, // message
IntPtr wParam, // first message parameter
IntPtr lParam // second message parameter
);
[DllImport("USER32", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
public static extern IntPtr GetSystemMenu(IntPtr WindowHandle, int bReset);
[DllImport("User32.dll")]
internal static extern int GetMenuItemCount(IntPtr hMenu);
[DllImport("USER32", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
public static extern int AppendMenuW(IntPtr MenuHandle, int Flags, int NewID, String Item);
[DllImport("USER32", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
public static extern int InsertMenuW(IntPtr hMenu, int Position, int Flags, int NewId, String Item);
[DllImport("User32.dll")]
internal static extern bool EnableMenuItem(IntPtr hMenu, Int32 uIDEnableItem, Int32 uEnable);
[DllImport("user32.dll")]
internal static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);
[DllImport("User32.dll")]
internal static extern int DrawMenuBar(IntPtr hWnd);
internal const UInt32 MF_ENABLED = 0x00000000;
internal const UInt32 MF_GRAYED = 0x00000001;
internal const UInt32 MF_DISABLED = 0x00000002;
internal const UInt32 MF_BYCOMMAND = 0x00000000;
internal const UInt32 MF_BYPOSITION = 0x00000400;
[DllImport("user32.dll")]
static extern IntPtr SetActiveWindow(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", EntryPoint = "GetWindowThreadProcessId", SetLastError = true,
CharSet = CharSet.Unicode, ExactSpelling = true,
CallingConvention = CallingConvention.StdCall)]
public static extern long GetWindowThreadProcessId(long hWnd, long lpdwProcessId);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern int SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll")]
public static extern uint GetWindowLong(IntPtr hwnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)]
public static extern uint SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Auto)]
public static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
public static extern long SetWindowPos(IntPtr hwnd, long hWndInsertAfter, long x, long y, long cx, long cy, long wFlags);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);
[DllImport("user32.dll", EntryPoint = "ostMessageA", SetLastError = true)]
public static extern bool PostMessage(IntPtr hwnd, uint Msg, uint wParam, uint lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetParent(IntPtr hwnd);
[DllImport("user32.dll", EntryPoint = "ShowWindow", SetLastError = true)]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
public const int SWP_NOOWNERZORDER = 0x200;
public const int SWP_NOREDRAW = 0x8;
public const int SWP_NOZORDER = 0x4;
public const int SWP_SHOWWINDOW = 0x0040;
public const int WS_EX_MDICHILD = 0x40;
public const int SWP_FRAMECHANGED = 0x20;
public const int SWP_NOACTIVATE = 0x10;
public const int SWP_ASYNCWINDOWPOS = 0x4000;
public const int SWP_NOMOVE = 0x2;
public const int SWP_NOSIZE = 0x1;
public const int GWL_STYLE = (-16);
public const int WS_VISIBLE = 0x10000000;
public const int WS_MAXIMIZE = 0x01000000;
public const int WS_BORDER = 0x00800000;
public const int WM_CLOSE = 0x10;
public const int WS_CHILD = 0x40000000;
public const int WS_POPUP = -2147483648;
public const int WS_CLIPSIBLINGS = 0x04000000;
public const int SW_HIDE = 0; //{隐藏, 并且任务栏也没有最小化图标}
public const int SW_SHOWNORMAL = 1; //{用最近的大小和位置显示, 激活}
public const int SW_NORMAL = 1; //{同 SW_SHOWNORMAL}
public const int SW_SHOWMINIMIZED = 2; //{最小化, 激活}
public const int SW_SHOWMAXIMIZED = 3; //{最大化, 激活}
public const int SW_MAXIMIZE = 3; //{同 SW_SHOWMAXIMIZED}
public const int SW_SHOWNOACTIVATE = 4; //{用最近的大小和位置显示, 不激活}
public const int SW_SHOW = 5; //{同 SW_SHOWNORMAL}
public const int SW_MINIMIZE = 6; //{最小化, 不激活}
public const int SW_SHOWMINNOACTIVE = 7; //{同 SW_MINIMIZE}
public const int SW_SHOWNA = 8; //{同 SW_SHOWNOACTIVATE}
public const int SW_RESTORE = 9; //{同 SW_SHOWNORMAL}
public const int SW_SHOWDEFAULT = 10; //{同 SW_SHOWNORMAL}
public const int SW_MAX = 10; //{同 SW_SHOWNORMAL}
public const int WM_SETTEXT = 0x000C;
public const int WM_ACTIVATE = 0x0006;
public static readonly IntPtr WA_ACTIVE = new IntPtr(1);
public static readonly IntPtr WA_INACTIVE = new IntPtr(0);
}
三. 在WPF项目的.xaml文件中的操作
- 在.xaml文件中添加
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Button Margin="0,59,727,-65" Content="打开unity程序" Click="Button_Click" HorizontalAlignment="Center" Width="65"/>
</Grid>
<WindowsFormsHost Margin="67,0,0,0" Grid.RowSpan="2">
<wf:Panel x:Name="host"/>
</WindowsFormsHost>
</Grid>
- 后台代码
private void Button_Click(object sender, RoutedEventArgs e)
{
AppContainer container = new AppContainer(this.host);
container.StartAndEmbedProcess(System.Windows.Forms.Application.StartupPath + @"\UnityApp\UnityApp.exe");
}
这里面UnityApp可以换成自己设定的名字,存放路径:bin/Debug/