Как вернуть объект в качестве возвращаемого значения через прозрачный прокси RealProxy?

Я разрабатываю систему, в которой планирую использовать объекты RealProxy, чтобы обеспечить перехват вызовов методов для набора объектов, обработку вызова и последующий возврат соответствующих результатов.

Это работает только для простых возвращаемых типов, таких как строки или целые числа, но я не могу возвращать объекты из метода RealProxy.Invoke.

Все работает. Я не получаю ошибок, но возвращаемое значение всегда НИЧЕГО, а не объект.

Я разработал наименьший образец кода, какой только мог, и включил его ниже.

По сути, просто вызовите RPtest и выполните один шаг. Код создает простой объект RPTestA со строковым полем и полем с объектным значением. Затем он извлекает строку Dim x = c.Name, которая работает нормально, а затем пытается получить объект.

Dim r = c.SubObj

Который всегда ничего не возвращает.

Однако в подпрограмме FieldGetter этот код:

'---- the field is an OBJECT type field  
Dim mc = New MethodCallMessageWrapper(Msg)  

'---- create the object  
Dim o = Activator.CreateInstance(t)  
'---- and construct the return message with that object  
Dim r = New ReturnMessage(o, mc.Args, mc.Args.Length, mc.LogicalCallContext, mc)  
Return r  

кажется, работает просто отлично, устанавливая поле ReturnValue в ReturnMessage для объекта, который был создан вызовом Activator.CreateInstance(t) чуть выше.

Я подозреваю, что это какая-то сериализация, но я в недоумении.

Вы сможете сразу запустить этот код, просто вставив его в новый проект VB.net.

'----------------------------------------------------------------------------
Imports System.Security.Permissions
Imports System.Diagnostics
Imports System.Reflection
Imports System.Runtime.CompilerServices
Imports System.Runtime.Serialization
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Activation
Imports System.Runtime.Remoting.Messaging
Imports System.Runtime.Remoting.Proxies


Public Module RPTest
    Public Sub RPTest()
        '---- create a new object that is automatically proxied
        '     See the RPProxyAttribute for details
        Dim c = New RPTestA

        Dim x = c.Name
        'x is returned as a string value just fine
        Dim r = c.SubObj
        '********* PROBLEM IS HERE, r ends up nothing
    End Sub
End Module


'ROOT test object
Public Class RPTestA
    Inherits RPBase

    Public Name As String = "Test Name"
    Public SubObj As RPTestB

End Class


'SUB OBJECT which should be returned as a field value from the root object above
Public Class RPTestB
    Inherits RPBase

    Public SubProperty As String = "SubObj Test Property"
End Class


''' <summary>
''' Base proxyable object class
''' </summary>
''' <remarks></remarks>
<RPProxy()> _
Public MustInherit Class RPBase
    Inherits ContextBoundObject

End Class


<PermissionSet(SecurityAction.Demand, Name:="FullTrust")> _
Public Class RPProxy
    Inherits RealProxy

    Private m_target As MarshalByRefObject


    Public Sub New()
        m_target = DirectCast(Activator.CreateInstance(GetType(ConfigRP)), MarshalByRefObject)
        Dim myObjRef = RemotingServices.Marshal(m_target)
    End Sub

    Public Sub New(ByVal classToProxy As Type)
        MyBase.New(classToProxy)
    End Sub


    Public Sub New(ByVal ClassToProxy As Type, ByVal targetObject As MarshalByRefObject)
        m_target = targetObject
        Dim myObjRef = RemotingServices.Marshal(m_target)
    End Sub


    Public Overrides Function Invoke(ByVal msg As IMessage) As IMessage
        Dim returnMsg As IMethodReturnMessage = Nothing

        If TypeOf msg Is IConstructionCallMessage Then
            '---- handle constructor calls
            Dim ConstructionCallMessage = DirectCast(msg, IConstructionCallMessage)
            returnMsg = InitializeServerObject(ConstructionCallMessage)
            Me.m_target = Me.GetUnwrappedServer()
            SetStubData(Me, Me.m_target)
            Return returnMsg

        ElseIf TypeOf msg Is IMethodCallMessage Then
            '---- handle all other method calls
            Dim methodCallMessage = DirectCast(msg, IMethodCallMessage)

            '---- before message processing
            preprocess(methodCallMessage)

            '---- execute the method call
            Dim rawReturnMessage = RemotingServices.ExecuteMessage(Me.m_target, methodCallMessage)

            '---- and postprocess
            returnMsg = postprocess(methodCallMessage, rawReturnMessage)

        Else
            Throw New NotSupportedException()
        End If

        Return returnMsg
    End Function


    'Called BEFORE the actual method is invoked
    Private Sub PreProcess(ByVal msg As IMessage)
        Console.WriteLine("before method call...")
    End Sub


    'Called AFTER the actual method is invoked
    Private Function PostProcess(ByVal Msg As IMethodCallMessage, ByVal msgReturn As ReturnMessage) As ReturnMessage
        Dim r As ReturnMessage
        If Msg.MethodName = "FieldGetter" Then
            r = FieldGetter(Msg, msgReturn)
        ElseIf Msg.MethodName = "FieldSetter" Then
            'na
            r = msgReturn
        ElseIf Msg.MethodName.StartsWith("get_") Then
            'na
            r = msgReturn
        ElseIf Msg.MethodName.StartsWith("set_") Then
            'na
            r = msgReturn
        Else
            r = msgReturn
        End If
        Return r
    End Function


    Private Function FieldGetter(ByVal Msg As IMethodCallMessage, ByVal msgReturn As IMethodReturnMessage) As IMethodReturnMessage
        Dim t = Me.Target.GetType

        '---- This retrieves the type of the field that the getter should retrieve
        t = t.GetField(Msg.InArgs(1), BindingFlags.Instance Or BindingFlags.Public).FieldType

        If t.Name = "String" Then
            '---- just return what the object returned as a result of ExecuteMessage
            Return msgReturn

        ElseIf t.BaseType.Equals(GetType(RPBase)) Then
            '---- the field is an OBJECT type field
            Dim mc = New MethodCallMessageWrapper(Msg)
            '---- create the object
            Dim o = Activator.CreateInstance(t)
            '---- and construct the return message with that object
            Dim r = New ReturnMessage(o, mc.Args, mc.Args.Length, mc.LogicalCallContext, mc)
            Return r

        Else
            Return msgReturn
        End If
    End Function


    Public Property Target() As Object
        Get
            Return Me.m_target
        End Get
        Set(ByVal value As Object)
            Me.m_target = value
        End Set
    End Property
End Class


<AttributeUsage(AttributeTargets.Class)> _
<SecurityPermissionAttribute(SecurityAction.Demand, Flags:=SecurityPermissionFlag.Infrastructure)> _
Public Class RPProxyAttribute
    Inherits ProxyAttribute


    Public Overrides Function CreateInstance(ByVal Type As Type) As MarshalByRefObject
        Dim proxy = New RPProxy(Type)
        Dim transparentProxy = DirectCast(proxy.GetTransparentProxy(), MarshalByRefObject)
        Return transparentProxy
    End Function
End Class

person DarinH    schedule 07.10.2010    source источник


Ответы (1)


Что ж, это оказывается довольно простым решением, как только вы обойдете чертовски ужасный конструктор ReturnMessage, который вводит в заблуждение!

Большое спасибо моему старому коллеге, Ричу Квакенбушу, за то, что уделил несколько минут и проверил это. Иногда за деревьями не видно леса!

Во всяком случае, в FieldGetter я делал это

ElseIf t.BaseType.Equals(GetType(RPBase)) Then
        '---- the field is an OBJECT type field
        Dim mc = New MethodCallMessageWrapper(Msg)
        '---- create the object
        Dim o = Activator.CreateInstance(t)
        '---- and construct the return message with that object
        Dim r = New ReturnMessage(o, mc.Args, mc.Args.Length, mc.LogicalCallContext, mc)
        Return r

Кажется вполне разумным, что вновь созданный объект передается в аргумент конструктора ReturnMessage с именем ReturnValue.

Но нет. На самом деле вам нужно создать массив объектов и передать его как элемент 3 в этом массиве, например:

ElseIf t.BaseType.Equals(GetType(RPBase)) Then
        '---- the field is an OBJECT type field
        Dim mc = New MethodCallMessageWrapper(Msg)            '---- create the object
        Dim o = Activator.CreateInstance(t)
        '---- and construct the return message with that object
        Dim r = New ReturnMessage(Nothing, New Object() {Nothing, Nothing, o}, 3, mc.LogicalCallContext, mc)
        Return r

Оказывается, это потому, что функция FieldGetter - это то, что "вызывается" и перехватывается прокси, и ее подпись

FieldGetter(StringtypeName,StringfieldName,Object&val)

Что для целей создания ReturnMessage для этого вызова означает, что у него вообще нет возвращаемого значения, а вместо этого возвращаемое значение возвращается в качестве третьего аргумента в этом списке.

Поскольку на самом деле я не вызываю настоящую функцию FieldGetter, первые два аргумента (имя типа и имя поля) несущественны, но третий аргумент — это правильное место для размещения возвращаемого значения.

Это всегда очевидно задним числом!

Большое спасибо Ричу.

person DarinH    schedule 15.10.2010
comment
Если бы я знал, что это было на SO, я бы ответил здесь! - person RQDQ; 15.10.2010
comment
Упс. Извиняюсь. Вот что ты получаешь, если знаешь меня лично ‹g› - person DarinH; 17.10.2010