VB.NET 中的动态事件
如何在 VB.NET 中处理 COM 对象的动态、后期绑定事件。
引言
本文展示了一种使用后期绑定 COM 对象事件的方法。为了解决这个问题,使用了 C# 脚本...
背景
在 VB.NET 中创建后期绑定对象有两种常见方法。
-
使用
CreateObject
方法。Dim ie As Object = CreateObject("InternetExplorer.Application")
-
使用
System.Activator.CreateInstance
方法。Dim iet As Type = Type.GetTypeFromProgID("InternetExplorer.Application") Dim ie As Object = Activator.CreateInstance(iet)
在 VB.NET 中调用后期绑定对象上的方法和属性也有三种常见方法。
-
在关闭
Option Strict
的情况下' call a method ie.Navigate("about:blank") ' read a property Dim readyState As Integer = ie.ReadyState ' write a property ie.Visible = True
-
使用
Microsoft.VisualBasic.Interaction.CallByName
方法。' Call a method CallByName(ie, "Navigate", CallType.Method, "about:blank") ' Read a property readyState = CInt(CallByName(ie, "ReadyState", CallType.Get)) ' Write a property CallByName(ie, "Visible", CallType.Let, True)
-
使用
System.Type.InvokeMember
方法。' Call a method ie.GetType().InvokeMember("Navigate", BindingFlags.InvokeMethod, Type.DefaultBinder, ie, {"about:blank"}) ' Read a property readyState = CInt(ie.GetType().InvokeMember("ReadyState", BindingFlags.GetProperty, Type.DefaultBinder, ie, Nothing)) ' Write a property ie.GetType().InvokeMember("Visible", BindingFlags.SetProperty, Type.DefaultBinder, ie, {True})
已经讨论了一些如何使用后期绑定对象事件的方法,请参阅 Google 搜索 ' Eventhandler for late bound objects)。但没有办法像在 C# 中使用 dynamic Type 那样为后期绑定 COM 对象添加事件处理程序。
-
在 C# 中工作
public delegate void OnQuitDelegate(); static void Main(string[] args) { var iet = Type.GetTypeFromProgID("InternetExplorer.Application"); dynamic ie = Activator.CreateInstance(iet); ie.OnQuit += new OnQuitDelegate(ie_OnQuit); } public static void ie_OnQuit() { }
-
在 VB.NET 中不起作用
Dim iet As Type = Type.GetTypeFromProgID("InternetExplorer.Application") ' No dynamic type... Dim ie = Activator.CreateInstance(iet) ' Not working... ' AddHandler ie.OnQuit, AddressOf ie_OnQuit ' Not working also... 'ie.OnQuit += New OnQuitDelegate(AddressOf ie_OnQuit)
最初的想法是在 C# 中编写一个库,但事件名称是硬编码的。所以,除了...在运行时编译 C# 脚本时,这也不可行。
-
因此,首先创建了一个接口,以便调用编译后的 C# 脚本。
Public Interface IEventBinder Sub [AddHandler](ByVal source As Object, ByVal [delegate] As [Delegate]) Sub [RemoveHandler](ByVal source As Object, ByVal [delegate] As [Delegate]) End Interface
-
然后编写了一个类,该类实现了该接口,并带有编译和返回类/接口实例的工厂方法。从那时起,就可以将事件附加到对象并从对象中分离事件了。
Imports Microsoft.CSharp Imports System.CodeDom Imports System.CodeDom.Compiler Imports System.Reflection Imports System.Text Public NotInheritable Class EventBinder Implements IEventBinder Private _name As String Private _event As IEventBinder Private Sub New(ByVal name As String, ByVal [event] As IEventBinder) Me._name = name Me._event = [event] End Sub Public ReadOnly Property Name As String Get Return _name End Get End Property Public Sub [AddHandler](source As Object, [delegate] As System.Delegate) Implements IEventBinder.AddHandler Me._event.AddHandler(source, [delegate]) End Sub Public Sub [RemoveHandler](source As Object, [delegate] As System.Delegate) Implements IEventBinder.RemoveHandler Me._event.RemoveHandler(source, [delegate]) End Sub Public Shared Function CreateEventBinders(ByVal names() As String) As Dictionary(Of String, EventBinder) Using provider As CodeDomProvider = New CSharpCodeProvider() Dim parameters As New CompilerParameters() With { .GenerateInMemory = True } parameters.ReferencedAssemblies.AddRange({ "System.dll", "System.Core.dll", Assembly.GetExecutingAssembly().Location, "Microsoft.CSharp.dll" }) Dim uid As String = Guid.NewGuid().ToString("N") Dim code As New StringBuilder() code.Append( "using System;" & vbCrLf & "" & vbCrLf & "namespace DynamicEvent {" & vbCrLf ) For Each name As String In names code.Append( String.Format( "public class {0}Event{2} : {1}.IEventBinder" & vbCrLf & "{{" & vbCrLf & " public void AddHandler(dynamic source, Delegate del) {{ source.{0} += del; }}" & vbCrLf & " public void RemoveHandler(dynamic source, Delegate del) {{ source.{0} -= del; }}" & vbCrLf & "}}" & vbCrLf, name, GetType(IEventBinder).Namespace, uid ) ) Next code.Append("}") Dim codeResult As CompilerResults = provider.CompileAssemblyFromSource(parameters, code.ToString()) If codeResult.Errors.Count = 0 Then Dim result As New Dictionary(Of String, EventBinder) For Each name As String In names Dim codeType As Type = codeResult.CompiledAssembly.GetType(String.Format("DynamicEvent.{0}Event{1}", name, uid)) Dim codeObject As Object = Activator.CreateInstance(codeType) result.Add(name, New EventBinder(name, DirectCast(codeObject, IEventBinder))) Next Return result Else ' todo: Compiler errors... Return Nothing End If End Using End Function Public Shared Function CreateEventBinder(ByVal name As String) As EventBinder Return CreateEventBinders({name})(name) End Function End Class
使用代码
现在附加事件
-
需要一个委托声明。
Delegate Sub OnQuitDelegate()
-
需要一个事件处理程序。
Sub ie_OnQuit() ' ... End SUb
-
需要委托的一个实例。
Dim [delegate] As [Delegate] = New OnQuitDelegate(AddressOf ie_OnQuit)
-
需要一个特定事件的 EventBinder 实例。
Dim binder As EventBinder = EventBinder.CreateEventBinder("OnQuit")
-
从那时起,就可以附加、分离事件...
Dim ie As Object = CreateObject("InternetExplorer.Application") binder.AddHandler(ie, [delegate]) binder.RemoveHandler(ie, [delegate])
但最好一次性为所有事件创建接口。(不是说速度很重要,VB.NET 的速度是光速...)
- 绑定到多个事件
' Create event binders Dim binders As Dictionary(Of String, EventBinder) binders = EventBinder.CreateEventBinders({ "BeforeNavigate2", "DocumentComplete", "NavigateComplete2", "OnQuit", "StatusTextChange" }) ' Create event handlers Dim delegates() As [Delegate] = { New BeforeNavigate2Delegate(AddressOf ie_BeforeNavigate2), New DocumentCompleteDelegate(AddressOf ie_DocumentComplete), New NavigateComplete2Delegate(AddressOf ie_NavigateComplete2), New OnQuitDelegate(AddressOf ie_OnQuit), New StatusTextChangeDelegate(AddressOf ie_StatusTextChange) } ' Bondage... For i As Integer = 0 To binders.Count - 1 binders(binders.Keys(i)).AddHandler(ie, delegates(i)) Next
历史
- 2013/03/18:初稿。