WPF 封装 dotnet remoting 调用其他进程

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

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

目录

  1. 创建端口
  2. 调用软件
  3. 运行的类
  4. 运行C++程序
  5. 通道
  6. 使用

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
),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请
与我联系

原文地址:https://www.cnblogs.com/lonelyxmas/p/10582116.html

时间: 2024-10-17 01:20:18

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

【如何使用shell 脚本调用java进程】

如何使用shell脚本调用java进程,直接看脚本: 源码地址:获取源码 更多java,java学习,java面试题 http://techfoxbbs.com

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

作者:邓家海 时间:2018-02-12 20:36:23 前言: office办公软件作为现在主流的一款办公软件,在我们的日常生活和日常工作里面几乎每天都要用到.到了今天,为了节省人力资源和减轻工作量,减少工作上面的错误以及减轻工作的复杂度,于是办公自动应运而生.于是出现了OA软件的春天,许多政府机关.单位.企业都会选择采购或者定制OA系统.OA软件普遍都涉及到office办公软件的操作.Excel.word是经常用到的. 抛出问题 案例:在为某政府机关研发一个管理系统,该政府机关有大量的合同

.Net Remoting 调用远程对象

根据需求,我们的系统必须以C/S方式构建,而且是三层架构,这样一来,就出现了服务器端和客户端通信的问题. 为了解决双方的通信问题,还要考虑效率.性能等方面,经过分析.试验,我们根据效率.移植.开发难易等几个因素,舍弃了一开始提出的WebService.消息队列机制,以及有人建议的基于流I/O自己解析数据的通信方式,在分析了目前主流的RPC方式(DCOM.CORBA..NET Remoting)及我们的开发平台后,最终选择了微软新推出的.NET Remoting机制.我们的原因如下: 1..NET

linux下线程调用sleep,进程挂起

http://blog.csdn.net/horstlinux/article/details/7911457 http://blog.csdn.net/eroswang/article/details/2932630 分类: Linux开发c/c++2008-09-15 13:05 1788人阅读 评论(0) 收藏 举报 linuxnullstruct 在linux下调用sleep是用时钟的,一个进程的时钟系统是有限制的.如果每个线程使用sleep,到了最大的数量,最终会进程会挂起.最好是用s

WinForm与WPF下跨线程调用控件

Winform下: public delegate void UpadataTextCallBack(string str,TextBox text); public void UpadtaText(string str, TextBox text) { if (text.InvokeRequired) { UpadataTextCallBack upadataTextCallBack = UpadtaText; text.Invoke(upadataTextCallBack, new obje

以JQuery的方式封装 cookie 方便调用

一.封装 jQuery.cookie = function (key, value, options) { // key and value given, set cookie... if (arguments.length > 1 && (value === null || typeof value !== "object")) { options = jQuery.extend({}, options); if (value === null) { optio

1.远程调用其他进程中的代码

学习记录. void usegoods() { _asm { push 3//3是小金疮药.金疮药id push 1 push 0 mov eax,0x79D330 call eax } } 上诉代码是通过od找出的一个call.然后用c++内嵌汇编来调用这个call void CrxjhcDlg::OnBnClickedButton1() { HWND h; h=::FindWindow(NULL,L"YB_OnlineClient");//获取游戏窗口句柄spy++ DWORD i

封装和参数调用

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 24.0px "PingFang SC" } p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 24.0px "PingFang SC" } p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 24.0px "PingFang SC"; min-height: 33.0px

小程序开发 组件定义(封装)、组件调用、父子组件方法调用、父子组件传值通讯

在小程序开发中,为了提高代码效率,和代码使用率,我们也用到了组件封装, 今天我介绍下如何在小程序中封装一个头部公用组件 首先,所有父组件(调用页面)的json文件都要引用子组件:index.json { "usingComponents": { "header": "../../component/header/header", } } 一,组件定义(封装) 子组件:header.wxml <view name='header' class