加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c# – 在Winforms-Application中嵌入的Unity-Application上调用

发布时间:2020-12-15 22:49:26 所属栏目:百科 来源:网络整理
导读:参见英文答案 Send message from one program to another in Unity????????????????????????????????????2个 ???????????? Embed Unity3D app inside WPF application????????????????????????????????????1个 我目前正在为编辑器开发一个简单的原型.编辑器
参见英文答案 > Send message from one program to another in Unity????????????????????????????????????2个
>???????????? Embed Unity3D app inside WPF application????????????????????????????????????1个
我目前正在为编辑器开发一个简单的原型.编辑器将使用WinForms(或WPF,如果可能)提供主用户界面,并且还将嵌入Unity 2017独立应用程序以可视化数据并提供其他控制元素(例如缩放,旋转,滚动……).

感谢下面的这篇好文章,让一个嵌入式Unity应用程序在WinForms应用程序中运行非常容易.

https://forum.unity.com/threads/unity-3d-within-windows-application-enviroment.236213/

此外,还有一个简单的示例应用程序,您可以在此处访问:

Example.zip

遗憾的是,无论是示例还是我找到的任何帖子都没有回答一个非常基本的问题:如何从嵌入式Unity应用程序中的WinForms应用程序传递数据(或调用方法)(反之亦然)?

您的WinForms应用程序是否可以在Unity应用程序中简单地调用MonoBehaviour脚本或静态方法?如果是这样,怎么样?如果没有,那么什么是好的解决方法? Unity-to-WinForms通信如何能够作为回报呢?

更新:

使用Programmer(link)提到的重复页面来实现一个解决方案,该解决方案使用命名管道在WinForms和Unity应用程序之间进行通信.

两个应用程序都使用BackgroundWorkers,WinForms应用程序充当服务器(因为它在客户端启动之前首先启动并需要一个活动的连接侦听器),而嵌入式Unity应用程序充当客户端.

不幸的是,Unity-application抛出NotImplementedException,在创建NamedPipeClientStream(使用Unity 2017.3和Net 2.0(不是Net 2.0子集)测试)时声明“Mono不支持ACL”).在the post mentioned above的一些评论中已经报道了这个例外,但如果它已经解决,则不清楚.建议的解决方案“确保服务器在客户端尝试连接之前启动并运行”和“以管理模式启动”已经尝试过,但到目前为止都失败了.

解:

经过一些更多测试后,很明显,“Mono不支持ACL”异常导致在创建NamedPipeClientStream实例时使用TokenImpersonationLevel参数.将其更改为TokenImpersonationLevel.None解决了该问题.

这里是WinForms应用程序使用的代码,它充当命名管道服务器.确保在Unity应用程序客户端尝试连接之前执行此脚本!此外,请确保在启动服务器之前已构建并发布Unity应用程序.将Unity应用程序的Unity可执行文件放在WinForms-applications文件夹中,并将其命名为“Child.exe”.

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO.Pipes;

namespace Container
{
    public partial class MainForm : Form
    {
        [DllImport("User32.dll")]
        static extern bool MoveWindow(IntPtr handle,int x,int y,int width,int height,bool redraw);

        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")]
        static extern int SendMessage(IntPtr hWnd,int msg,IntPtr wParam,IntPtr lParam);

        /// <summary>
        /// A Delegate for the Update Log Method.
        /// </summary>
        /// <param name="text">The Text to log.</param>
        private delegate void UpdateLogCallback(string text);

        /// <summary>
        /// The Unity Application Process.
        /// </summary>
        private Process process;

        /// <summary>
        /// The Unity Application Window Handle.
        /// </summary>
        private IntPtr unityHWND = IntPtr.Zero;

        private const int WM_ACTIVATE = 0x0006;
        private readonly IntPtr WA_ACTIVE = new IntPtr(1);
        private readonly IntPtr WA_INACTIVE = new IntPtr(0);

        /// <summary>
        /// The Background Worker,which will send and receive Data.
        /// </summary>
        private BackgroundWorker backgroundWorker;

        /// <summary>
        /// A Named Pipe Stream,acting as the Server for Communication between this Application and the Unity Application.
        /// </summary>
        private NamedPipeServerStream namedPipeServerStream;



        public MainForm()
        {
            InitializeComponent();

            try
            {
                //Create Server Instance
                namedPipeServerStream = new NamedPipeServerStream("NamedPipeExample",PipeDirection.InOut,1);

                //Start Background Worker
                backgroundWorker = new BackgroundWorker();
                backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
                backgroundWorker.WorkerReportsProgress = true;

                backgroundWorker.RunWorkerAsync();

                //Start embedded Unity Application
                process = new Process();
                process.StartInfo.FileName = Application.StartupPath + "Child.exe";
                process.StartInfo.Arguments = "-parentHWND " + splitContainer.Panel1.Handle.ToInt32() + " " + Environment.CommandLine;
                process.StartInfo.UseShellExecute = true;
                process.StartInfo.CreateNoWindow = true;

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

                //Embed Unity Application into this Application
                EnumChildWindows(splitContainer.Panel1.Handle,WindowEnum,IntPtr.Zero);

                //Wait for a Client to connect
                namedPipeServerStream.WaitForConnection();
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }

        /// <summary>
        /// Activates the Unity Window.
        /// </summary>
        private void ActivateUnityWindow()
        {
            SendMessage(unityHWND,WM_ACTIVATE,WA_ACTIVE,IntPtr.Zero);
        }

        /// <summary>
        /// Deactivates the Unity Window.
        /// </summary>
        private void DeactivateUnityWindow()
        {
            SendMessage(unityHWND,WA_INACTIVE,IntPtr.Zero);
        }

        private int WindowEnum(IntPtr hwnd,IntPtr lparam)
        {
            unityHWND = hwnd;
            ActivateUnityWindow();
            return 0;
        }

        private void panel1_Resize(object sender,EventArgs e)
        {
            MoveWindow(unityHWND,splitContainer.Panel1.Width,splitContainer.Panel1.Height,true);
            ActivateUnityWindow();
        }

        /// <summary>
        /// Called,when this Application is closed. Tries to close the Unity Application and the Named Pipe as well.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_FormClosed(object sender,FormClosedEventArgs e)
        {
            try
            {
                //Close Connection
                namedPipeServerStream.Close();

                //Kill the Unity Application
                process.CloseMainWindow();

                Thread.Sleep(1000);

                while (process.HasExited == false)
                {
                    process.Kill();
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void MainForm_Activated(object sender,EventArgs e)
        {
            ActivateUnityWindow();
        }

        private void MainForm_Deactivate(object sender,EventArgs e)
        {
            DeactivateUnityWindow();
        }

        /// <summary>
        /// A simple Background Worker,which sends Data to the Client via a Named Pipe and receives a Response afterwards.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker_DoWork(object sender,DoWorkEventArgs e)
        {
            //Init
            UpdateLogCallback updateLog = new UpdateLogCallback(UpdateLog);
            string dataFromClient = null;

            try
            {
                //Don't pass until a Connection has been established
                while (namedPipeServerStream.IsConnected == false)
                {
                    Thread.Sleep(100);
                }

                //Created stream for reading and writing
                StreamString serverStream = new StreamString(namedPipeServerStream);

                //Send to Client and receive Response (pause using Thread.Sleep for demonstration Purposes)
                Invoke(updateLog,new object[] { "Send Data to Client: " + serverStream.WriteString("A Message from Server.") + " Bytes." });
                Thread.Sleep(1000);
                dataFromClient = serverStream.ReadString();
                Invoke(updateLog,new object[] { "Received Data from Server: " + dataFromClient });

                Thread.Sleep(1000);

                Invoke(updateLog,new object[] { "Send Data to Client: " + serverStream.WriteString("A small Message from Server.") + " Bytes." });
                Thread.Sleep(1000);
                dataFromClient = serverStream.ReadString();
                Invoke(updateLog,new object[] { "Send Data to Client: " + serverStream.WriteString("Another Message from Server.") + " Bytes." });
                Thread.Sleep(1000);
                dataFromClient = serverStream.ReadString();
                Invoke(updateLog,new object[] { "Send Data to Client: " + serverStream.WriteString("The final Message from Server.") + " Bytes." });
                Thread.Sleep(1000);
                dataFromClient = serverStream.ReadString();
                Invoke(updateLog,new object[] { "Received Data from Server: " + dataFromClient });
            }
            catch(Exception ex)
            {
                //Handle usual Communication Exceptions here - just logging here for demonstration and debugging Purposes
                Invoke(updateLog,new object[] { ex.Message });
            }
        }

        /// <summary>
        /// A simple Method,which writes Text into a Console / Log. Will be invoked by the Background Worker,since WinForms are not Thread-safe and will crash,if accessed directly by a non-main-Thread.
        /// </summary>
        /// <param name="text">The Text to log.</param>
        private void UpdateLog(string text)
        {
            lock (richTextBox_Console)
            {
                Console.WriteLine(text);
                richTextBox_Console.AppendText(Environment.NewLine + text);
            }
        }
    }
}

将此代码附加到Unity应用程序中的GameObject.还要确保将带有TextMeshProUGUI组件的GameObject(TextMeshPro-Asset,你会在你的资产商店中找到它)引用到’textObject’成员,这样应用程序就不会崩溃,你可以看到一些调试信息.
另外(如上所述)确保构建并释放Unity应用程序,将其命名为“Child.exe”并将其放在与WinForms应用程序相同的文件夹中.

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System;
using System.IO.Pipes;
using System.Security.Principal;
using Assets;
using System.ComponentModel;
using TMPro;



/// <summary>
/// A simple Example Project,which demonstrates Communication between WinForms-Applications and embedded Unity Engine Applications via Named Pipes.
/// 
/// This Code (Unity) is considered as the Client,which will receive Data from the WinForms-Server and send a Response in Return.
/// </summary>
public class SendAndReceive : MonoBehaviour
{
    /// <summary>
    /// A GameObject with an attached Text-Component,which serves as a simple Console.
    /// </summary>
    public GameObject textObject;

    /// <summary>
    /// The Background Worker,which will send and receive Data.
    /// </summary>
    private BackgroundWorker backgroundWorker;

    /// <summary>
    /// A Buffer needed to temporarely save Text,which will be shown in the Console.
    /// </summary>
    private string textBuffer = "";



    /// <summary>
    /// Use this for initialization.
    /// </summary>
    void Start ()
    {
        //Init the Background Worker to send and receive Data
        this.backgroundWorker = new BackgroundWorker();
        this.backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
        this.backgroundWorker.WorkerReportsProgress = true;
        this.backgroundWorker.RunWorkerAsync();
    }

    /// <summary>
    /// Update is called once per frame.
    /// </summary>
    void Update ()
    {
        //Update the Console for debugging Purposes
        lock (textBuffer)
        {
            if (string.IsNullOrEmpty(textBuffer) == false)
            {
                textObject.GetComponent<TextMeshProUGUI>().text = textObject.GetComponent<TextMeshProUGUI>().text + Environment.NewLine + textBuffer;
                textBuffer = "";
            }
        }
    }

    /// <summary>
    /// A simple Background Worker,which receives Data from the Server via a Named Pipe and sends a Response afterwards.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void backgroundWorker_DoWork(object sender,DoWorkEventArgs e)
    {
        try
        {
            //Init
            NamedPipeClientStream client = null;
            string dataFromServer = null;

            //Create Client Instance
            client = new NamedPipeClientStream(".","NamedPipeExample",PipeOptions.None,TokenImpersonationLevel.None);
            updateTextBuffer("Initialized Client");

            //Connect to Server
            client.Connect();
            updateTextBuffer("Connected to Server");

            //Created stream for Reading and Writing
            StreamString clientStream = new StreamString(client);

            //Read from Server and send Response (flush in between to clear the Buffer and fix some strange Issues I couldn't really explain,sorry)
            dataFromServer = clientStream.ReadString();
            updateTextBuffer("Received Data from Server: " + dataFromServer);
            client.Flush();
            updateTextBuffer("Sent Data back to Server: " + clientStream.WriteString("Some data from client.") + " Bytes.");

            dataFromServer = clientStream.ReadString();
            updateTextBuffer("Received Data from Server: " + dataFromServer);
            client.Flush();
            updateTextBuffer("Sent Data back to Server: " + clientStream.WriteString("Some more data from client.") + " Bytes.");

            dataFromServer = clientStream.ReadString();
            updateTextBuffer("Received Data from Server: " + dataFromServer);
            client.Flush();
            updateTextBuffer("Sent Data back to Server: " + clientStream.WriteString("A lot of more data from client.") + " Bytes.");

            dataFromServer = clientStream.ReadString();
            updateTextBuffer("Received Data from Server: " + dataFromServer);
            client.Flush();
            updateTextBuffer("Sent Data back to Server: " + clientStream.WriteString("Clients final message.") + " Bytes.");

            //Close client
            client.Close();
            updateTextBuffer("Done");
        }
        catch (Exception ex)
        {
            //Handle usual Communication Exceptions here - just logging here for demonstration and debugging Purposes
            updateTextBuffer(ex.Message + Environment.NewLine + ex.StackTrace.ToString() + Environment.NewLine + "Last Message was: " + textBuffer);
        }
    }

    /// <summary>
    /// A Buffer,which allows the Background Worker to save Texts,which may be written into a Log or Console by the Update-Loop
    /// </summary>
    /// <param name="text">The Text to save.</param>
    private void updateTextBuffer(string text)
    {
        lock (textBuffer)
        {
            if (string.IsNullOrEmpty(textBuffer))
            {
                textBuffer = text;
            }
            else
            {
                textBuffer = textBuffer + Environment.NewLine + text;
            }
        }
    }
}

此外,两个脚本都需要一个额外的类,它封装了管道流,因此发送和接收文本变得更加容易.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Assets
{
    /// <summary>
    /// Simple Wrapper to write / read Data to / from a Named Pipe Stream.
    /// 
    /// Code based on:
    /// https://stackoverflow.com/questions/43062782/send-message-from-one-program-to-another-in-unity
    /// </summary>
    public class StreamString
    {
        private Stream ioStream;
        private UnicodeEncoding streamEncoding;

        public StreamString(Stream ioStream)
        {
            this.ioStream = ioStream;
            streamEncoding = new UnicodeEncoding();
        }

        public string ReadString()
        {
            int len = 0;

            len = ioStream.ReadByte() * 256;
            len += ioStream.ReadByte();
            byte[] inBuffer = new byte[len];
            ioStream.Read(inBuffer,len);

            return streamEncoding.GetString(inBuffer);
        }

        public int WriteString(string outString)
        {
            byte[] outBuffer = streamEncoding.GetBytes(outString);
            int len = outBuffer.Length;
            if (len > UInt16.MaxValue)
            {
                len = (int)UInt16.MaxValue;
            }
            ioStream.WriteByte((byte)(len / 256));
            ioStream.WriteByte((byte)(len & 255));
            ioStream.Write(outBuffer,len);
            ioStream.Flush();

            return outBuffer.Length + 2;
        }
    }
}

如果你读到这一点的帖子:谢谢:)我希望它能帮助你成功的开发者之旅!

我原型的最终结果:

WinForms-application with embedded Unity Engine,successfully communicating via named pipes

解决方法

如果没有别的,你可以回到基本的文件I / O来进行两者之间的通信.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读