c# – 在Winforms-Application中嵌入的Unity-Application上调用
参见英文答案 >
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’成员,这样应用程序就不会崩溃,你可以看到一些调试信息. 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; } } } 如果你读到这一点的帖子:谢谢:)我希望它能帮助你成功的开发者之旅! 我原型的最终结果: 解决方法
如果没有别的,你可以回到基本的文件I / O来进行两者之间的通信.
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |