原文:WPF 封装 dotnet remoting 调用其他进程

本文告诉大家一个封装好的库,使用这个库可以快速搭建多进程相互使用。

WPF 使用RPC调用其他进程 已经告诉大家调用的原理,但是大家可以看到,如果自己写一个框架是比较难的。

因为我经常调用 C++ 代码,如果C++出现异常,软件会直接退出,所以我就想把 C++ 代码放在其他进程,这样C++运行出现异常了,软件也不会直接退出。

但是如果每次都需要自己写相同的代码,我是不同意的,因为很容易写错。

因为我的代码放在了公司代码使用,所以我不会把源代码放出来,但是大家可以通过复制本文的类来创建框架。

创建端口

创建端口包含一个接口和一个类,因为我需要在一个设备运行,所以为了性能,我不使用 http 连接,这时的端口可以使用一个字符串

为了区分两个程序,我把程序分为两个,一个是 WPF 程序,一个是C++程序。因为另一个程序主要是运行 C++ 代码。

为了让两个程序能联系,就需要约定端口,因为这是框架,可能需要使用 http 通信,所以就需要写一个接口,如果需要使用 http 修改端口就继承,这样框架才可以在很多地方使用。

    /// <summary>
/// 创建端口
/// </summary>
public interface IPortGenerator
{
/// <summary>
/// 获得端口
/// </summary>
/// <returns></returns>
string GetPort();
}

因为我需要在一个系统运行两个程序,所以我的端口是这样写

    /// <summary>
/// 创建端口
/// </summary>
public class PortGenerator : IPortGenerator
{
/// <summary>
/// 获得一个随机端口
/// </summary>
/// <returns></returns>
public string GetPort()
{
const int maxPort = 65535;
const int minPort = 49152;
return (Process.GetCurrentProcess().Id % (maxPort - minPort) + minPort).ToString(); //获得进程ID作为端口号
}
}

调用软件

从 WPF 程序调用 C++ 程序需要告诉他参数,参数就是刚才的端口

这时 C++ 程序使用命令行解析,请看安利一款非常好用的命令行参数库:McMaster.Extensions.CommandLineUtils - walterlv

创建一个类用来解析

    public class Options
{
[Option('p', "port", Required = true, HelpText = "远程开启的端口")]
public string Port { get; set; }
}

解析只需要使用主函数传入的 args 就可以拿到端口

            Parser.Default.ParseArguments<Options>(args).MapResult(options =>
{
Console.WriteLine("端口号" + options.Port);
new Thread(() =>
{
Console.WriteLine("启动库");
Run(options.Port, assembly);
Console.WriteLine("启动完成");
}).Start();
return 0;
}, _ => -1);

这里启动一个新的线程因为C++程序需要使用另一个线程去计算,主函数的线程会如果没有使用 Console.Read() 会退出。

现在 WPF 可以开始调用 C++ 程序,使用下面的代码进行管理

    /// <summary>
/// 管理其他进程
/// </summary>
public class RemoteProcessManager
{
/// <summary>
/// 管理其他进程
/// </summary>
/// <param name="processName">进程名,用于启动进程</param>
public RemoteProcessManager(string processName)
{
ProcessName = processName;
} /// <summary>
/// 获取管理的进程
/// </summary>
public string ProcessName { get; } /// <summary>
/// 获取是否连接
/// </summary>
public bool IsConnected { get; private set; } /// <summary>
/// 创建端口
/// </summary>
public IPortGenerator PortGenerator { get; set; } /// <summary>
/// 远程应用退出时,建议监听后使用异常
/// </summary>
public event EventHandler RemoteExited; /// <summary>
/// 连接
/// </summary>
public void Connect()
{
if (IsConnected)
{
throw new InvalidOperationException("禁止多次连接");
} IsConnected = true; if (PortGenerator == null)
{
PortGenerator = new PortGenerator();
} Port = PortGenerator.GetPort(); //启动远程进程
var st = new ProcessStartInfo(ProcessName, "-p " + Port); #if !DEBUG
st.CreateNoWindow = true;
st.WindowStyle = ProcessWindowStyle.Hidden;
#endif
var remoteGuardian = Process.Start(st); //监控远程应用 if (remoteGuardian == null)
{
throw new RemoteProcessStartException("启动时出现返回值为空")
{
Data =
{
{"ProcessStartInfo", st}
}
};
} ProcessId = remoteGuardian.Id;
remoteGuardian.EnableRaisingEvents = true;
remoteGuardian.Exited += RemoteGuardian_OnExited; CleanRegister(); _channel = Terminal.CreatChannel(); //客户端 ChannelServices.RegisterChannel(_channel, false);
} /// <summary>
/// 从远程获得实例
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T GetObject<T>()
{
CheckProcess();
return (T) Activator.GetObject(typeof(T),
"Ipc://" + Port + "/" + typeof(T).Name);
} /// <summary>
/// 结束远程进程
/// </summary>
public void ExitProcess()
{
_isManualExit = true; var remoteProcessBrake = GetObject<RemoteProcessBrake>();
#pragma warning disable 618
remoteProcessBrake.OnExit();
#pragma warning restore 618
} private IChannel _channel; /// <summary>
/// 是否手动退出远程应用
/// </summary>
private bool _isManualExit; private void RemoteGuardian_OnExited(object sender, EventArgs e)
{
IsConnected = false;
ProcessId = -1; //手动退出就不需要事件
if (_isManualExit)
{
return;
} //防止360等垃圾软件觉得这个应用可以退出
RemoteExited?.Invoke(sender, e); //即使被你退出了,我还是要启动,但是可能存在一些地方使用的变量放在本地,所以拿到的值就是之前的应用,还是需要用户重启
Connect();
} /// <summary>
/// 清理注册,因为一个信道只能注册
/// </summary>
private void CleanRegister()
{
if (_channel != null)
{
ChannelServices.UnregisterChannel(_channel);
}
} private int ProcessId { set; get; } = -1;
private string Port { get; set; } private void CheckProcess()
{
if (!IsConnected || ProcessId == -1)
{
throw new NativeProcessException("远程应用已经意外结束或没有启动");
} var processes = Process.GetProcesses();
if (ProcessId != -1 && processes.All(temp => temp.Id != ProcessId))
{
#if DEBUG
throw new NativeProcessException();
#else
IsConnected = false;
Connect();
#endif
}
}
}

注意现在的代码存在很多类没有引用

从上面代码可以看到,这里使用的连接是 IPC ,因为调用其他进程是在同一个电脑,所以这时使用 IPC 的效率会比 http 和 tcp 高。原因是 IPC 是进程间通信,效率和内存共享差不多。而使用 http 或 tcp 需要把信息发送给本地巡回,然后再返回。而且使用 http 需要做额外的过程,需要走 http 的协议。使用 tcp 需要使用握手,性能都比 IPC 差。

运行的类

所有需要在 C++ 程序运行的类都需要注册,因为C++程序需要找到程序集所有符合的类,所以需要这些类标记

    /// <summary>
/// 放在远程的实例
/// <remarks>请不要在代码保存实例</remarks>
/// </summary>
interface IRemote
{ }
    /// <summary>
/// 共享使用的类,这个类会在远程进程创建
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class RemoteAttribute : Attribute
{ }

例如有一个类需要在 C++ 程序运行,在 WPF 程序使用,那么就需要这样写

    [Remote]
public class BaltrartularLouronay : MarshalByRefObject
{
public void TerecaLesi()
{
Console.WriteLine("调用");
}
}

继承 MarshalByRefObject 标记 Remote 就可以了

运行C++程序

运行需要获得程序所有类,需要在C++程序使用的类,实现它。

    /// <summary>
/// 远程本机进程
/// </summary>
public class RemoteNative
{
/// <summary>
/// 加载程序集
/// </summary>
/// <param name="args">传入参数</param>
/// <param name="assembly"></param>
public void Run(string[] args, Assembly assembly)
{
Parser.Default.ParseArguments<Options>(args).MapResult(options =>
{
Console.WriteLine("端口号" + options.Port);
new Thread(() =>
{
Console.WriteLine("启动库");
Run(options.Port, assembly);
Console.WriteLine("启动完成");
}).Start();
return 0;
}, _ => -1);
} /// <summary>
/// 加载程序集
/// </summary>
/// <param name="port">端口</param>
/// <param name="assembly">程序集</param>
public void Run(string port, Assembly assembly)
{
_channel = Terminal.CreatChannel(port); ChannelServices.RegisterChannel(_channel, false); //设置租用管理器的初始租用时间为无限 http://www.cnblogs.com/wayfarer/archive/2004/08/05/30437.html
LifetimeServices.LeaseTime = TimeSpan.Zero; //注册实例
var remoteProcessBrake = new RemoteProcessBrake(); remoteProcessBrake.Exit += RemoteProcessBrake_Exit; // 防止对象回收
// 如果不使用 var objRef = x 那么在运行就发现 System.Runtime.Remoting.RemotingException:“找不到请求的服务”
var objRef = RemotingServices.Marshal(remoteProcessBrake, remoteProcessBrake.GetType().Name); _objRefList.Add(objRef); Init(assembly);
} private void RemoteProcessBrake_Exit(object sender, EventArgs e)
{
Environment.Exit(0);
} private void Init(Assembly assembly)
{
foreach (var temp in assembly.GetTypes().Where(temp => temp.GetCustomAttribute<RemoteAttribute>() != null))
{
var obj = CreateInstance(temp);
if (obj != null)
{
// 防止对象回收
var objRef = RemotingServices.Marshal(obj, temp.Name);
_objRefList.Add(objRef);
}
}
} private MarshalByRefObject CreateInstance(Type type)
{
ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
return constructor == null ? null : Activator.CreateInstance(type) as MarshalByRefObject;
} /// <summary>
/// 防止对象回收
/// </summary>
private List<ObjRef> _objRefList = new List<ObjRef>(); private IChannel _channel;
}

通道

如果需要两个程序连接,需要创建通道

   /// <summary>
/// 通道
/// </summary>
public class Terminal
{
/// <summary>
/// 创建连接
/// </summary>
/// <param name="port">对于服务端需要一个标示符,对于客户端请使用空</param>
/// <returns></returns>
public static IChannel CreatChannel(string port = "")
{
if (string.IsNullOrEmpty(port))
{
port = Guid.NewGuid().ToString("N");
} var serverProvider = new BinaryServerFormatterSinkProvider();
var clientProvider = new BinaryClientFormatterSinkProvider();
serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["portName"] = port; return new IpcChannel(props, clientProvider, serverProvider);
}
}

对于 WPF 程序只需要创建随机的端口,对于 C++ 程序需要创建 WPF 程序告诉他的端口,这样 WPF 程序才可以发送数据到 C++ 程序

使用

尝试把上面的类复制到自己的一个项目,然后创建两个项目,一个是 WPF 程序,一个是C++程序,让两个程序都引用这个项目。

注意创建的项目需要引用 System.Runtime.Remoting

例如创建 MairzearPowhel 程序做 WPF 程序用来调用 SedreaSudome 程序。在 MairzearPowhel 需要引用 SedreaSudome 可以获得里面的类而且用来启动 SedreaSudome 。

    /// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
} private void SouhaiNosoja()
{
// 启动C++程序
var remoteProcessManager = new RemoteProcessManager("SedreaSudome.exe"); // 连接
remoteProcessManager.Connect(); var baltrartularLouronay = remoteProcessManager.GetObject<BaltrartularLouronay>(); // 执行里面方法
baltrartularLouronay.TerecaLesi();
} private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
SouhaiNosoja(); }
}

对于 C++ 程序只需要几个代码

    class Program
{
static void Main(string[] args)
{
Debugger.Launch(); var remoteNative = new RemoteNative();
remoteNative.Run(args, typeof(Program).Assembly); while (true)
{
Console.Read();
}
}
}

如果发现无法使用,请联系我

感谢 洪三水提供图片


本文会经常更新,请阅读原文:
https://lindexi.gitee.io/post/WPF-%E5%B0%81%E8%A3%85-dotnet-remoting-%E8%B0%83%E7%94%A8%E5%85%B6%E4%BB%96%E8%BF%9B%E7%A8%8B.html
,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,或者前往 CSDN 关注我的主页


本作品采用
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:
https://lindexi.gitee.io
),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请
与我联系

WPF 封装 dotnet remoting 调用其他进程的更多相关文章

  1. WPF 使用RPC调用其他进程

    如果在 WPF 需要用多进程通信,一个推荐的方法是 WCF ,因为 WCF 是 RPC 计算.先来讲下 RPC (Remote Procedure Call) 远程过程调用,他是通过特定协议,包括 t ...

  2. WPF 从零开始开发 dotnet Remoting 程序

    本文告诉大家如何不使用框架,从零开始开发一个 dotnet remoting 程序 在我的另一篇博客 WPF 使用RPC调用其他进程 就大概告诉了大家如何在 WPF 使用 dotnet remotin ...

  3. 2018-6-24-WPF-使用RPC调用其他进程

    title author date CreateTime categories WPF 使用RPC调用其他进程 lindexi 2018-06-24 14:41:29 +0800 2018-2-13 ...

  4. WPF 程序如何跨窗口/跨进程设置控件焦点

    原文:WPF 程序如何跨窗口/跨进程设置控件焦点 WPF 程序提供了 Focus 方法和 TraversalRequest 来在 WPF 焦点范围内转移焦点.但如果 WPF 窗口中嵌入了其他框架的 U ...

  5. PHP封装curl的调用接口及常用函数

    <?php /** * @desc 封装curl的调用接口,post的请求方式 */ function doCurlPostRequest($url, $requestString, $time ...

  6. Hibernate的简单封装Session(方便调用)

    因为每次用增删改查时都需要用到hibernate的配置来生成session工厂进而生成session,比较麻烦,所以我们直接封装一个可以调用的类,需要的时候只需要调用即可. 新建一个Hibernate ...

  7. WPF封装控件时 检测是否在设计模式中

    原文:WPF封装控件时 检测是否在设计模式中 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/Vblegend_2013/article/detail ...

  8. C# 调用word进程操作文档关闭进程

    C# 调用word进程操作文档关闭进程 作者:Jesai 时间:2018-02-12 20:36:23 前言: office办公软件作为现在主流的一款办公软件,在我们的日常生活和日常工作里面几乎每天都 ...

  9. .Net Remoting 调用远程对象

    根据需求,我们的系统必须以C/S方式构建,而且是三层架构,这样一来,就出现了服务器端和客户端通信的问题. 为了解决双方的通信问题,还要考虑效率.性能等方面,经过分析.试验,我们根据效率.移植.开发难易 ...

随机推荐

  1. centos7安装anaconda之后报错:rpm: /home/wyl/anaconda3/lib/liblzma.so.5: version `XZ_5.1.2alpha' not found (required by /lib64/librpmio.so.3)

    1.报错 参考:https://stackoverflow.com/questions/47633870/rpm-lib64-liblzma-so-5-version-xz-5-1-2alpha-no ...

  2. SOAP扩展PHP轻松实现WebService

    最近在一个PHP项目中对接外部接口涉及到WebService,搜索引擎上相关文章不是很多,找到的大都是引用一个号称很强大的开源软件 NuSOAP(下载地址:http://sourceforge.net ...

  3. 紫书 例题8-1 UVa 120(构造法)

    #include<cstdio> #include<iostream> #include<sstream> #include<algorithm> #d ...

  4. 洛谷P1004 方格取数

    网络流大法吼 不想用DP的我选择了用网络流-- 建模方法: 从源点向(1,1)连一条容量为2(走两次),费用为0的边 从(n,n)向汇点连一条容量为2,费用为0的边 每个方格向右边和下边的方格连一条容 ...

  5. STM32 SPI 发送第一个数据不成功问题

    STM32的标准库,跟HAL库都是很实用的, 在使用SPI库的过程中一定要注意时序的问题. 我在调试SPI过程中,调试了两个IC,都是用HAL库, 第一个IC没出问题,第二个IC出现了第一次发送数据不 ...

  6. 【codeforces 738E】Subordinates

    [题目链接]:http://codeforces.com/problemset/problem/738/E [题意] 给你一个类似树形的关系; 然后告诉你某个人头顶上有多少个上司numi; 只有fat ...

  7. SQL SERVER-union

    UNION 操作符用于合并两个或多个 SELECT 语句的结果集. 请注意,UNION 内部的每个 SELECT 语句必须拥有相同数量的列.列也必须拥有相似的数据类型.同时,每个 SELECT 语句中 ...

  8. [Python Test] Use pytest fixtures to reduce duplicated code across unit tests

    In this lesson, you will learn how to implement pytest fixtures. Many unit tests have the same resou ...

  9. JSP简单练习-上传文件

    注意:在编写上传文件的代码时,需确保"WEB-INF/lib"下含有jspsmartupload.jar包.否则会出错. jspSmartupload.jar下载 <!-- ...

  10. Linux Yum 命令使用举例

    转自:https://blog.csdn.net/u012359618/article/details/51199309/ 本文给大家讲解Yum的使用15个范例: Yum软件包管理方式,在Red Ha ...