如何捕获通过反射订阅的DLL事件返回的参数?
如何在跨AppDomain的WinForm事件处理程序中捕获事件返回参数
我有一个使用AppDomain加载DLL的WinForm应用(WindowsFormsApplication2),已订阅ClassLibrary.dll中实现的TestEvent事件。现在需要在WinForm的事件处理程序HandleEvent中捕获该事件的返回参数,具体实现方案如下:
原始代码参考
WinForm应用代码
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Reflection; using System.Diagnostics; using ClassLibrary1; using static System.Net.Mime.MediaTypeNames; namespace WindowsFormsApplication2 { [Serializable] public partial class Form1 : Form { void HandleEvent(object sender, EventArgs e) { Debug.WriteLine("HandleEvent called"); } string DLL = @"..\ConsoleApplication1\ClassLibrary1\bin\Debug\ClassLibrary1.dll"; public Form1() { InitializeComponent(); Loader.Call( DLL, "ClassLibrary1.Class1", "RaiseEvent", HandleEvent, DateTime.Now.ToShortDateString()); } private void button1_Click(object sender, EventArgs e) { Application.Restart(); Application.Run(new Form1()); this.Close(); } } public class Loader : MarshalByRefObject { AppDomain ad = AppDomain.CreateDomain("Test"); object CallInternal(string dll, string typename, string method, EventHandler handler, object[] parameters) { Assembly a = Assembly.LoadFile(dll); object o = a.CreateInstance(typename); Type t = o.GetType(); // 订阅事件 EventInfo eventInfo = t.GetEvent("TestEvent"); eventInfo.AddEventHandler(o, handler); MethodInfo m = t.GetMethod(method); return m.Invoke(o, parameters); } public static object Call( string dll, string typename, string method, EventHandler handler, params object[] parameters) { Loader ld = (Loader)ad.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(Loader).FullName); object result = ld.CallInternal(dll, typename, method, handler, parameters); AppDomain.Unload(ad); return result; } } }
ClassLibrary1.dll代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ClassLibrary1 { [Serializable] public class Class1 { public event EventHandler TestEvent; public int RaiseEvent(string msg) { try { TestEvent(this, EventArgs.Empty); } catch (Exception ex) { Console.WriteLine("异常信息: " + ex.ToString()); if (ex.InnerException != null) { Console.WriteLine("内部异常信息: " + ex.InnerException.Message.ToString()); } } return 2; } } }
实现方案
要捕获事件的返回参数,核心是通过自定义可序列化的事件参数类传递数据——标准EventHandler的EventArgs无法携带自定义数据,且跨AppDomain传递对象必须支持序列化。具体步骤如下:
1. 定义自定义事件参数类
创建继承自EventArgs的类,标记[Serializable]以支持跨AppDomain序列化:
[Serializable] public class TestEventArgs : EventArgs { // 定义需要传递的返回参数 public int ReturnValue { get; set; } }
2. 修改ClassLibrary1中的事件定义与触发逻辑
将事件类型改为EventHandler<TestEventArgs>,触发事件时传入携带返回参数的自定义实例:
using System; namespace ClassLibrary1 { [Serializable] public class Class1 { // 替换原EventHandler为泛型EventHandler<TestEventArgs> public event EventHandler<TestEventArgs> TestEvent; public int RaiseEvent(string msg) { try { int result = 2; // 示例返回值,可替换为实际业务逻辑结果 // 触发事件时传入自定义参数 TestEvent?.Invoke(this, new TestEventArgs { ReturnValue = result }); } catch (Exception ex) { Console.WriteLine("异常信息: " + ex.ToString()); if (ex.InnerException != null) { Console.WriteLine("内部异常信息: " + ex.InnerException.Message.ToString()); } } return 2; } } }
3. 修改WinForm中的事件处理与订阅逻辑
更新事件处理方法的参数类型为TestEventArgs,并调整Loader类的委托类型以匹配新事件:
using System; using System.Diagnostics; using System.Reflection; using System.Windows.Forms; namespace WindowsFormsApplication2 { [Serializable] public partial class Form1 : Form { // 修改事件处理方法参数为自定义TestEventArgs void HandleEvent(object sender, TestEventArgs e) { Debug.WriteLine("HandleEvent called"); // 捕获并使用事件传递的返回参数 Debug.WriteLine($"事件返回参数值: {e.ReturnValue}"); } string DLL = @"..\ConsoleApplication1\ClassLibrary1\bin\Debug\ClassLibrary1.dll"; public Form1() { InitializeComponent(); // 订阅事件时使用泛型EventHandler<TestEventArgs>委托 Loader.Call(DLL, "ClassLibrary1.Class1", "RaiseEvent", new EventHandler<TestEventArgs>(HandleEvent), DateTime.Now.ToShortDateString()); } private void button1_Click(object sender, EventArgs e) { Application.Restart(); this.Close(); } } public class Loader : MarshalByRefObject { static AppDomain ad = AppDomain.CreateDomain("Test"); // 将参数中的EventHandler改为Delegate,适配泛型事件委托 object CallInternal(string dll, string typename, string method, Delegate handler, object[] parameters) { Assembly a = Assembly.LoadFile(dll); object o = a.CreateInstance(typename); Type t = o.GetType(); // 获取事件信息并订阅 EventInfo eventInfo = t.GetEvent("TestEvent"); eventInfo.AddEventHandler(o, handler); MethodInfo m = t.GetMethod(method); return m.Invoke(o, parameters); } public static object Call(string dll, string typename, string method, Delegate handler, params object[] parameters) { Loader ld = (Loader)ad.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(Loader).FullName); object result = ld.CallInternal(dll, typename, method, handler, parameters); AppDomain.Unload(ad); return result; } } // 自定义可序列化事件参数类,需确保ClassLibrary1也能访问该类型 // 最优方案是将该类放在独立类库中,让两个项目都引用;也可复制代码到ClassLibrary1项目中 [Serializable] public class TestEventArgs : EventArgs { public int ReturnValue { get; set; } } }
关键注意事项
- 序列化要求:自定义
TestEventArgs必须标记[Serializable],否则跨AppDomain传递时会抛出序列化异常。 - 类型一致性:确保
TestEventArgs类型在WinForm和ClassLibrary1中完全一致(命名空间、类名、属性都相同),最稳妥的方式是将该类放在独立的类库项目中,让两个项目都引用。 - 委托类型适配:Loader类的
CallInternal方法参数改用Delegate而非EventHandler,以适配泛型事件委托类型。
内容的提问来源于stack exchange,提问作者Crystal Crystal
相关产品推荐
相关产品推荐

