跨AppDomain调用DLL触发事件时出现未处理空引用异常
问题分析与解决:跨AppDomain订阅DLL事件触发空引用异常
我的项目包含两个组件:
- Windows Forms应用程序(WindowsFormsApplication1)
- 类库DLL(ClassLibrary1)
需求是当DLL中触发事件时,Windows Forms应用执行对应逻辑,当前通过AppDomain访问DLL方法,但运行时抛出空引用异常。
Windows Forms应用Form1.cs代码
using System; // more usings ... using ClassLibrary1; using System.Reflection; using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); trial obj = new trial(); obj.trial1(); } } class trial { void HandleEvent(object sender, EventArgs e) { Console.WriteLine("HandleEvent called"); } AppDomain ad = AppDomain.CreateDomain("Test"); string DLL = @"C:\..\ClassLibrary1.dll"; public void trial1() { trial pro = new trial(); MethodInfo method = typeof(trial).GetMethod("HandleEvent", BindingFlags.NonPublic | BindingFlags.Instance); // Subscribe to the event EventInfo eventInfo = typeof(ClassLibrary1.Class1).GetEvent("TestEvent"); Type type = eventInfo.EventHandlerType; Delegate handler = Delegate.CreateDelegate(type, pro, method); // method call 1 Loader.Call(ad, DLL,"ClassLibrary1.Class1","RaiseEvent",DateTime.Now.ToShortDateString()); } } public class Loader : MarshalByRefObject { object CallInternal(string dll, string typename, string method, object[] parameters) { Assembly a = Assembly.LoadFile(dll); object o = a.CreateInstance(typename); Type t = o.GetType(); MethodInfo m = t.GetMethod(method); return m.Invoke(o, parameters); } public static object Call(AppDomain domain, string dll, string typename, string method, params object[] parameters) { Loader ld = (Loader)domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(Loader).FullName); object result = ld.CallInternal(dll, typename, method, parameters); AppDomain.Unload(domain); return result; } } }
ClassLibrary1.dll代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ClassLibrary1 { public class Class1 { public event EventHandler TestEvent; public int RaiseEvent(string msg) { try { TestEvent(this, EventArgs.Empty); } catch (Exception ex) { Console.WriteLine("the exception is: " + ex.ToString()); if (ex.InnerException != null) { Console.WriteLine("the inner exception is: " + ex.InnerException.Message.ToString()); } } return 2; } } }
异常信息
Object reference not set to an instance of an object.
对比:控制台应用正常执行代码
using System; using System.Reflection; class Test { void HandleEvent(object sender, EventArgs e) { Console.WriteLine("HandleEvent called"); } static void Main() { Test test = new Test(); ClassLibrary1.Class1 publisher = new ClassLibrary1.Class1(); MethodInfo method = typeof(Test).GetMethod("HandleEvent", BindingFlags.NonPublic | BindingFlags.Instance); // Subscribe to the event EventInfo eventInfo = typeof(ClassLibrary1.Class1).GetEvent("TestEvent"); Type type = eventInfo.EventHandlerType; Delegate handler = Delegate.CreateDelegate(type, test, method); eventInfo.AddEventHandler(publisher, handler); publisher.RaiseEvent("hello"); } }
问题根源
核心问题是跨AppDomain的事件订阅逻辑错误:
- WinForms代码中,你在主AppDomain创建了委托并尝试订阅事件,但实际调用
RaiseEvent时,是在新建的TestAppDomain中创建了一个全新的Class1实例——两个实例分属不同AppDomain,主域的订阅完全没关联到实际触发事件的实例,导致TestEvent为null,触发空引用异常。 - 控制台程序没有使用多AppDomain,所有对象都在同一个域内,订阅和触发针对的是同一个实例,所以正常工作。
解决方案
方案一:在目标AppDomain内完成事件订阅与触发
修改代码,让事件订阅和触发在同一个AppDomain内完成,同时确保事件处理类可跨域访问:
- 修改
trial类继承MarshalByRefObject(允许跨AppDomain传递代理):
class trial : MarshalByRefObject { public void HandleEvent(object sender, EventArgs e) { // WinForms环境建议用MessageBox替代Console.WriteLine MessageBox.Show("HandleEvent called"); } string DLL = @"C:\..\ClassLibrary1.dll"; public void trial1() { AppDomain ad = AppDomain.CreateDomain("Test"); // 将当前trial实例传递到目标AppDomain Loader.Call(ad, DLL, this); AppDomain.Unload(ad); } }
- 修改
Loader类,增加订阅事件的逻辑:
public class Loader : MarshalByRefObject { public void SetupAndRaiseEvent(string dll, MarshalByRefObject eventHandler) { Assembly a = Assembly.LoadFile(dll); object o = a.CreateInstance("ClassLibrary1.Class1"); Type t = o.GetType(); // 在当前AppDomain内完成事件订阅 EventInfo eventInfo = t.GetEvent("TestEvent"); MethodInfo handlerMethod = eventHandler.GetType().GetMethod("HandleEvent", BindingFlags.Public | BindingFlags.Instance); Delegate handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, eventHandler, handlerMethod); eventInfo.AddEventHandler(o, handler); // 触发事件 MethodInfo m = t.GetMethod("RaiseEvent"); m.Invoke(o, new object[] { DateTime.Now.ToShortDateString() }); } public static void Call(AppDomain domain, string dll, MarshalByRefObject eventHandler) { Loader ld = (Loader)domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(Loader).FullName); ld.SetupAndRaiseEvent(dll, eventHandler); } }
方案二:移除多AppDomain逻辑(无特殊隔离需求时推荐)
如果不需要AppDomain的隔离/卸载能力,直接在主AppDomain内实例化DLL对象,和控制台程序逻辑一致:
public partial class Form1 : Form { public Form1() { InitializeComponent(); trial obj = new trial(); obj.trial1(); } } class trial { void HandleEvent(object sender, EventArgs e) { MessageBox.Show("HandleEvent called"); } public void trial1() { ClassLibrary1.Class1 publisher = new ClassLibrary1.Class1(); MethodInfo method = typeof(trial).GetMethod("HandleEvent", BindingFlags.NonPublic | BindingFlags.Instance); EventInfo eventInfo = typeof(ClassLibrary1.Class1).GetEvent("TestEvent"); Delegate handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, method); eventInfo.AddEventHandler(publisher, handler); publisher.RaiseEvent(DateTime.Now.ToShortDateString()); } }
内容的提问来源于stack exchange,提问作者Crystal Crystal
相关产品推荐
相关产品推荐

