Auto inspección de VB6 UDT
-
23-08-2019 - |
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
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.