本文主旨是介绍“进程间通讯”在Grasshopper中的应用。
常用场景:手头已经有一个程序,这个程序想控制Grasshopper画布中某些组件进行一些数据操作,这个程序是独立Rhino和Grasshopper之外运行的。
从另一个程序来控制 Grasshopper 中的组件。
进程之间通讯
Windows平台下,进程间通讯称为“Interprocess Communications”,具体详见msdn文档,有多重实现模式,在此就不赘述了:
https://docs.microsoft.com/en-us/windows/win32/ipc/interprocess-communications
(中文版暂未提供)
鉴于Rhino/Grasshopper是.NET Framework平台,本文选择一个最简单的实现方法来实现这一点 —— Remoting。
关于Remoting的介绍:
https://docs.microsoft.com/en-us/dotnet/api/system.runtime.remoting?view=netframework-4.8
实现方法
Grasshopper电池中新开一个 自定义线程 用来接收外部程序的调用,外部程序与Grasshopper中新建的 自定义线程 通过Remoting协议实现通讯,这个线程负责依照外部程序指令对Grasshopper进行相应操作。
本文的例子是通过外部程序使得Grasshopper中重新计算(Recompute)。
- 新建一个 .Net Framework Library项目,里面用来定义进程通讯的对象的接口。
namespace IPCLibrary
{
public interface IPCInterface
{
// 定义一个能够被外部调用的接口,具体实现在Grasshopper中完成
void Recompute();
}
}
将该项目编译,构建一个 IPCInterface.dll 文件,该文件在接下来会被引用。
- 新建一个Grasshopper电池项目,并引用步骤1中生成的 IPCInterface.dll 文件。这个项目主要是实现两个部分的内容:
(1)依据步骤1中定义的接口,实现一个对象用来对Grasshopper实现所需的操作;
(2)在SolveInstance
内部开启新线程,用于实例化 步骤(1) 中的对象的静态属性以及注册通讯协议至操作系统。
using Grasshopper.Kernel;
using IPCLibrary;
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Threading;
namespace SolutionExpirer
{
internal class SolutionExpirer : MarshalByRefObject, IPCInterface
{
internal static GH_Component component; // 静态属性,在线程启动时指定为线程发起者
public void Recompute() => component.OnPingDocument().NewSolution(true); // 实现Grasshopper重算逻辑
}
public class SolutionExpirerComponent : GH_Component
{
public SolutionExpirerComponent()
: base("SolutionExpirer", "SEer",
"",
"IPC", "IPC")
{ }
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager) { }
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) { }
Thread thd; // 定义线程
int slvCount = 0; // 用于存储该电池总共的运行次数(证明真的ExpireSolution了)
protected override void SolveInstance(IGH_DataAccess DA)
{
slvCount++;
Message = slvCount.ToString(); // 用来显示SolveInstance自该电池在画布上起,总共运行了几次
if (thd != null) return;
thd = new Thread(() =>
{
var channel = new IpcServerChannel("GHMFIPCD");
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(SolutionExpirer), "SE", WellKnownObjectMode.SingleCall);
SolutionExpirer.component = this; // 指定静态属性
while (true) { } // 线程保活
});
thd.Start(); // 开启线程
}
protected override System.Drawing.Bitmap Icon => null;
public override Guid ComponentGuid => new Guid("*** *** Apply a new Guid here *** ***");
}
}
- 外部程序调用:这里采用一个Console程序来实现 —— 新建一个Visual Studio命令行项目,将步骤1中的 IPCInterface.dll 加入引用。
using IPCLibrary;
using System;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
namespace ClientSide
{
class Program
{
static void Main(string[] args)
{
var channel = new IpcClientChannel();
ChannelServices.RegisterChannel(channel, false);
var url = @"ipc://GHMFIPCD/SE"; // 分别对应Grasshopper电池定义中的Channel名字及ServiceType名字
var remoteObj = (IPCInterface)Activator.GetObject(typeof(IPCInterface), url);
remoteObj.Recompute();
}
}
}
本质上来说,是使用Activator.GetObject()
来实例化了一个在Grasshopper内的对象(远程对象),而在命令行程序中可以作为本地对象来使用。
将上述命令行程序运行的结果就是Recompute()
方法被执行,Grasshopper中画布上的所有电池将会被Expire并重启运算。运行结果:
图中,靠上部的电池是一个随机数生成器,每次会随即生成一个0~1之间的浮点数。可以看到,每次Recompute
的时候,都会生成一批新的浮点数。
左侧命令行运行的是步骤3中的生成的外部程序。可以看到,每次程序运行时都成功地将使Grasshopper重新计算了。电池本身的计数器也在不断增长。
生成器,每次会随即生成一个0~1之间的浮点数。可以看到,每次Recompute
的时候,都会生成一批新的浮点数。
左侧命令行运行的是步骤3中的生成的外部程序。可以看到,每次程序运行时都成功地将使Grasshopper重新计算了。电池本身的计数器也在不断增长。
此时如果我们对命令行程序(步骤3中的代码)进行重构、任何更改,都不会影响到Rhino或者Grasshopper的运行。甚至可以在Rhino运行的时候进行代码调试。在某些特定情况下,还是十分有效的。