我目前正在为编辑器开发一个简单的原型。编辑器将使用WinForms(或WPF,如果可能)来提供主用户界面,并且还将嵌入Unity 2017独立应用程序以可视化数据并提供其他控制元素(例如缩放,旋转,滚动......)。 p>
感谢下面这篇不错的帖子,让一个嵌入式Unity应用程序在WinForms应用程序中运行非常简单。
https://forum.unity.com/threads/unity-3d-within-windows-application-enviroment.236213/
此外,还有一个简单的示例应用程序,您可以在此处访问:
不幸的是,我找到的示例或任何帖子都没有回答一个非常基本的问题:如何从嵌入式Unity应用程序中的WinForms应用程序传递数据(或调用方法)(反之亦然) ?
您的WinForms应用程序是否可以在Unity应用程序中简单地调用MonoBehaviour脚本或静态方法?如果是这样,怎么样?如果没有,那么什么是好的解决方法? Unity-to-WinForms通信如何才能起作用呢?
更新
使用Programmer(link)提到的重复页面来实现一个解决方案,该解决方案使用命名管道在WinForms和Unity应用程序之间进行通信。
两个应用程序都使用BackgroundWorkers,WinForms应用程序充当服务器(因为它在客户端启动之前首先启动并需要一个活动的连接侦听器),而嵌入式Unity应用程序充当客户端。
不幸的是,Unity-application抛出NotImplementedException,声明" Mono"不支持ACL。创建NamedPipeClientStream时(使用Unity 2017.3和Net 2.0(不是Net 2.0子集)测试)。 the post mentioned above中的一些评论已经报道了这个例外,但如果它已经解决,则不清楚。建议的解决方案"确保服务器在客户端尝试连接之前启动并运行。和#34;以管理模式启动它"已经尝试过,但到目前为止失败了。
解决方案:
经过一些更多的测试后,很明显,Mono"不支持" ACL。异常导致在创建NamedPipeClientStream实例时使用TokenImpersonationLevel参数。将其更改为TokenImpersonationLevel.None解决了这个问题。
这里是WinForms应用程序使用的代码,它充当命名管道服务器。确保在Unity应用程序客户端尝试连接之前执行此脚本!此外,请确保在启动服务器之前已构建并发布Unity应用程序。将Unity应用程序的Unity可执行文件放在WinForms-applications文件夹中,并将其命名为#34; 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, WM_ACTIVATE, 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, 0, 0, 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[] { "Received Data from Server: " + dataFromClient });
Thread.Sleep(1000);
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[] { "Received Data from Server: " + dataFromClient });
Thread.Sleep(1000);
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,您将在资源商店中找到它)引用到&text;&#39; -member,因此应用程序不会崩溃,你可以看到一些调试信息。 另外(如上所述)确保您构建并释放您的Unity应用程序,并将其命名为&#34; Child.exe&#34;并将其放在与WinForms-application相同的文件夹中。
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", PipeDirection.InOut, 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, 0, 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, 0, len);
ioStream.Flush();
return outBuffer.Length + 2;
}
}
}
如果你读到这篇文章:谢谢:)我希望它能帮助你成功的开发者之旅!
答案 0 :(得分:1)
如果不出意外,您可以依靠基本文件I / O来进行两者之间的通信。