WinForm embedded Unity (Socket communication)

Recently, there is a need to realize the interaction between WinForm and Unity, that is, communication. I have checked several solutions that use the UnityWebPlayer Control component (I didn’t try it due to version issues). There are also solutions that package Winform into a dll dynamic link library and then introduce it into Unity. There is one that packages Unity.exe and then embeds it in Winform. Both of the latter two are acceptable.

1. Winform is packaged into a dll dynamic link library and then introduced into unity.

        ​ ​ 1. In short, draw the interface first (roughly something like it)

        ​​​​ 2.Backend code (I use winform as the server here, and unity as the client to connect and implement socket communication)

                2.1 WinForm: Create the SocketServer class

    public class SocketServer
    {
        public Socket serverSocket;
        public Socket clientSocket;

        private string _ip = string.Empty;
        private int _port = 12345;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="ip">监听的IP</param>
        /// <param name="port">监听的端口</param>
        public SocketServer(string ip, int port)
        {
            this._ip = ip;
            this._port = port;
        }
        public SocketServer(int port)
        {
            this._ip = "0.0.0.0";
            this._port = port;
        }
        static List<Socket> userOnline = new List<Socket>();
        private static readonly object textsLock;
        public Queue<string> texts = new Queue<string>();
        public void StartListen()
        {
            try
            {
                //1.0 实例化套接字(IP4寻找协议,流式协议,TCP协议)
                serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //2.0 创建IP对象
                IPAddress address = IPAddress.Parse(_ip);
                //3.0 创建网络端口,包括ip和端口
                IPEndPoint endPoint = new IPEndPoint(address, _port);
                //4.0 绑定套接字
                serverSocket.Bind(endPoint);
                //5.0 设置最大连接数量
                serverSocket.Listen(int.MaxValue);
                //MessageBox.Show(serverSocket.LocalEndPoint.ToString());
                //6.0 开始监听
                Thread thread = new Thread(ListenClientConnect);
                thread.IsBackground = true;
                thread.Start();

            }
            catch (Exception ex)
            {

            }
        }
        /// <summary>
        /// 监听客户端连接
        /// </summary>
        private void ListenClientConnect()
        {
            try
            {
                while (true)
                {
                    //阻塞当前的线程直到某个客户端连接,连接上则返回一个新的Socket(即客户端)
                    clientSocket = serverSocket.Accept();
                    userOnline.Add(clientSocket);//每连接上一个客户端,就将该客户端添加至客户端列表
                    Thread thread = new Thread(ReceiveMessage);//每连接上一个客户端,启动一个线程(用于接受客户端信息)
                    thread.Start(clientSocket);
                }
            }
            catch (Exception)
            {

            }
        }
        /// <summary>
        /// 接收客户端消息
        /// </summary>
        /// <param name="socket">来自客户端的socket</param>
        private void ReceiveMessage(object socket)
        {
            Socket clientSocket = (Socket)socket;
            byte[] buffer = new byte[1024 * 1024 * 2];
            while (true)
            {
                try
                {
                    //获取从客户端发来的数据
                    int length = clientSocket.Receive(buffer);
                    lock (textsLock)//如果textsLock没有被锁上,则可执行内部代码并主动锁上(PS:有序的执行,避免资源冲突)
                    {
                        texts.Enqueue(Encoding.UTF8.GetString(buffer, 0, length));//接收到消息则存入texts
                    }
                    //Console.WriteLine("接收客户端{0},消息{1}", clientSocket.RemoteEndPoint.ToString(), Encoding.UTF8.GetString(buffer, 0, length));
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    clientSocket.Shutdown(SocketShutdown.Both);
                    clientSocket.Close();
                    break;
                }
            }
        }
    }

                ​ ​ ​ ​ ​ 2.2 Winform: Start the service when the Form is loaded, and the thread monitors the client (unity) connection 

        private void Form1_Load(object sender, EventArgs e)
        {
            socketServer = new SocketServer(12345);
            socketServer.StartListen();
            context = SynchronizationContext.Current;//同步上下文用
            Thread thread = new Thread(ShowMessage);//开启用于接收消息的线程
            thread.IsBackground = true;
            thread.Start();
        }
        private void button4_Click(object sender, EventArgs e)
        {
            Socket clientSocket = socketServer.clientSocket;//当前连接的客户端
            if (clientSocket != null)//如果客户端对象不为空
            {
                clientSocket.Send(Encoding.UTF8.GetBytes(textBox1.Text));//发送消息
            }
        }
        private void ShowMessage()//用于接收消息(线程启动)
        {
            while (true)
            {
                Thread.Sleep(200);
                if (socketServer.texts.Count > 0)
                {
                    //同步上下文显示消息在TextBox1
                    context.Send(e =>
                    {
                        textBox1.Text = socketServer.texts.Dequeue();
                    }, null);
                }
            }
        }

                  2.3 Unity: Creates the SocketClient class

public class SocketClient
{
    private string _ip = string.Empty;
    private int _port = 12345;
    public Socket clientSocket = null;
    SynchronizationContext context;//同步上下文
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="ip">连接服务器的IP</param>
    /// <param name="port">连接服务器的端口</param>
    public SocketClient(string ip, int port)
    {
        this._ip = ip;
        this._port = port;
    }
    public SocketClient(int port)
    {
        this._ip = "127.0.0.1";
        this._port = port;
    }

    /// <summary>
    /// 开启服务,连接服务端
    /// </summary>
    public void StartClient(GameObject gameObject)
    {
        clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        IPAddress address = IPAddress.Parse(_ip);
        IPEndPoint endPoint = new IPEndPoint(address, _port);
        clientSocket.Connect(endPoint);
        Debug.Log("連接成功");

        Thread thread = new Thread(new ParameterizedThreadStart(ReceiveMessage));
        thread.Start(gameObject);
        context = SynchronizationContext.Current;
    }
    public void ReceiveMessage(object gameObject)
    {
        byte[] buffer = new byte[1024 * 1024 * 2];
        while (true)
        {
            //阻塞当前线程直到收到消息
            int length = clientSocket.Receive(buffer);
            string text = Encoding.UTF8.GetString(buffer, 0, length);
            //直接写 cs.GetComponent<Text>().text = text; 会报错提示只能在主线程调用
            //同步上下文
            context.Send(e =>
            {
                ((GameObject)e).GetComponent<Text>().text = text;
            }, gameObject);

        }
    }
}

                ​ ​ ​ ​ ​ 2.4 Then create a script (csMain) and bind it to an empty object

                 csMain code:

using System.Windows.Forms;//免得你们找不到 C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.1\System.Windows.Forms.dll
using ChangeSite;

    public static Form form;
    void Start()
    {
        form = new Form1();
        form.Show();
        socketClient = new SocketClient(12345);
        socketClient.StartClient(this.cs);
    }
    private void OnDestroy()
    {
        form.Close();
    }

2. Package the Unity project into an exe program and then embed it in Winform

        The project file after Unity packages the exe is as follows:

        Then use code to start the exe in winform and embed it into the panel.

        

        Unity is packaged into a client for connection. The code is consistent with the above. The following is Winform as the server. It monitors the client's connection in the loading method. If the connection is successful, communication can be achieved. The specific code is as follows:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Common;

namespace ChangeSite
{
    public partial class Form1 : Form
    {
        private SocketServer socketServer;
        EmbeddedExeTool fr = null;
        private SynchronizationContext context;
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            socketServer = new SocketServer(12345);
            socketServer.StartListen();
            context = SynchronizationContext.Current;//同步上下文用
            Thread thread = new Thread(ShowMessage);//开启用于接收消息的线程
            thread.IsBackground = true;
            thread.Start();
        }
        private void btn_start_Click(object sender, EventArgs e)
        {
            panel1.Controls.Clear();
            if (fr != null && fr.IsStarted)//如果重新啟動(即fr不為空),則關閉
            {
                fr.Stop();
            }
            string path = Directory.GetCurrentDirectory() + @"../../../\Unity\ChangeSite\ChangeSite.exe";
            fr = new EmbeddedExeTool(panel1, "");
            fr.Start(path);
        }
        private void btn_send_Click(object sender, EventArgs e)
        {
            Socket clientSocket = socketServer.clientSocket;//当前连接的客户端
            if (clientSocket != null)//如果客户端对象不为空
            {
                clientSocket.Send(Encoding.UTF8.GetBytes(textBox1.Text));//发送消息
            }
        }
        private void ShowMessage()//用于接收消息
        {
            while (true)
            {
                Thread.Sleep(200);
                if (socketServer.texts.Count > 0)
                {
                    //同步上下文显示消息在TextBox1
                    //MessageBox.Show(Thread.CurrentThread.ManagedThreadId + "");
                    context.Send(e =>
                    {
                        textBox1.Text = socketServer.texts.Dequeue();
                    }, null);
                }
                //if (socketServer.clientSocket != null)
                //{
                //    byte[] buffer = new byte[1024 * 1024 * 2];
                //    int length = socketServer.clientSocket.Receive(buffer);
                //    //lock (textsLock)//如果textsLock没有被锁上,则可执行内部代码并主动锁上(PS:有序的执行,避免资源冲突
                //    if (length != 0)
                //    {
                //        //同步上下文显示消息在TextBox1
                //        context.Send(e =>
                //        {
                //            textBox1.Text = Encoding.UTF8.GetString(buffer, 0, length);
                //        }, null);
                //    }
                //}
            }
        }

        /// <summary>
        /// 嵌入外部exe
        /// </summary>
        public class EmbeddedExeTool
        {
            EventHandler appIdleEvent = null;
            Control ParentCon = null;
            string strGUID = "";

            public EmbeddedExeTool(Control C, string Titlestr)
            {
                appIdleEvent = new EventHandler(Application_Idle);
                ParentCon = C;
                strGUID = Titlestr;
            }

            /// <summary>
            /// 将属性<code>AppFilename</code>指向的应用程序打开并嵌入此容器
            /// </summary>
            public IntPtr Start(string FileNameStr)
            {
                if (m_AppProcess != null)
                {
                    Stop();
                }
                try
                {
                    ProcessStartInfo info = new ProcessStartInfo(FileNameStr);
                    info.UseShellExecute = true;
                    info.WindowStyle = ProcessWindowStyle.Minimized;
                    m_AppProcess = System.Diagnostics.Process.Start(info);
                    m_AppProcess.WaitForInputIdle();
                    Application.Idle += appIdleEvent;
                    Application.ApplicationExit += m_AppProcess_Exited;

                }
                catch
                {
                    if (m_AppProcess != null)
                    {
                        if (!m_AppProcess.HasExited)
                            m_AppProcess.Kill();
                        m_AppProcess = null;
                    }
                }
                return m_AppProcess.Handle;

            }
            /// <summary>
            /// 确保应用程序嵌入此容器
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            void Application_Idle(object sender, EventArgs e)
            {
                if (this.m_AppProcess == null || this.m_AppProcess.HasExited)
                {
                    this.m_AppProcess = null;
                    Application.Idle -= appIdleEvent;
                    return;
                }

                while (m_AppProcess.MainWindowHandle == IntPtr.Zero)
                {
                    Thread.Sleep(100);
                    m_AppProcess.Refresh();
                }
                Application.Idle -= appIdleEvent;

                EmbedProcess(m_AppProcess, ParentCon);
            }
            /// <summary>
            /// 应用程序结束运行时要清除这里的标识
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            void m_AppProcess_Exited(object sender, EventArgs e)
            {
                if (m_AppProcess != null && !m_AppProcess.HasExited)
                {
                    m_AppProcess.Kill();
                    m_AppProcess = null;
                }
            }
            /// <summary>
            /// 将属性<code>AppFilename</code>指向的应用程序关闭
            /// </summary>
            public void Stop()
            {
                if (m_AppProcess != null)// && m_AppProcess.MainWindowHandle != IntPtr.Zero)
                {
                    try
                    {
                        if (!m_AppProcess.HasExited)
                        {
                            m_AppProcess.Kill();
                            Application.ApplicationExit -= m_AppProcess_Exited;//每次重新啟動都減一次調用
                        }
                    }
                    catch (Exception)
                    {
                    }
                    m_AppProcess = null;
                }
            }


            #region 属性
            /// <summary>
            /// application process
            /// </summary>
            Process m_AppProcess = null;

            /// <summary>
            /// 标识内嵌程序是否已经启动
            /// </summary>
            public bool IsStarted { get { return (this.m_AppProcess != null); } }

            #endregion 属性

            #region Win32 API
            [DllImport("user32.dll", EntryPoint = "GetWindowThreadProcessId", SetLastError = true,
                 CharSet = CharSet.Unicode, ExactSpelling = true,
                 CallingConvention = CallingConvention.StdCall)]
            private static extern long GetWindowThreadProcessId(long hWnd, long lpdwProcessId);

            [DllImport("user32.dll", SetLastError = true)]
            private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

            [DllImport("user32.dll", SetLastError = true)]
            private static extern long SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

            [DllImport("user32.dll", EntryPoint = "GetWindowLongA", SetLastError = true)]
            private static extern long GetWindowLong(IntPtr hwnd, int nIndex);

            public static IntPtr SetWindowLong(HandleRef hWnd, int nIndex, int dwNewLong)
            {
                if (IntPtr.Size == 4)
                {
                    return SetWindowLongPtr32(hWnd, nIndex, dwNewLong);
                }
                return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
            }
            [DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)]
            public static extern IntPtr SetWindowLongPtr32(HandleRef hWnd, int nIndex, int 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)]
            private static extern long SetWindowPos(IntPtr hwnd, long hWndInsertAfter, long x, long y, long cx, long cy, long wFlags);

            [DllImport("user32.dll", SetLastError = true)]
            private static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);

            [DllImport("user32.dll", EntryPoint = "PostMessageA", SetLastError = true)]
            private static extern bool PostMessage(IntPtr hwnd, uint Msg, uint wParam, uint lParam);

            [DllImport("user32.dll", SetLastError = true)]
            private static extern IntPtr GetParent(IntPtr hwnd);

            [DllImport("user32.dll", EntryPoint = "ShowWindow", SetLastError = true)]
            static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

            private const int SWP_NOOWNERZORDER = 0x200;
            private const int SWP_NOREDRAW = 0x8;
            private const int SWP_NOZORDER = 0x4;
            private const int SWP_SHOWWINDOW = 0x0040;
            private const int WS_EX_MDICHILD = 0x40;
            private const int SWP_FRAMECHANGED = 0x20;
            private const int SWP_NOACTIVATE = 0x10;
            private const int SWP_ASYNCWINDOWPOS = 0x4000;
            private const int SWP_NOMOVE = 0x2;
            private const int SWP_NOSIZE = 0x1;
            private const int GWL_STYLE = (-16);
            private const int WS_VISIBLE = 0x10000000;
            private const int WM_CLOSE = 0x10;
            private const int WS_CHILD = 0x40000000;

            private const int SW_HIDE = 0; //{隐藏, 并且任务栏也没有最小化图标}
            private const int SW_SHOWNORMAL = 1; //{用最近的大小和位置显示, 激活}
            private const int SW_NORMAL = 1; //{同 SW_SHOWNORMAL}
            private const int SW_SHOWMINIMIZED = 2; //{最小化, 激活}
            private const int SW_SHOWMAXIMIZED = 3; //{最大化, 激活}
            private const int SW_MAXIMIZE = 3; //{同 SW_SHOWMAXIMIZED}
            private const int SW_SHOWNOACTIVATE = 4; //{用最近的大小和位置显示, 不激活}
            private const int SW_SHOW = 5; //{同 SW_SHOWNORMAL}
            private const int SW_MINIMIZE = 6; //{最小化, 不激活}
            private const int SW_SHOWMINNOACTIVE = 7; //{同 SW_MINIMIZE}
            private const int SW_SHOWNA = 8; //{同 SW_SHOWNOACTIVATE}
            private const int SW_RESTORE = 9; //{同 SW_SHOWNORMAL}
            private const int SW_SHOWDEFAULT = 10; //{同 SW_SHOWNORMAL}
            private const int SW_MAX = 10; //{同 SW_SHOWNORMAL}

            #endregion Win32 API

            /// <summary>
            /// 将指定的程序嵌入指定的控件
            /// </summary>
            private void EmbedProcess(Process app, Control control)
            {
                // Get the main handle
                if (app == null || app.MainWindowHandle == IntPtr.Zero || control == null) return;
                try
                {
                    // Put it into this form
                    SetParent(app.MainWindowHandle, control.Handle);
                }
                catch (Exception)
                { }
                try
                {
                    // Remove border and whatnot               
                    SetWindowLong(new HandleRef(this, app.MainWindowHandle), GWL_STYLE, WS_VISIBLE);
                    SendMessage(app.MainWindowHandle, WM_SETTEXT, IntPtr.Zero, strGUID);
                }
                catch (Exception)
                { }
                try
                {
                    // Move the window to overlay it on this window
                    MoveWindow(app.MainWindowHandle, 0, 0, control.Width, control.Height, true);
                }
                catch (Exception)
                { }
            }

            [DllImport("User32.dll", EntryPoint = "SendMessage")]
            private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, string lParam);

            const int WM_SETTEXT = 0x000C;
        }
    }
}

 And the effect is also very nice!

Guess you like

Origin blog.csdn.net/qq_51502150/article/details/123072320