如何在目标函数中用枚举类型的参数实现反射

时间:2012-08-07 16:41:41

标签: vb.net

这是一个测试代码:

Public Class Class2
   Public GoodName as String
   Public Function someFunction ()
   End Function
End Class

Public Class Class1
    Public Enum ENUM_VAL1_
       abc = 1
       def = 2
    End Enum

    Public Enum ERROR_VAL1_
       yhn = 1
       ujm = 2
    End Enum

    Public Function fun1(byval _enumArg1 as ENUM_VAL1_,
                         byval _stringArg as String,
                         byref _classArg Class2) as ERROR_VAL1_
       ... SOMETHING HERE
       return something
    End Function
End Class

和测试

    Dim asm As Assembly = Assembly.LoadFrom(<the source>)
    If asm Is Nothing Then Return Nothing

    Dim typ As Type = asm.GetType(<namespace>.<class1>)
    If typ Is Nothing Then Return Nothing

    Dim obj As Object = Activator.CreateInstance(typ)
    If obj Is Nothing Then Return Nothing

    Dim arg As Object() = New Object() {ENUM_VAL1_.abc, "qazxsw", New Class2}

    Dim res As Object = typ.InvokeMember( _
        "fun1", _
        BindingFlags.[Default] Or BindingFlags.InvokeMethod, _
        Nothing, _
        obj, _
        arg)

[Dim res As Object = typ.InvokeMember( _ "fun1", _ BindingFlags.[Default] Or BindingFlags.InvokeMethod, _ Nothing, _ obj, _ arg)] 返回错误:function '[fullname]' not found.

然后我意识到fun1签名与我的调用不同(即使枚举值是整数)。我做了一些研究,当目标程序集上的属性或函数具有与标准类型不同的参数时,我发现了一些关于如何实现反射的“一些”示例。但我成功地将这些样本“概念”转化为我的需要。

所以,我在这里放了一些带有虚拟枚举类型的虚拟类,只是为了指出问题的骨架。

原始代码是关于windows防火墙/端口(win7 / xp / vista),其中有许多枚举值,包括来自firewallApi.dll和Hnetcfg.dll的类型。

我遇到的问题是我不能使用以下语句“InvokeMember”(也称为“SetProperties”和“SetProperties”):

Dim args As Object() = New Object() {"SQL", 6, "1433", 1}

其中我的汇编函数有这些参数类型。

Public Function PortExists( _
    ByVal _ruleName As String, _
    ByVal _protocol As NET_FW_IP_PROTOCOL_, _
    ByVal _remotePorts As String, _
    ByVal _direction As NET_FW_RULE_DIRECTION_ _
) As FW_ERROR_CODE

好的,为了简化我制作了自己的枚举类型(不需要安装额外的库)

Public Function PortExists( _
    ByVal _ruleName As String, _
    ByVal _protocol As FW_IP_PROTOCOL, _
    ByVal _remotePorts As String, _
    ByVal _direction As FW_RULE_DIRECTION _
) As FW_ERROR_CODE

不知何论,参数'6'和'1'应分别转换为FW_IP_PROTOCOLFW_RULE_DIRECTION类型......

我不知道怎么做!

2 个答案:

答案 0 :(得分:0)

通常一个好的设计会使Reflection变得多余。我的建议是让不同的对象实现一些定义要调用的函数的通用接口。

通用定义:

Public Enum MyValueEnum
    value1 = 1
    value2 = 2
End Enum

Public Enum MyErrorEnum
    error1 = 1
    error2 = 2
End Enum

Public Class SomeClass
    Public GoodName As String

    Public Function SomeFunction() As Object
        Return Nothing
    End Function
End Class

界面

Public Interface IMyInterface
    Function TheFunction(ByVal val As MyValueEnum, ByVal name As String, _
                         ByRef someObject As SomeClass) As MyErrorEnum
End Interface

实现接口的两个不同的类

Public Class SomeImplementation
  Implements IMyInterface

  Public Function TheFunction(ByVal val As MyValueEnum, ByVal name As String, _
                              ByRef someObject As SomeClass) As MyErrorEnum _
      Implements IMyInterface.TheFunction
      '...
      Return MyErrorEnum.error1
  End Function
End Class

Public Class SomeOtherImplementation
  Implements IMyInterface

  Public Function TheFunction(ByVal val As MyValueEnum, ByVal name As String, _
                              ByRef someObject As SomeClass) As MyErrorEnum _
      Implements IMyInterface.TheFunction
      '...
      Return MyErrorEnum.error2
  End Function
End Class

测试

Dim obj As IMyInterface
Dim result As MyErrorEnum

obj = New SomeImplementation()
result = obj.TheFunction(MyValueEnum.value1, "test", New SomeClass())

obj = New SomeOtherImplementation()
result = obj.TheFunction(MyValueEnum.value2, "another test", New SomeClass())

答案 1 :(得分:0)

好的,这是解决方案。

当我们从反射中使用Invoke方法时,我们必须将参数作为对象(object())传递,并且类型完全匹配。 即使枚举类型的值是一个整数(这是我原来的问题),我必须将此值转换为引用程序集类型的确切枚举类型。所以以下函数&lt; _FEnumToObject&gt;会做这个工作。我添加了_F来推送列表底部的所有私有函数并将它们分组(IDE属性列表); _S代表sub和_P代表属性......

Private Function _FEnumToObject( _
    ByVal _typeName As String, _
    ByVal _typeValue As String _
) As Object
    Dim enumType As Type = m_Assembly.[GetType](_typeName)
    Dim enumInfo As FieldInfo = enumType.GetField(_typeValue)
    Dim valueINT As Integer = CInt(enumInfo.GetValue(enumType))
    Dim valueOBJ As Object = [Enum].ToObject(enumType, valueINT)
    Return valueOBJ
End Function

下一个函数&lt; _FProtocolToObject&gt;用于将协议值(enumurated)转换为对象。

Public Function _FProtocolToObject(ByVal _protocol As FW_IP_PROTOCOL) As Object
    'where <m_AssemblyRef> is like "wf7."
    Return _FEnumToObject(m_AssemblyRef + "FW_IP_PROTOCOL", _protocol.ToString)
End Function

这是最后的决定:

Public Function PortExists( _
    ByVal _ruleName As String, _
    ByVal _protocol As FW_IP_PROTOCOL, _
    ByVal _portNumber As Integer _
) As Boolean
    If m_AssemblyNotFound Then Return False
    Static mi As MethodInfo = m_RemoteType.GetMethod("PortExists")
    Dim m As FW_ERROR_CODE = FW_ERROR_CODE.UNKNOWN
    Try
        Dim a As Object = New Object() _
            { _
                _ruleName, _
                _FProtocolToObject(_protocol), _
                _portNumber _
            }
        m = mi.Invoke(m_RemoteObject, a)

    Catch ex As Exception
        'some error handler here...

    End Try
    Return (m = FW_ERROR_CODE.SUCCESS)
End Function

我还发布了一些额外的代码来在我的示例代码中提供一些无法解释/未设置的变量赋值。

Private ReadOnly Property _PAssemblyPath() As String
    Get
        Dim ad As String = IO.Path.Combine(My.Application.Info.DirectoryPath, "bin")
        Dim af As String = IO.Path.Combine(ad, _PAssemblyName + ".dll")
        Return af
    End Get
End Property

Public Sub New()

    m_AssemblyPath = _PAssemblyPath
    m_AssemblyRef = _PAssemblyName + "."
    m_ClassPath = m_AssemblyRef + _PClassName

    If IO.File.Exists(m_AssemblyPath) Then

        Dim name As New AssemblyName()
        name.CodeBase = m_AssemblyPath
        m_Assembly = AppDomain.CurrentDomain.Load(name)
        m_RemoteObject = m_Assembly.CreateInstance(m_ClassPath)
        m_RemoteType = m_Assembly.[GetType](m_ClassPath)
        m_AssemblyNotFound = False

    Else
        'some error handler here

    End If

End Sub

假设'SCSysInfo'是用于检查OS系统等的实用程序类......

Private ReadOnly Property _PClassName() As String
    Get
        Dim s As String = "CFwRule"
        If SCSysInfo.IsWin7 Then
            s += "7"
        ElseIf SCSysInfo.IsWinVista Then
            s += "Vista"
        ElseIf SCSysInfo.IsWinXP Then
            s += "XP"
        End If
        Return s
    End Get
End Property
Private ReadOnly Property _PAssemblyName() As String
    Get
        Dim s As String = String.Empty
        If SCSysInfo.IsWin7 Then
            s = "fw7"
        ElseIf SCSysInfo.IsWinVista Then
            s = "fwv"
        ElseIf SCSysInfo.IsWinXP Then
            s = "fwxp"
        End If
        Return s
    End Get
End Property
我的班级(上面部分列出)中的'PortExists'功能还有两个针对vista和win7的签名。有一点我必须提到反射不能识别方法/属性多重签名。使用反射时,您必须只有一个方法或属性的签名。谈到我的情况:win7,vista和xp为端口编程完全不同的属性。在每个程序集(我在各自的环境中编译)中,存在具有不同参数数量的“相同”方法和属性。从我的“适配器”类(其中一部分在上面发布)我从具有3个不同签名的所有3个程序集中调用“相同”方法(基于OS我选择适当的签名)。所以,我的所有3个“PortExists”方法的签名都在我的“适配器”类中。我希望我的解释有意义......谢谢,