Pregunta

Tengo la sensación de que la respuesta a esto va a ser "no es posible", pero voy a darle un tiro ... Estoy en la posición poco envidiable de la modificación de una aplicación de legado VB6 con algunas mejoras. La conversión a un lenguaje más inteligente no es una opción. La aplicación cuenta con una amplia colección de tipos definidos por el usuario para mover datos. Me gustaría definir una función común que puede tomar una referencia a cualquiera de estos tipos y extraer los datos contenidos.
En pseudo código, esto es lo que estoy buscando:

Public Sub PrintUDT ( vData As Variant )
  for each vDataMember in vData
    print vDataMember.Name & ": " & vDataMember.value 
  next vDataMember 
End Sub

Parece que esta información debe estar disponible a algún lugar com ... Cualquier gurús VB6 se preocupan por ahí a tomar una foto?

Gracias,

Dan

¿Fue útil?

Solución

Al contrario de lo que otros han dicho, es posible obtener en tiempo de ejecución para la información de tipo UDT en VB6 (aunque no es una característica integrada de la lengua). de Microsoft Información biblioteca de tipos de objetos Biblioteca (tlbinf32.dll) le permite inspeccionar mediante programación COM información de tipo en tiempo de ejecución. Usted ya debe tener este componente si ha instalado Visual Studio: para añadirlo a un proyecto de VB6 existente, vaya a Proyecto-> Referencias y comprobar la entrada con la etiqueta "biblioteca de tipos de información." Tenga en cuenta que tendrá que distribuir y registrar tlbinf32.dll en el programa de configuración de la aplicación.

Puede inspeccionar casos UDT utilizando el componente de información biblioteca de tipos en tiempo de ejecución, siempre que de sus UDT se declaran Public y se definen dentro de una clase Public. Esto es necesario a fin de que VB6 generar información de tipo compatible con COM para su UDT (que luego se pueden enumerar con varias clases en el componente de información biblioteca de tipos). La forma más fácil de cumplir con este requisito sería la de poner todos los de la UDT en una clase UserTypes pública que se compila en un archivo DLL de ActiveX o EXE.

resumen de un ejemplo de trabajo

Este ejemplo contiene tres partes:

  • Parte 1 : Creación de un proyecto DLL ActiveX que contendrá todas las declaraciones UDT público
  • Parte 2 : Creación de un ejemplo de método PrintUDT para demostrar cómo se pueden enumerar los campos de una instancia de UDT
  • Parte 3 . Creación de una clase de iterador personalizado que le permite iterar fácilmente a través de los campos de cualquier UDT pública y obtener los nombres y valores de campo

El ejemplo de trabajo

Parte 1: El DLL ActiveX

Como ya he mencionado, que necesita para hacer de su UDT público y accesible con el fin de enumerar utilizando el componente de biblioteca de tipos de información. La única manera de lograr esto es poner de UDT en una clase pública dentro de un proyecto ActiveX DLL o EXE. Otros proyectos en su aplicación que necesitan acceder a sus de UDT después hacer referencia a este nuevo componente.

Para continuar con este ejemplo, empezar por crear un nuevo proyecto DLL de ActiveX y el nombre de UDTLibrary.

A continuación, cambiar el nombre del módulo de clase Class1 (esto se agrega de forma predeterminada por el IDE) para UserTypes y añadir dos tipos definidos por el usuario a la clase, Person y Animal:

' UserTypes.cls '

Option Explicit

Public Type Person
    FirstName As String
    LastName As String
    BirthDate As Date
End Type

Public Type Animal
    Genus As String
    Species As String
    NumberOfLegs As Long
End Type

Listado 1: actos UserTypes.cls como un contenedor para de nuestra UDT

A continuación, cambiar el Instancing propiedades de la clase UserTypes a "2-PublicNotCreatable". No hay ninguna razón para que cualquiera pueda crear una instancia de la clase UserTypes directamente, porque está actuando simplemente como un contenedor para nuestros pública de UDT.

Por último, asegúrese de que el Project Startup Object (bajo Proyecto-> Propiedades ) se sitúa en TO "(ninguno)" y compile el proyecto. Ahora debe tener un nuevo archivo llamado UDTLibrary.dll.

Parte 2: Enumerar UDT Tipo de información

Ahora es el momento para demostrar cómo podemos utilizar la biblioteca de tipos biblioteca de objetos para poner en práctica un método PrintUDT.

En primer lugar, empezar por crear un nuevo proyecto EXE estándar y lo llaman lo que quiera. Añadir una referencia a la UDTLibrary.dll archivo que se creó en la Parte 1. Dado que sólo quiero demostrar cómo funciona esto, vamos a utilizar la ventana Inmediato para probar el código que vamos a escribir.

Crear un nuevo módulo, el nombre UDTUtils y agregue el código siguiente a la misma:

'UDTUtils.bas'
Option Explicit    

Public Sub PrintUDT(ByVal someUDT As Variant)

    ' Make sure we have a UDT and not something else... '
    If VarType(someUDT) <> vbUserDefinedType Then
        Err.Raise 5, , "Parameter passed to PrintUDT is not an instance of a user-defined type."
    End If

    ' Get the type information for the UDT '
    ' (in COM parlance, a VB6 UDT is also known as VT_RECORD, Record, or struct...) '

    Dim ri As RecordInfo
    Set ri = TLI.TypeInfoFromRecordVariant(someUDT)

    'If something went wrong, ri will be Nothing'

    If ri Is Nothing Then
        Err.Raise 5, , "Error retrieving RecordInfo for type '" & TypeName(someUDT) & "'"
    Else

        ' Iterate through each field (member) of the UDT '
        ' and print the out the field name and value     '

        Dim member As MemberInfo
        For Each member In ri.Members

            'TLI.RecordField allows us to get/set UDT fields:                 '
            '                                                                 '
            ' * to get a fied: myVar = TLI.RecordField(someUDT, fieldName)    '
            ' * to set a field TLI.RecordField(someUDT, fieldName) = newValue ' 
            '                                                                 '
            Dim memberVal As Variant
            memberVal = TLI.RecordField(someUDT, member.Name)

            Debug.Print member.Name & " : " & memberVal

        Next

    End If

End Sub

Public Sub TestPrintUDT()

    'Create a person instance and print it out...'

    Dim p As Person

    p.FirstName = "John"
    p.LastName = "Doe"
    p.BirthDate = #1/1/1950#

    PrintUDT p

    'Create an animal instance and print it out...'

    Dim a As Animal

    a.Genus = "Canus"
    a.Species = "Familiaris"
    a.NumberOfLegs = 4

    PrintUDT a

End Sub

Listado 2: un ejemplo de método PrintUDT y un método de ensayo sencillo

Parte 3: Por lo que es orientado a objetos

Los ejemplos anteriores proporcionan una demostración "rápida y sucia" de cómo utilizar el TypeLib Información biblioteca de objetos para enumerar los campos de un UDT. En un escenario del mundo real, probablemente crear una clase UDTMemberIterator que le permitiría a iterar más fácilmente a través de los campos de la UDT, junto con una función de utilidad en un módulo que crea una UDTMemberIterator para una instancia determinada UDT. Esto le permitiría a hacer algo como lo siguiente en su código, que es mucho más cerca de la pseudo-código que envió en su pregunta:

Dim member As UDTMember 'UDTMember wraps a TLI.MemberInfo instance'

For Each member In UDTMemberIteratorFor(someUDT)
   Debug.Print member.Name & " : " & member.Value
Next

En realidad no es demasiado difícil de hacer esto, y podemos reutilizar la mayor parte del código de la rutina PrintUDT creado en la Parte 2.

En primer lugar, crear un nuevo proyecto ActiveX y el nombre de UDTTypeInformation o algo similar.

A continuación, asegúrese de que el objeto inicial para el nuevo proyecto se ajusta a "(ninguno)".

Lo primero que hay que hacer es crear una clase contenedora simple que ocultar los detalles de la clase TLI.MemberInfo de código de llamada y hacer que sea fácil obtener el nombre del campo de un UDT y valor. Llamé a este UDTMember clase. El Instancing propiedad de esta clase debe ser PublicNotCreatable .

'UDTMember.cls'
Option Explicit

Private m_value As Variant
Private m_name As String

Public Property Get Value() As Variant
    Value = m_value
End Property

'Declared Friend because calling code should not be able to modify the value'
Friend Property Let Value(rhs As Variant)
    m_value = rhs
End Property

Public Property Get Name() As String
    Name = m_name
End Property

'Declared Friend because calling code should not be able to modify the value'
Friend Property Let Name(ByVal rhs As String)
    m_name = rhs
End Property

Listado 3: La clase UDTMember envoltorio

Ahora tenemos que crear una clase de iterador, UDTMemberIterator, que nos permitirá utilizar la sintaxis For Each...In de VB para recorrer los campos de una instancia de UDT. La propiedad Instancing para esta clase debe establecerse en PublicNotCreatable (vamos a definir un método de utilidad más adelante que va a crear instancias en nombre de código de llamada).

EDIT:. (02/15/09) He limpiado el código un poco más

'UDTMemberIterator.cls'

Option Explicit

Private m_members As Collection ' Collection of UDTMember objects '


' Meant to be called only by Utils.UDTMemberIteratorFor '
'                                                       '
' Sets up the iterator by reading the type info for     '
' the passed-in UDT instance and wrapping the fields in '
' UDTMember objects                                     '

Friend Sub Initialize(ByVal someUDT As Variant)

    Set m_members = GetWrappedMembersForUDT(someUDT)

End Sub

Public Function Count() As Long

    Count = m_members.Count

End Function

' This is the default method for this class [See Tools->Procedure Attributes]   '
'                                                                               '
Public Function Item(Index As Variant) As UDTMember

    Set Item = GetWrappedUDTMember(m_members.Item(Index))

End Function

' This function returns the enumerator for this                                     '
' collection in order to support For...Each syntax.                                 '
' Its procedure ID is (-4) and marked "Hidden" [See Tools->Procedure Attributes]    '
'                                                                                   '
Public Function NewEnum() As stdole.IUnknown

    Set NewEnum = m_members.[_NewEnum]

End Function

' Returns a collection of UDTMember objects, where each element                 '
' holds the name and current value of one field from the passed-in UDT          '
'                                                                               '
Private Function GetWrappedMembersForUDT(ByVal someUDT As Variant) As Collection

    Dim collWrappedMembers As New Collection
    Dim ri As RecordInfo
    Dim member As MemberInfo
    Dim memberVal As Variant
    Dim wrappedMember As UDTMember

    ' Try to get type information for the UDT... '

    If VarType(someUDT) <> vbUserDefinedType Then
        Fail "Parameter passed to GetWrappedMembersForUDT is not an instance of a user-defined type."
    End If

    Set ri = tli.TypeInfoFromRecordVariant(someUDT)

    If ri Is Nothing Then
        Fail "Error retrieving RecordInfo for type '" & TypeName(someUDT) & "'"
    End If

    ' Wrap each UDT member in a UDTMember object... '

    For Each member In ri.Members

        Set wrappedMember = CreateWrappedUDTMember(someUDT, member)
        collWrappedMembers.Add wrappedMember, member.Name

    Next

    Set GetWrappedMembersForUDT = collWrappedMembers

End Function

' Creates a UDTMember instance from a UDT instance and a MemberInfo object  '
'                                                                           '
Private Function CreateWrappedUDTMember(ByVal someUDT As Variant, ByVal member As MemberInfo) As UDTMember

    Dim wrappedMember As UDTMember
    Set wrappedMember = New UDTMember

    With wrappedMember
        .Name = member.Name
        .Value = tli.RecordField(someUDT, member.Name)
    End With

    Set CreateWrappedUDTMember = wrappedMember

End Function

' Just a convenience method
'
Private Function Fail(ByVal message As String)

    Err.Raise 5, TypeName(Me), message

End Function

Listado 4:. La clase UDTMemberIterator

Tenga en cuenta que con el fin de hacer esta clase iterable para que For Each se puede utilizar con él, usted tendrá que establecer ciertos atributos del procedimiento sobre los métodos y Item _NewEnum (como se señala en los comentarios de código). Puede cambiar atributos del procedimiento desde el menú Herramientas (Herramientas> Atributos Procedimiento).

Finalmente, necesitamos una función de utilidad (UDTMemberIteratorFor en el primer ejemplo de código en esta sección) que creará un UDTMemberIterator para una instancia de UDT, que podemos entonces iterar con For Each. Crear un nuevo módulo llamado Utils y agregue el código siguiente:

'Utils.bas'

Option Explicit

' Returns a UDTMemberIterator for the given UDT    '
'                                                  '
' Example Usage:                                   '
'                                                  '
' Dim member As UDTMember                          '
'                                                  '        
' For Each member In UDTMemberIteratorFor(someUDT) '
'    Debug.Print member.Name & ":" & member.Value  '
' Next                                             '
Public Function UDTMemberIteratorFor(ByVal udt As Variant) As UDTMemberIterator

    Dim iterator As New UDTMemberIterator
    iterator.Initialize udt

    Set UDTMemberIteratorFor = iterator

End Function

Listado 5:. La función de utilidad UDTMemberIteratorFor

Por último, compilar el proyecto y crear un nuevo proyecto para probarlo.

En el projet prueba, añadir una referencia a la UDTTypeInformation.dll de nueva creación y la UDTLibrary.dll creado en la Parte 1 y probar el código siguiente en un nuevo módulo:

'Module1.bas'

Option Explicit

Public Sub TestUDTMemberIterator()

    Dim member As UDTMember

    Dim p As Person

    p.FirstName = "John"
    p.LastName = "Doe"
    p.BirthDate = #1/1/1950#

    For Each member In UDTMemberIteratorFor(p)
        Debug.Print member.Name & " : " & member.Value
    Next

    Dim a As Animal

    a.Genus = "Canus"
    a.Species = "Canine"
    a.NumberOfLegs = 4

    For Each member In UDTMemberIteratorFor(a)
        Debug.Print member.Name & " : " & member.Value
    Next

End Sub

Listado 6:. Poner a prueba la clase UDTMemberIterator

Otros consejos

@ Dan,

Parece que su tratando de utilizar RTTI de un UDT. No creo que realmente se puede obtener esa información sin necesidad de conocer la UDT antes de tiempo de ejecución. Para empezar Proveedores:

UDT Entendiendo
Debido a no tener esta capacidad de reflexión. Me gustaría crear mi propia RTTI a mis UDT.

Para darle una línea de base. Prueba esto:

Type test
    RTTI as String
    a as Long
    b as Long 
    c as Long
    d as Integer
end type

Puede escribir una utilidad que va a abrir todos los archivos fuente y añadir la RTTI con el nombre del tipo de la UDT. Probablemente sería mejor poner todos los UDT en un archivo común.

El RTTI sería algo como esto:

"Cadena: Largo: Largo: Largo: Entero"

El uso de la memoria de la UDT puede extraer los valores.

Si cambia todos los tipos de clases. Tienes opciones. La gran trampa de cambiar de un tipo a una clase es que usted tiene que utilizar el nuevo keyworld. Cada vez que hay una declaración de una variable de tipo añadir nueva.

A continuación, puede utilizar la palabra clave variante o CallByName. VB6 no tiene anyType de la reflexión, pero se puede hacer listas de campos válidos y poner a prueba para ver si están presentes, por ejemplo

La prueba de clase tiene la siguiente

Public Key As String
Public Data As String

A continuación, puede hacer lo siguiente

Private Sub Command1_Click()
    Dim T As New Test 'This is NOT A MISTAKE read on as to why I did this.
    T.Key = "Key"
    T.Data = "One"
    DoTest T
End Sub

Private Sub DoTest(V As Variant)
    On Error Resume Next
    Print V.Key
    Print V.Data
    Print V.DoesNotExist
    If Err.Number = 438 Then Print "Does Not Exist"
    Print CallByName(V, "Key", VbGet)
    Print CallByName(V, "Data", VbGet)
    Print CallByName(V, "DoesNotExist", VbGet)
    If Err.Number = 438 Then Print "Does Not Exist"
End Sub

Si intenta utilizar un campo que no existe, entonces el error 438 serán levantados. CallByName permite el uso de cadenas para llamar el campo y los métodos de una clase.

Lo que hace VB6 cuando se declara Dim como nuevo es bastante interesante y minimizará en gran medida los errores en esta conversión. Vea este

Dim T as New Test

No se trata exactamente el mismo que

Dim T as Test
Set T = new Test

Por ejemplo esto funcionará

Dim T as New Test
T.Key = "A Key"
Set T = Nothing
T.Key = "A New Key"

Esto dará un error

Dim T as Test
Set T = New Test
T.Key = "A Key"
Set T = Nothing
T.Key = "A New Key"

La razón de esto es que en las primeras banderas ejemplo VB6 T de manera que cada vez que se accede a ella un miembro de comprobar si el T es nada. Si lo es, se crea automáticamente una nueva instancia de la clase de prueba y luego asignar la variable.

En el segundo ejemplo de VB no añade este comportamiento.

En la mayoría de proyectos que rigurosamente asegurarse de que vayamos Dim T como prueba, de T = Nueva prueba. Pero en su caso, ya que desea convertir los tipos en clases con la menor cantidad de efectos secundarios que utilizan Dim T como nueva prueba es el camino a seguir. Esto se debe a que el Dim como nueva causa la variable para imitar la forma en que funciona tipos más de cerca.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top