控件进程化,32位程序做大内存消耗时存在内存不够用时,特此记录解决方案,控件进程化,模块进程化...
文章尾部提供完整demo下载
前端时间公司做了图片视频分析处理的项目,图片支持4k,6k甚至勉强支持8k;因为处理的方式很多,各模块之前不能切换后销毁,用户需要来回切换的,针对这个问题,每个模块都会加载图片,图片过大后程序内存告急,且程序是32位的,即使开启了大内存(32位开启大内存)的支持依然不是很好的解决,内存占用过高很容易被360告警,所以项目上最后的解决方案就是进程化各个模块!
通俗点讲就是,主进程启动后,会再启动子进程,子进程上会加载局部模块,附属于主进程,主进程退出后,子进程也终止,且子进程悬浮在主进程界面之上,所以例如打开遮罩时,需要发送命令到子进程,子进程自己再开启遮罩才能解决。
公司封装了几个dll,服务于快捷进程化开发,为此我精简成2个库Cal.Wpf.Part、Cal.Wpf.PartHost
Cal.Wpf.Part 辅助代码
Cal.Wpf.PartHost 子进程依赖的启动程序exe,如果有多个,该程序会生成多个按进程分
先大致看下效果:
一个主进程,2个子进程
下面是所有相关的dll
实现原理:主程序启动后,传递窗口句柄并启动子程序,设置子程序的所有者句柄为主程序的,让其绑定在一起;程序之间利用TcpChannel实现数据通讯,特别啰嗦一句就是子程序永远悬浮于主程序之上。
下面开始针对demo进行使用说明:
1、引用Cal.Wpf.Part.dll、Cal.Wpf.PartHost.exe
2、针对自定义的控件创建**Host.xaml(承载)、**Patrt.cs(辅助类)
3、**Host.xaml 引用 Cal.Wpf.Part.dll,并显示占位
1 <UserControl x:Class="ProcessDemo.CustomUserControlHostControl" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 5 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 6 xmlns:part="clr-namespace:Cal.Wpf.Part;assembly=Cal.Wpf.Part" 7 xmlns:local="clr-namespace:ProcessDemo" 8 mc:Ignorable="d" 9 d:DesignHeight="450" d:DesignWidth="800"> 10 <Grid> 11 <part:PartHostControl Name="partHost" /> 12 </Grid> 13 </UserControl>
1 using Cal.Wpf.Part.Remoting; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 using System.Windows; 8 using System.Windows.Controls; 9 using System.Windows.Data; 10 using System.Windows.Documents; 11 using System.Windows.Input; 12 using System.Windows.Interop; 13 using System.Windows.Media; 14 using System.Windows.Media.Imaging; 15 using System.Windows.Navigation; 16 using System.Windows.Shapes; 17 18 namespace ProcessDemo 19 { 20 /// <summary> 21 /// UserControlHostControl.xaml 的交互逻辑 22 /// </summary> 23 public partial class CustomUserControlHostControl : UserControl, IDisposable 24 { 25 public CustomUserControlHostControl() 26 { 27 InitializeComponent(); 28 } 29 30 ~CustomUserControlHostControl() 31 { 32 Dispose(); 33 } 34 35 public void Dispose() 36 { 37 partHost.Dispose(); 38 } 39 40 /// <summary> 41 /// 收到进程的事件推送时 42 /// </summary> 43 public event MessageEventHandler ReceiveCommand 44 { 45 add 46 { 47 partHost.ReceiveCommand += value; 48 } 49 remove 50 { 51 partHost.ReceiveCommand -= value; 52 } 53 } 54 55 /// <summary> 56 /// 设置在独立进程中运行的控件 57 /// </summary> 58 /// <param name="parentWindowHandler">父窗口句柄</param> 59 public void InitPartControl(IntPtr parentWindowHandler) 60 { 61 partHost.InitPartControl(typeof(CustomUserControlPart), parentWindowHandler); 62 } 63 64 /// <summary> 65 /// 发送消息 66 /// </summary> 67 /// <param name="msg"></param> 68 public void SendMsg(string msg) 69 { 70 partHost.SendCommand(new ProcessMessage() 71 { 72 Body = msg, 73 Command = 1 74 }); 75 } 76 } 77 }
4、 **Patrt.cs实现接口IPart
1 using Cal.Wpf.Part; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 using System.Windows; 8 9 namespace ProcessDemo 10 { 11 public class CustomUserControlPart : IPart 12 { 13 /// <summary> 14 /// 逻辑主控件 15 /// </summary> 16 private CustomUserControl control; 17 18 /// <summary> 19 /// 向主进程发消息时 20 /// </summary> 21 public event Cal.Wpf.Part.Remoting.MessageEventHandler SendCommand; 22 23 /// <summary> 24 /// 进程调用,需要支援释放了 25 /// </summary> 26 /// <returns></returns> 27 public void Dispose() 28 { 29 if (control != null) 30 { 31 control.SendMsg -= Control_SendMsg; 32 } 33 } 34 35 /// <summary> 36 /// 收到主进程消息时 37 /// </summary> 38 /// <param name="message"></param> 39 public void ExecuteCommand(Cal.Wpf.Part.Remoting.ProcessMessage message) 40 { 41 if (message == null || message.Body == null) return; 42 43 control.SetMsg(message.Body.ToString()); 44 } 45 46 /// <summary> 47 /// 进程调用,获取当前插件的展示界面 48 /// </summary> 49 /// <returns></returns> 50 public FrameworkElement GetPartControl() 51 { 52 if (control == null) 53 { 54 control = new CustomUserControl(); 55 control.SendMsg += Control_SendMsg; 56 } 57 return control; 58 } 59 60 /// <summary> 61 /// CustomUserControl 需要先外部进程发送消息时 62 /// </summary> 63 /// <param name="sender"></param> 64 /// <param name="e"></param> 65 private void Control_SendMsg(object sender, Tuple<int, string> e) 66 { 67 SendCommand?.Invoke(new Cal.Wpf.Part.Remoting.ProcessMessage() 68 { 69 Command = e.Item1, 70 Body = e.Item2 71 }); 72 } 73 74 public void Initialize() 75 { 76 //加载CustomUserControl 之前需要做的事情,可以在这里写,比如加载皮肤dll等... 77 } 78 } 79 }
5、真实的自定义控件再原有的逻辑里,需要处理和主程序的消息通讯,因为当前控件是渲染在子控件上的
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Diagnostics; 5 using System.Linq; 6 using System.Text; 7 using System.Threading.Tasks; 8 using System.Windows; 9 using System.Windows.Controls; 10 using System.Windows.Data; 11 using System.Windows.Documents; 12 using System.Windows.Input; 13 using System.Windows.Media; 14 using System.Windows.Media.Imaging; 15 using System.Windows.Navigation; 16 using System.Windows.Shapes; 17 18 namespace ProcessDemo 19 { 20 /// <summary> 21 /// CustomUserControl.xaml 的交互逻辑 22 /// </summary> 23 public partial class CustomUserControl : UserControl 24 { 25 public CustomUserControl() 26 { 27 InitializeComponent(); 28 29 if (!DesignerProperties.GetIsInDesignMode(this)) 30 { 31 pid.Text = $"当前进程id:{Process.GetCurrentProcess().Id}"; 32 } 33 } 34 35 /// <summary> 36 /// 发送消息时 37 /// </summary> 38 public event EventHandler<Tuple<int, string>> SendMsg; 39 40 /// <summary> 41 /// 发送消息到主进程 42 /// </summary> 43 /// <param name="sender"></param> 44 /// <param name="e"></param> 45 private void Button_Click(object sender, RoutedEventArgs e) 46 { 47 SendMsg?.Invoke(this, new Tuple<int, string>(1, msg.Text)); 48 } 49 50 /// <summary> 51 /// 收到主进程的消息时 52 /// </summary> 53 /// <param name="msg"></param> 54 public void SetMsg(string msg) 55 { 56 Dispatcher.Invoke(() => 57 { 58 msgstr.Text = $"收到主进程发来的消息:{msg}"; 59 }); 60 } 61 } 62 }
6、最后就是在主进程上,把**Host占位,且在恰当的时候初始化子进程即可
1 <Window x:Class="ProcessDemo.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:ProcessDemo" 7 xmlns:uc="clr-namespace:ProcessDemo.UC" 8 WindowStartupLocation="CenterScreen" 9 mc:Ignorable="d" 10 Title="控件进程化Demo" Height="300" Width="400"> 11 <Grid> 12 <DockPanel Margin="5"> 13 <Grid DockPanel.Dock="Top"> 14 <StackPanel> 15 <TextBlock Text="我是主进程上的" /> 16 <TextBlock x:Name="pid"/> 17 <StackPanel Orientation="Horizontal"> 18 <TextBox x:Name="msg" Width="100" VerticalAlignment="Center" /> 19 <Button Content="发消息给子进程" Margin="2" Click="Button_Click" /> 20 </StackPanel> 21 </StackPanel> 22 </Grid> 23 <Grid DockPanel.Dock="Top"> 24 <TextBlock Text="收到子进程消息时:..." x:Name="msgstr" /> 25 </Grid> 26 <Grid DockPanel.Dock="Bottom"> 27 <!--子进程2--> 28 <uc:UserControlHost Margin="10" x:Name="hostcontrol2" Height="50" /> 29 </Grid> 30 <Grid> 31 <!-- 子进程存放位置 --> 32 <local:CustomUserControlHostControl x:Name="hostcontrol" Margin="10" /> 33 </Grid> 34 </DockPanel> 35 </Grid> 36 </Window>
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Diagnostics; 5 using System.Linq; 6 using System.Text; 7 using System.Threading.Tasks; 8 using System.Windows; 9 using System.Windows.Controls; 10 using System.Windows.Data; 11 using System.Windows.Documents; 12 using System.Windows.Input; 13 using System.Windows.Interop; 14 using System.Windows.Media; 15 using System.Windows.Media.Imaging; 16 using System.Windows.Navigation; 17 using System.Windows.Shapes; 18 19 namespace ProcessDemo 20 { 21 /// <summary> 22 /// MainWindow.xaml 的交互逻辑 23 /// </summary> 24 public partial class MainWindow : Window 25 { 26 public MainWindow() 27 { 28 InitializeComponent(); 29 30 if (!DesignerProperties.GetIsInDesignMode(this)) 31 { 32 pid.Text = $"当前进程id:{Process.GetCurrentProcess().Id}"; 33 hostcontrol.ReceiveCommand += Hostcontrol_ReceiveCommand; 34 Loaded += MainWindow_Loaded; 35 } 36 } 37 38 /// <summary> 39 /// 窗口加载后 40 /// </summary> 41 /// <param name="sender"></param> 42 /// <param name="e"></param> 43 private void MainWindow_Loaded(object sender, RoutedEventArgs e) 44 { 45 //获取当前句柄 46 var parentWnd = Window.GetWindow(this); 47 var winHelper = new WindowInteropHelper(parentWnd); 48 IntPtr parentWindowHandler = winHelper.EnsureHandle(); 49 50 //初始化子进程 51 hostcontrol.InitPartControl(parentWindowHandler); 52 hostcontrol2.InitPartControl(parentWindowHandler); 53 } 54 55 /// <summary> 56 /// 当收到子进程发来消息时 57 /// </summary> 58 /// <param name="message"></param> 59 /// <returns></returns> 60 private Cal.Wpf.Part.Remoting.ProcessMessage Hostcontrol_ReceiveCommand(Cal.Wpf.Part.Remoting.ProcessMessage message) 61 { 62 if (message == null || message.Body == null) return null; 63 64 msgstr.Dispatcher.Invoke(() => 65 { 66 msgstr.Text = $"子进程发来消息:{message.Body.ToString()}"; 67 }); 68 return null; 69 } 70 71 /// <summary> 72 /// 发给子进程 73 /// </summary> 74 /// <param name="sender"></param> 75 /// <param name="e"></param> 76 private void Button_Click(object sender, RoutedEventArgs e) 77 { 78 hostcontrol.SendMsg(msg.Text); 79 } 80 } 81 }
完整demo,有需要的可以移步下载,下面是解决方案摘要