Vb.net 仅当运行32位时,使用动态方法的访问冲突

Vb.net 仅当运行32位时,使用动态方法的访问冲突,vb.net,access-violation,il,dynamicmethod,Vb.net,Access Violation,Il,Dynamicmethod,我有以下代码,用于使用.net 3.5(无法切换到Lambda表达式样式)创建一个动态方法来调用VB.net应用程序中属性的Set方法。使用发布的示例,我添加了一个函数,因为它不适用于Int64属性,主要是如果使用常规int调用它,unbox操作将导致无效的强制转换错误。所以我添加了代码来处理这个问题,但是现在我遇到了一个新问题。在64位下运行时,一切正常,但只要我更改为32位进程,调用Int64属性的委托就会导致AccessViolationException,尝试读取或写入受保护的内存。其他

我有以下代码,用于使用.net 3.5(无法切换到Lambda表达式样式)创建一个动态方法来调用VB.net应用程序中属性的Set方法。使用发布的示例,我添加了一个函数,因为它不适用于
Int64
属性,主要是如果使用常规
int
调用它,unbox操作将导致无效的强制转换错误。所以我添加了代码来处理这个问题,但是现在我遇到了一个新问题。在64位下运行时,一切正常,但只要我更改为32位进程,调用
Int64
属性的委托就会导致
AccessViolationException
,尝试读取或写入受保护的内存。其他类型,如字符串,似乎工作良好。请参阅下面的代码,我做错了什么

Public Shared Sub SetFieldData(Instance As Object, PropInfo As PropertyInfo, value As Object)

        Dim Compiled As Action(Of Object, Object) = Nothing
        If Not _PropSetterCache.TryGetValue(PropInfo, Compiled) Then
            SyncLock _PropSetterCache
                If Not _PropSetterCache.TryGetValue(PropInfo, Compiled) Then

                    'http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/
                    Dim setMethod = PropInfo.GetSetMethod()

                    Dim arguments As Type() = New Type(1) {}
                    arguments(0) = GetType(Object)
                    arguments(1) = arguments(0)

                    Dim setter As New DynamicMethod([String].Concat("_Set", PropInfo.Name, "_"), Nothing, arguments, PropInfo.DeclaringType)
                    Dim generator As ILGenerator = setter.GetILGenerator()
                    generator.Emit(OpCodes.Ldarg_0)

                    generator.Emit(OpCodes.Castclass, PropInfo.DeclaringType)

                    If PropInfo.PropertyType.IsClass Then
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Castclass, PropInfo.PropertyType)
                    ElseIf PropInfo.PropertyType Is GetType(Boolean) Then
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, PropInfo.PropertyType)
                    ElseIf PropInfo.PropertyType.IsValueType Then
                        'my stuff, blog example doesn't cover the sent value being different type than the property



                        Dim LByte = generator.DefineLabel
                        Dim LInt = generator.DefineLabel
                        Dim LInt16 = generator.DefineLabel
                        Dim LInt32 = generator.DefineLabel
                        Dim LInt64 = generator.DefineLabel
                        Dim LSByte = generator.DefineLabel
                        Dim LUInt16 = generator.DefineLabel
                        Dim LUInt32 = generator.DefineLabel
                        Dim LUInt64 = generator.DefineLabel
                        Dim LDouble = generator.DefineLabel
                        Dim LSingle = generator.DefineLabel
                        Dim LElse = generator.DefineLabel
                        Dim LEnd = generator.DefineLabel


                        'byte
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Byte))
                        generator.Emit(OpCodes.Brtrue, LByte)
                        'int
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Integer))
                        generator.Emit(OpCodes.Brtrue, LInt)
                        'int16
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Int16))
                        generator.Emit(OpCodes.Brtrue, LInt16)
                        'int32
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Int32))
                        generator.Emit(OpCodes.Brtrue, LInt32)
                        'int64
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Int64))
                        generator.Emit(OpCodes.Brtrue, LInt64)
                        'double 
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Double))
                        generator.Emit(OpCodes.Brtrue, LDouble)
                        'short
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(Single))
                        generator.Emit(OpCodes.Brtrue, LSingle)
                        'sbyte
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(SByte))
                        generator.Emit(OpCodes.Brtrue, LSByte)
                        'uint16
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(UInt16))
                        generator.Emit(OpCodes.Brtrue, LUInt16)
                        'uint32
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(UInt32))
                        generator.Emit(OpCodes.Brtrue, LUInt32)
                        'uint64
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Isinst, GetType(UInt64))
                        generator.Emit(OpCodes.Brtrue, LUInt64)
                        'else
                        generator.Emit(OpCodes.Br, LElse)


                        '
                        generator.MarkLabel(LByte)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Byte))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LInt)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Integer))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LInt16)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Int16))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LInt32)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Int32))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LInt64)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Int64))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LDouble)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Double))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LSingle)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(Single))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LSByte)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(SByte))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LUInt16)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(UInt16))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LUInt32)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(UInt32))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LUInt64)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, GetType(UInt64))
                        generator.Emit(OpCodes.Br, LEnd)
                        '
                        generator.MarkLabel(LElse)
                        generator.Emit(OpCodes.Ldarg_1)
                        generator.Emit(OpCodes.Unbox_Any, PropInfo.PropertyType)
                        generator.Emit(OpCodes.Br, LEnd)

                        generator.MarkLabel(LEnd)


                    End If

                    generator.Emit(OpCodes.Callvirt, setMethod)
                    generator.Emit(OpCodes.Ret)

                    Compiled = setter.CreateDelegate(GetType(Action(Of Object, Object)))


                    _PropSetterCache.Add(PropInfo, Compiled)

                End If
            End SyncLock
        End If

        Compiled(Instance, value)

    End Sub

如果我为.Net 4.5编译代码,我会得到
invalidProgrammeException
,无论它是编译为32位还是64位。这是因为不能简单地将一种类型的值分配给完全不同类型的字段。您需要包括适当的强制转换,这将使您的代码更加复杂。我认为您的代码在某些情况下实际工作的事实是.Net中的一个bug(在较新版本中已修复)

但是,即使在.NET3.5中,也可以使用表达式来实现这一点。您可以在set方法上使用
Call
,而不是使用
Assign
。在VB.NET(或C#)中不能将该方法作为方法调用,但可以在表达式中调用

但要使其正常工作,您需要对该值进行两次转换:首先将其取消装箱,然后将其转换为所需的类型。但正因为如此,生成的代码取决于值的类型。这意味着您需要将缓存的键更改为类似于
Tuple(属性信息的类型)

代码可能如下所示:

Private Shared ReadOnly _PropSetterCache As Dictionary(Of Tuple(Of PropertyInfo, Type), Action(Of Object, Object)) _
    = New Dictionary(Of Tuple(Of PropertyInfo, Type), Action(Of Object, Object))

Public Shared Sub SetFieldData(instance As Object, propInfo As PropertyInfo, value As Object)

    Dim compiled As Action(Of Object, Object) = Nothing
    Dim key = New Tuple(Of PropertyInfo, Type)(propInfo, If(value IsNot Nothing, value.GetType(), GetType(Object)))
    If Not _PropSetterCache.TryGetValue(key, compiled) Then
        SyncLock _PropSetterCache
            If Not _PropSetterCache.TryGetValue(key, compiled) Then
                Dim setMethod = propInfo.GetSetMethod()

                Dim instanceParameter = Expression.Parameter(GetType(Object), "instance")
                Dim castedInstance = Expression.Convert(instanceParameter, propInfo.DeclaringType)

                Dim valueParameter = Expression.Parameter(GetType(Object), "value")
                Dim valueCastedOnce = Expression.Convert(valueParameter, If(value IsNot Nothing, value.GetType(), GetType(Object)))
                Dim valueCastedTwice = Expression.Convert(valueCastedOnce, propInfo.PropertyType)

                Dim callExpression = Expression.Call(castedInstance, setMethod, valueCastedTwice)

                Dim lambda = Expression.Lambda(Of Action(Of Object, Object))(callExpression, instanceParameter, valueParameter)

                compiled = lambda.Compile()

                _PropSetterCache.Add(key, compiled)

            End If
        End SyncLock
    End If

    compiled(instance, value)

End Sub
.Net 3.5没有
元组
,但您可以自己创建一个元组:

NotInheritable Class Tuple(Of T1, T2)
    Implements IEquatable(Of Tuple(Of T1, T2))
    Private ReadOnly _value1 As T1

    Public ReadOnly Property Value1 As T1
        Get
            Return _value1
        End Get
    End Property

    Private ReadOnly _value2 As T2

    Public ReadOnly Property Value2 As T2
        Get
            Return _value2
        End Get
    End Property

    Public Sub New(ByVal value1 As T1, ByVal value2 As T2)
        _value1 = value1
        _value2 = value2
    End Sub

    '' following code generated by R#
    Public Overloads Function Equals(ByVal other As Tuple(Of T1, T2)) As Boolean Implements IEquatable(Of Tuple(Of T1, T2)).Equals
        If ReferenceEquals(Nothing, other) Then Return False
        If ReferenceEquals(Me, other) Then Return True
        Return EqualityComparer(Of T1).[Default].Equals(_value1, other._value1) AndAlso EqualityComparer(Of T2).[Default].Equals(_value2, other._value2)
    End Function

    Public Overloads Overrides Function Equals(ByVal obj As Object) As Boolean
        If ReferenceEquals(Nothing, obj) Then Return False
        If ReferenceEquals(Me, obj) Then Return True
        Return TypeOf obj Is Tuple(Of T1, T2) AndAlso Equals(DirectCast(obj, Tuple(Of T1, T2)))
    End Function

    Public Overrides Function GetHashCode() As Integer
        Dim hashCode = EqualityComparer(Of T1).[Default].GetHashCode(_value1)
        hashCode = CInt((hashCode * 397L) Mod Integer.MaxValue) Xor EqualityComparer(Of T2).[Default].GetHashCode(_value2)
        Return hashCode
    End Function
End Class

“使用.NET3.5(无法切换到Lambda表达式样式)”什么?Lambda表达式在.NET3.5上可用。@Svick,我可以在3.5中使用get,但是
Expression.Assign
仅在4中添加。除非有解决方法,否则我无法使用3.5.Ahh中的Lambda表达式指定属性,我起初没有想到根据不同的值类型生成不同的方法。太好了,谢谢你!作为旁注,我的代码可能导致InvalidProgrameException,因为我的示例遗漏了转换操作码,如Conv_I8。因此,当它在3.5上运行时,会分配一个垃圾值。我想我的访问违规可能是我试图的IL if语句出现了一些问题。不过,我确实通过在Microsoft.VisualBasic.CompilerServices.Conversions中查找并插入对适当函数的调用来修复了这种风格(在令人不安的情况下看到了它)。