求助:IPlugin实现中无法单独关闭插件,更新时主应用被关闭
问题分析与解决方案
我完全懂你遇到的困扰——你的插件调用Application.Exit()会直接终止整个主应用的消息循环,而不是只关闭插件自身的托盘组件。这是因为Application.Exit()是Windows Forms的全局方法,不管哪个组件调用它,都会结束整个应用的所有消息循环。另外你还面临DLL被锁定无法热更新的问题,我给你一步步拆解解决:
1. 修复插件退出逻辑,避免关闭主应用
首先要改掉插件里DoExit()方法的全局退出逻辑,转而只清理插件自身的托盘资源:
第一步:修改MyContext类,添加托盘清理方法
给你的托盘上下文类加一个专门的退出方法,只结束当前插件的消息循环:
Public Class MyContext Inherits ApplicationContext Private _trayIcon As NotifyIcon Public Sub New() ' 初始化托盘图标的原有代码 _trayIcon = New NotifyIcon() _trayIcon.Icon = New Icon("your-icon-path.ico") _trayIcon.Visible = True ' ... 其他托盘初始化逻辑 End Sub ' 添加专门的托盘退出方法 Public Sub CleanupTray() _trayIcon.Visible = False _trayIcon.Dispose() Me.ExitThread() ' 仅结束当前插件的消息循环,不影响主应用 End Sub End Class
第二步:更新插件类的DoExit()和DoSomething()
在插件类里保存MyContext的实例引用,退出时调用清理方法而非全局Exit():
Imports PluginContracts Imports System.Windows.Forms Imports System.Threading Public Class Plugin Implements IPlugin Private _pluginContext As MyContext Public Sub DoSomething() Implements IPlugin.DoSomething _pluginContext = New MyContext() Application.Run(_pluginContext) End Sub Public Sub DoExit() Implements IPlugin.DoExit If _pluginContext IsNot Nothing Then _pluginContext.CleanupTray() _pluginContext = Nothing End If End Sub Public ReadOnly Property Name As String Implements IPlugin.Name Get Return "First Plugin" End Get End Property End Class
2. 解决DLL锁定问题,实现热更新
直接用Assembly.LoadFrom()加载DLL会锁定原文件,导致你无法替换更新后的DLL。解决办法是先把DLL读入内存,再从字节数组加载:
修改主应用的LoadTray()方法
Imports PluginContracts Imports System.Reflection Imports System.IO Imports System.Threading Module Program Private _loadedPlugins As New List(Of IPlugin) Private _pluginThreads As New List(Of Thread) ' 保存插件线程引用,方便后续等待退出 Sub Main() LoadTray() ' 主应用可以在这里启动自己的消息循环或逻辑 Application.Run() End Sub Sub LoadTray() Dim dllPath As String = GetDLL.TrayDll ' 先把DLL读入字节数组,避免锁定原文件 Dim dllBytes As Byte() = File.ReadAllBytes(dllPath) Dim dll As Assembly = Assembly.Load(dllBytes) For Each t As Type In dll.GetTypes ' 更安全的接口判断方式 If GetType(IPlugin).IsAssignableFrom(t) AndAlso Not t.IsAbstract Then Try Dim pluginInstance As IPlugin = DirectCast(Activator.CreateInstance(t), IPlugin) _loadedPlugins.Add(pluginInstance) ' 用单独的STA线程启动插件(Windows Forms控件必须在STA线程运行) Dim pluginThread As New Thread(Sub() pluginInstance.DoSomething()) pluginThread.SetApartmentState(ApartmentState.STA) pluginThread.Start() _pluginThreads.Add(pluginThread) Catch ex As Exception MessageBox.Show(ex.ToString()) End Try End If Next End Sub End Module
3. 完善FileSystemWatcher的热更新逻辑
当检测到DLL变化时,先优雅关闭所有插件,再重新加载:
' 在Program模块里添加FileSystemWatcher的事件处理 Private Sub SetupDllWatcher() Dim watcher As New FileSystemWatcher(Path.GetDirectoryName(GetDLL.TrayDll)) watcher.Filter = Path.GetFileName(GetDLL.TrayDll) watcher.Changed += AddressOf OnDllUpdated watcher.EnableRaisingEvents = True End Sub Private Sub OnDllUpdated(sender As Object, e As FileSystemEventArgs) ' 先关闭所有已加载的插件 For Each plugin In _loadedPlugins plugin.DoExit() Next ' 等待插件线程结束(设置超时避免无限等待) For Each thread In _pluginThreads If thread.IsAlive Then thread.Join(5000) ' 等待5秒超时 End If Next ' 清空旧的插件和线程引用 _loadedPlugins.Clear() _pluginThreads.Clear() ' 重新加载新的插件 LoadTray() End Sub
核心总结
- 不要在插件里调用
Application.Exit(),只清理自身的资源和消息循环 - 用内存加载DLL的方式避免文件锁定,实现热更新
- 维护插件实例和线程的引用,确保更新前能优雅关闭旧插件
内容的提问来源于stack exchange,提问作者user7406533
相关产品推荐
相关产品推荐

