From 255bf0aa1d4d02a7824d24296466c15b781ada8f Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 30 Aug 2023 18:04:39 -0700 Subject: [PATCH 1/3] Add late binding lookup in VB.NET for COM objects --- .../CompilerServices/NewLateBinding.vb | 111 +++++++++++------- .../VisualBasic/CompilerServices/Symbols.vb | 6 + 2 files changed, 75 insertions(+), 42 deletions(-) diff --git a/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb b/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb index de0bde5e73a2f0..f611f5c0d93ea2 100644 --- a/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb +++ b/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb @@ -47,6 +47,10 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference = New Container(Instance) End If + If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then + Return LateBinding.InternalLateCall(Instance, Type, MemberName, Arguments, ArgumentNames, CopyBack, IgnoreReturn) + End If + Dim idmop As IDynamicMetaObjectProvider = IDOUtils.TryCastToIDMOP(Instance) If idmop IsNot Nothing AndAlso TypeArguments Is NoTypeArguments Then Return IDOBinder.IDOCall(idmop, MemberName, Arguments, ArgumentNames, CopyBack, IgnoreReturn) @@ -139,7 +143,7 @@ Namespace Microsoft.VisualBasic.CompilerServices ' LateCallInvokeDefault is used to optionally invoke the default action on a call target. ' If the arguments are non-empty, then it isn't optional, and is treated ' as an error if there is no default action. - ' Currently we can get here only in the process of execution of NewLateBinding.LateCall. + ' Currently we can get here only in the process of execution of NewLateBinding.LateCall. @@ -155,7 +159,7 @@ Namespace Microsoft.VisualBasic.CompilerServices ' LateGetInvokeDefault is used to optionally invoke the default action. ' If the arguments are non-empty, then it isn't optional, and is treated ' as an error if there is no default action. - ' Currently we can get here only in the process of execution of NewLateBinding.LateGet. + ' Currently we can get here only in the process of execution of NewLateBinding.LateGet. @@ -167,7 +171,7 @@ Namespace Microsoft.VisualBasic.CompilerServices ' According to a comment in VBGetBinder.FallbackInvoke, this function is called when ' "The DLR was able to resolve o.member, but not o.member(args)" - ' When NewLateBinding.LateGet is evaluating similar expression itself, it never tries to invoke default action + ' When NewLateBinding.LateGet is evaluating similar expression itself, it never tries to invoke default action ' if arguments are not empty. It simply returns result of evaluating o.member. I believe, it makes sense ' to follow the same logic here. I.e., if there are no arguments, simply return the instance unless it is an IDO. @@ -265,19 +269,22 @@ Namespace Microsoft.VisualBasic.CompilerServices Private Shared Function InternalLateIndexGet( - ByVal instance As Object, - ByVal arguments() As Object, - ByVal argumentNames() As String, - ByVal reportErrors As Boolean, - ByRef failure As ResolutionFailure, - ByVal copyBack As Boolean()) As Object + ByVal Instance As Object, + ByVal Arguments() As Object, + ByVal ArgumentNames() As String, + ByVal ReportErrors As Boolean, + ByRef Failure As ResolutionFailure, + ByVal CopyBack As Boolean()) As Object - failure = ResolutionFailure.None + Failure = ResolutionFailure.None - If arguments Is Nothing Then arguments = NoArguments - If argumentNames Is Nothing Then argumentNames = NoArgumentNames + If Arguments Is Nothing Then Arguments = NoArguments + If ArgumentNames Is Nothing Then ArgumentNames = NoArgumentNames - Dim baseReference As Container = New Container(instance) + Dim baseReference As Container = New Container(Instance) + If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then + Return LateBinding.LateIndexGet(Instance, Arguments, ArgumentNames) + End If 'An r-value expression o(a) has two possible forms: ' 1: o(a) array lookup--where o is an array object and a is a set of indices @@ -286,10 +293,10 @@ Namespace Microsoft.VisualBasic.CompilerServices If baseReference.IsArray Then 'This is an array lookup o(a). - If argumentNames.Length > 0 Then - failure = ResolutionFailure.InvalidArgument + If ArgumentNames.Length > 0 Then + Failure = ResolutionFailure.InvalidArgument - If reportErrors Then + If ReportErrors Then Throw New ArgumentException(SR.Argument_InvalidNamedArgs) End If @@ -297,21 +304,21 @@ Namespace Microsoft.VisualBasic.CompilerServices End If ' Initialize the copy back array to all ByVal - ResetCopyback(copyBack) - Return baseReference.GetArrayValue(arguments) + ResetCopyback(CopyBack) + Return baseReference.GetArrayValue(Arguments) End If 'This is a default member access o.d(a), which is a call to method "". Return CallMethod( baseReference, "", - arguments, - argumentNames, + Arguments, + ArgumentNames, NoTypeArguments, - copyBack, + CopyBack, BindingFlagsInvokeMethod Or BindingFlagsGetProperty, - reportErrors, - failure) + ReportErrors, + Failure) End Function @@ -372,6 +379,10 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference = New Container(Instance) End If + If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then + Return LateBinding.LateGet(Instance, Type, MemberName, Arguments, ArgumentNames, CopyBack) + End If + Dim invocationFlags As BindingFlags = BindingFlagsInvokeMethod Or BindingFlagsGetProperty Dim idmop As IDynamicMetaObjectProvider = IDOUtils.TryCastToIDMOP(Instance) @@ -617,16 +628,16 @@ Namespace Microsoft.VisualBasic.CompilerServices Friend Shared Sub ObjectLateIndexSetComplex( - ByVal instance As Object, - ByVal arguments As Object(), - ByVal argumentNames As String(), - ByVal optimisticSet As Boolean, - ByVal rValueBase As Boolean) + ByVal Instance As Object, + ByVal Arguments As Object(), + ByVal ArgumentNames As String(), + ByVal OptimisticSet As Boolean, + ByVal RValueBase As Boolean) - If arguments Is Nothing Then arguments = NoArguments - If argumentNames Is Nothing Then argumentNames = NoArgumentNames + If Arguments Is Nothing Then Arguments = NoArguments + If ArgumentNames Is Nothing Then ArgumentNames = NoArgumentNames - Dim baseReference As Container = New Container(instance) + Dim baseReference As Container = New Container(Instance) 'An l-value expression o(a) has two possible forms: ' 1: o(a) = v array lookup--where o is an array object and a is a set of indices @@ -635,24 +646,28 @@ Namespace Microsoft.VisualBasic.CompilerServices If baseReference.IsArray Then 'This is an array lookup and assignment o(a) = v. - If argumentNames.Length > 0 Then + If ArgumentNames.Length > 0 Then Throw New ArgumentException(SR.Argument_InvalidNamedArgs) End If - baseReference.SetArrayValue(arguments) + baseReference.SetArrayValue(Arguments) Return End If - If argumentNames.Length > arguments.Length Then + If ArgumentNames.Length > Arguments.Length Then Throw New ArgumentException(SR.Argument_InvalidValue) End If - If arguments.Length < 1 Then + If Arguments.Length < 1 Then 'We're binding to a Set, we must have at least the Value argument. Throw New ArgumentException(SR.Argument_InvalidValue) End If Dim methodName As String = "" + If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then + LateBinding.LateIndexSetComplex(Instance, Arguments, ArgumentNames, OptimisticSet, RValueBase) + Return + End If Dim invocationFlags As BindingFlags = BindingFlagsSetProperty @@ -664,8 +679,8 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference, methodName, members, - arguments, - argumentNames, + Arguments, + ArgumentNames, NoTypeArguments, invocationFlags, False, @@ -673,7 +688,7 @@ Namespace Microsoft.VisualBasic.CompilerServices If failure = OverloadResolution.ResolutionFailure.None Then - If rValueBase AndAlso baseReference.IsValueType Then + If RValueBase AndAlso baseReference.IsValueType Then Throw New Exception( SR.Format( SR.RValueBaseForValueType, @@ -681,10 +696,10 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference.VBFriendlyName)) End If - baseReference.InvokeMethod(targetProcedure, arguments, Nothing, invocationFlags) + baseReference.InvokeMethod(targetProcedure, Arguments, Nothing, invocationFlags) Return - ElseIf optimisticSet Then + ElseIf OptimisticSet Then Return Else @@ -693,8 +708,8 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference, methodName, members, - arguments, - argumentNames, + Arguments, + ArgumentNames, NoTypeArguments, invocationFlags, True, @@ -927,6 +942,18 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference = New Container(Instance) End If + If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then + Try + LateBinding.InternalLateSet(Instance, Type, MemberName, Arguments, ArgumentNames, OptimisticSet, CallType) + If (RValueBase And Type.IsValueType) Then + Throw New Exception(Utils.GetResourceString(SR.RValueBaseForValueType, Utils.VBFriendlyName(Type, Instance), Utils.VBFriendlyName(Type, Instance))) + End If + Return + Catch ex As MissingMemberException When OptimisticSet + Return + End Try + End If + Dim invocationFlags As BindingFlags ' If we have a IDO that implements TryGetMember for a property but not TrySetMember then we could land up diff --git a/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/Symbols.vb b/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/Symbols.vb index 8301ee324af3fc..44c694595b35bb 100644 --- a/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/Symbols.vb +++ b/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/Symbols.vb @@ -823,6 +823,12 @@ Namespace Microsoft.VisualBasic.CompilerServices End Get End Property + Friend ReadOnly Property IsCOMObject() As Boolean + Get + Return _type.IsCOMObject + End Get + End Property + Friend ReadOnly Property VBFriendlyName() As String Get Return Utils.VBFriendlyName(_type, _instance) From 9e9027262690fe91e428eb79ab237b8d1dac84c1 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 31 Aug 2023 11:35:06 -0700 Subject: [PATCH 2/3] Review feedback --- .../CompilerServices/NewLateBinding.vb | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb b/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb index f611f5c0d93ea2..42d4ac077a6cda 100644 --- a/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb +++ b/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb @@ -269,21 +269,21 @@ Namespace Microsoft.VisualBasic.CompilerServices Private Shared Function InternalLateIndexGet( - ByVal Instance As Object, - ByVal Arguments() As Object, - ByVal ArgumentNames() As String, - ByVal ReportErrors As Boolean, - ByRef Failure As ResolutionFailure, - ByVal CopyBack As Boolean()) As Object + ByVal instance As Object, + ByVal arguments() As Object, + ByVal argumentNames() As String, + ByVal reportErrors As Boolean, + ByRef failure As ResolutionFailure, + ByVal copyBack As Boolean()) As Object - Failure = ResolutionFailure.None + failure = ResolutionFailure.None - If Arguments Is Nothing Then Arguments = NoArguments - If ArgumentNames Is Nothing Then ArgumentNames = NoArgumentNames + If arguments Is Nothing Then arguments = NoArguments + If argumentNames Is Nothing Then argumentNames = NoArgumentNames - Dim baseReference As Container = New Container(Instance) + Dim baseReference As Container = New Container(instance) If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then - Return LateBinding.LateIndexGet(Instance, Arguments, ArgumentNames) + Return LateBinding.LateIndexGet(instance, arguments, argumentNames) End If 'An r-value expression o(a) has two possible forms: @@ -293,10 +293,10 @@ Namespace Microsoft.VisualBasic.CompilerServices If baseReference.IsArray Then 'This is an array lookup o(a). - If ArgumentNames.Length > 0 Then - Failure = ResolutionFailure.InvalidArgument + If argumentNames.Length > 0 Then + failure = ResolutionFailure.InvalidArgument - If ReportErrors Then + If reportErrors Then Throw New ArgumentException(SR.Argument_InvalidNamedArgs) End If @@ -304,21 +304,21 @@ Namespace Microsoft.VisualBasic.CompilerServices End If ' Initialize the copy back array to all ByVal - ResetCopyback(CopyBack) - Return baseReference.GetArrayValue(Arguments) + ResetCopyback(copyBack) + Return baseReference.GetArrayValue(arguments) End If 'This is a default member access o.d(a), which is a call to method "". Return CallMethod( baseReference, "", - Arguments, - ArgumentNames, + arguments, + argumentNames, NoTypeArguments, - CopyBack, + copyBack, BindingFlagsInvokeMethod Or BindingFlagsGetProperty, - ReportErrors, - Failure) + reportErrors, + failure) End Function @@ -628,16 +628,16 @@ Namespace Microsoft.VisualBasic.CompilerServices Friend Shared Sub ObjectLateIndexSetComplex( - ByVal Instance As Object, - ByVal Arguments As Object(), - ByVal ArgumentNames As String(), - ByVal OptimisticSet As Boolean, - ByVal RValueBase As Boolean) + ByVal instance As Object, + ByVal arguments As Object(), + ByVal argumentNames As String(), + ByVal optimisticSet As Boolean, + ByVal rValueBase As Boolean) - If Arguments Is Nothing Then Arguments = NoArguments - If ArgumentNames Is Nothing Then ArgumentNames = NoArgumentNames + If arguments Is Nothing Then arguments = NoArguments + If argumentNames Is Nothing Then argumentNames = NoArgumentNames - Dim baseReference As Container = New Container(Instance) + Dim baseReference As Container = New Container(instance) 'An l-value expression o(a) has two possible forms: ' 1: o(a) = v array lookup--where o is an array object and a is a set of indices @@ -646,26 +646,26 @@ Namespace Microsoft.VisualBasic.CompilerServices If baseReference.IsArray Then 'This is an array lookup and assignment o(a) = v. - If ArgumentNames.Length > 0 Then + If argumentNames.Length > 0 Then Throw New ArgumentException(SR.Argument_InvalidNamedArgs) End If - baseReference.SetArrayValue(Arguments) + baseReference.SetArrayValue(arguments) Return End If - If ArgumentNames.Length > Arguments.Length Then + If argumentNames.Length > arguments.Length Then Throw New ArgumentException(SR.Argument_InvalidValue) End If - If Arguments.Length < 1 Then + If arguments.Length < 1 Then 'We're binding to a Set, we must have at least the Value argument. Throw New ArgumentException(SR.Argument_InvalidValue) End If Dim methodName As String = "" If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then - LateBinding.LateIndexSetComplex(Instance, Arguments, ArgumentNames, OptimisticSet, RValueBase) + LateBinding.LateIndexSetComplex(instance, arguments, argumentNames, optimisticSet, rValueBase) Return End If @@ -679,8 +679,8 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference, methodName, members, - Arguments, - ArgumentNames, + arguments, + argumentNames, NoTypeArguments, invocationFlags, False, @@ -688,7 +688,7 @@ Namespace Microsoft.VisualBasic.CompilerServices If failure = OverloadResolution.ResolutionFailure.None Then - If RValueBase AndAlso baseReference.IsValueType Then + If rValueBase AndAlso baseReference.IsValueType Then Throw New Exception( SR.Format( SR.RValueBaseForValueType, @@ -696,10 +696,10 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference.VBFriendlyName)) End If - baseReference.InvokeMethod(targetProcedure, Arguments, Nothing, invocationFlags) + baseReference.InvokeMethod(targetProcedure, arguments, Nothing, invocationFlags) Return - ElseIf OptimisticSet Then + ElseIf optimisticSet Then Return Else @@ -708,8 +708,8 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference, methodName, members, - Arguments, - ArgumentNames, + arguments, + argumentNames, NoTypeArguments, invocationFlags, True, @@ -945,8 +945,8 @@ Namespace Microsoft.VisualBasic.CompilerServices If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then Try LateBinding.InternalLateSet(Instance, Type, MemberName, Arguments, ArgumentNames, OptimisticSet, CallType) - If (RValueBase And Type.IsValueType) Then - Throw New Exception(Utils.GetResourceString(SR.RValueBaseForValueType, Utils.VBFriendlyName(Type, Instance), Utils.VBFriendlyName(Type, Instance))) + If RValueBase And Type.IsValueType Then + Throw New Exception(Utils.GetResourceString(SR.RValueBaseForValueType, baseReference.VBFriendlyName, baseReference.VBFriendlyName)) End If Return Catch ex As MissingMemberException When OptimisticSet From 067986f29321b5327582a2b35f77f07ede0c0634 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 31 Aug 2023 11:50:34 -0700 Subject: [PATCH 3/3] Feedback --- .../VisualBasic/CompilerServices/NewLateBinding.vb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb b/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb index 42d4ac077a6cda..5d2f624b7a6b93 100644 --- a/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb +++ b/src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/NewLateBinding.vb @@ -47,7 +47,7 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference = New Container(Instance) End If - If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then + If baseReference.IsCOMObject AndAlso Not baseReference.IsWindowsRuntimeObject Then Return LateBinding.InternalLateCall(Instance, Type, MemberName, Arguments, ArgumentNames, CopyBack, IgnoreReturn) End If @@ -282,7 +282,7 @@ Namespace Microsoft.VisualBasic.CompilerServices If argumentNames Is Nothing Then argumentNames = NoArgumentNames Dim baseReference As Container = New Container(instance) - If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then + If baseReference.IsCOMObject AndAlso Not baseReference.IsWindowsRuntimeObject Then Return LateBinding.LateIndexGet(instance, arguments, argumentNames) End If @@ -379,7 +379,7 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference = New Container(Instance) End If - If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then + If baseReference.IsCOMObject AndAlso Not baseReference.IsWindowsRuntimeObject Then Return LateBinding.LateGet(Instance, Type, MemberName, Arguments, ArgumentNames, CopyBack) End If @@ -664,7 +664,7 @@ Namespace Microsoft.VisualBasic.CompilerServices End If Dim methodName As String = "" - If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then + If baseReference.IsCOMObject AndAlso Not baseReference.IsWindowsRuntimeObject Then LateBinding.LateIndexSetComplex(instance, arguments, argumentNames, optimisticSet, rValueBase) Return End If @@ -942,7 +942,7 @@ Namespace Microsoft.VisualBasic.CompilerServices baseReference = New Container(Instance) End If - If baseReference.IsCOMObject And Not baseReference.IsWindowsRuntimeObject Then + If baseReference.IsCOMObject AndAlso Not baseReference.IsWindowsRuntimeObject Then Try LateBinding.InternalLateSet(Instance, Type, MemberName, Arguments, ArgumentNames, OptimisticSet, CallType) If RValueBase And Type.IsValueType Then