vb6 user-defined-types

Auto Inspección de VB6 UDTs



user-defined-types (3)

Tengo la sensación de que la respuesta a esto va a ser "imposible", pero lo intentaré ... Estoy en una posición poco envidiable de modificar una aplicación VB6 heredada con algunas mejoras. Convertir a un lenguaje más inteligente no es una opción. La aplicación se basa en una gran 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 para COM en alguna parte ... ¿Algún gurú de VB6 por ahí quiere tomar una foto?

Gracias,

Dan


@Dan,

Parece que estás tratando de usar RTTI de un UDT. No creo que realmente pueda obtener esa información sin conocer el UDT antes del tiempo de ejecución. Para empezar, intente:

Comprender los UDT
Por no tener esta capacidad de reflexión. Crearía mi propio RTTI para mis UDT.

Para darte 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 abrirá todos los archivos fuente y agregará el RTTI con el nombre del tipo al UDT. Probablemente sea mejor colocar todos los UDT en un archivo común.

El RTTI sería algo como esto:

"Cadena: largo: largo: largo: entero"

Usando la memoria del UDT puede extraer los valores.


Si cambia todos sus tipos a Clases. Tienes opciones. El gran escollo de cambiar de un tipo a una clase es que tienes que usar el nuevo mundo clave. Cada vez que hay una declaración de una variable de tipo agrega nueva.

Luego puede usar la palabra clave variante o CallByName. VB6 no tiene ningún tipo de reflejo pero puede hacer listas de campos válidos y probar para ver si están presentes, por ejemplo

La prueba de clase tiene lo 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, se generará el error 438. CallByName le permite usar cadenas para llamar al campo y a los métodos de una clase.

Lo que VB6 hace cuando declara Dim como nuevo es bastante interesante y minimizará enormemente los errores en esta conversión. Ves esto

Dim T as New Test

no se trata exactamente lo 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 el primer ejemplo, VB6 marca T de manera que cada vez que se accede a un miembro, verifique si la T no es nada. Si lo es, creará automáticamente una nueva instancia de la Clase de prueba y luego asignará la variable.

En el segundo ejemplo, VB no agrega este comportamiento.

En la mayoría de los proyectos, nos aseguramos rigurosamente de que vayamos a Dim T como Test, Set T = New Test. Pero en su caso, ya que desea convertir Tipos en clases con la menor cantidad de efectos secundarios con Dim T ya que New Test es el camino a seguir. Esto se debe a que Dim como nuevo causa que la variable imite el modo en que los tipos funcionan más de cerca.


Al contrario de lo que otros han dicho, ES POSIBLE obtener información de tipo de tiempo de ejecución para UDT en VB6 (aunque no es una función de lenguaje incorporado). La biblioteca de objetos de información TypeLib de Microsoft (tlbinf32.dll) le permite inspeccionar mediante programación la información del tipo COM en tiempo de ejecución. Ya debe tener este componente si tiene instalado Visual Studio: para agregarlo a un proyecto VB6 existente, vaya a Proyecto-> Referencias y verifique la entrada etiquetada como "Información de TypeLib". Tenga en cuenta que deberá distribuir y registrar tlbinf32.dll en el programa de instalación de su aplicación.

Puede inspeccionar instancias de UDT utilizando el componente de información de TypeLib en tiempo de ejecución, siempre que sus UDT se declaren Public y se definan dentro de una clase Public . Esto es necesario para hacer que VB6 genere información de tipo compatible con COM para sus UDT (que luego se pueden enumerar con varias clases en el componente de información de TypeLib). La forma más fácil de cumplir este requisito sería colocar todos sus UDT en una clase de UserTypes pública que se compilará en una DLL de ActiveX o EXE de ActiveX.

Resumen de un ejemplo de trabajo

Este ejemplo contiene tres partes:

  • Parte 1 : Crear un proyecto DLL de ActiveX que contendrá todas las declaraciones públicas de UDT
  • Parte 2 : Crear un ejemplo del método PrintUDT para demostrar cómo puede enumerar los campos de una instancia de UDT
  • Parte 3 : Crear una clase de iterador personalizada que le permita iterar fácilmente en los campos de cualquier UDT público y obtener nombres y valores de campo.

El ejemplo de trabajo

Parte 1: la DLL de ActiveX

Como ya mencioné, debe hacer que su UDT sea de acceso público para enumerarlos usando el componente de información de TypeLib. La única forma de lograr esto es colocar su UDT en una clase pública dentro de un archivo DLL ActiveX o un proyecto EXE ActiveX. Otros proyectos en su aplicación que necesitan acceder a su UDT harán referencia a este nuevo componente.

Para seguir con este ejemplo, comience por crear un nuevo proyecto DLL ActiveX y UDTLibrary nombre UDTLibrary .

A continuación, cambie el nombre del módulo de clase Class1 (esto se agrega de forma predeterminada por el IDE) a UserTypes y agregue 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: UserTypes.cls actúa como un contenedor para nuestro UDT

A continuación, cambie la propiedad de Instanciación para la clase UserTypes a "2-PublicNotCreatable". No hay ninguna razón para que nadie instanciar la clase UserTypes directamente, ya que simplemente actúa como un contenedor público para nuestros UDT.

Finalmente, asegúrese de que el Project Startup Object del Project Startup Object (en Proyecto-> Propiedades ) esté configurado en "(Ninguno)" y compile el proyecto. Ahora debería tener un nuevo archivo llamado UDTLibrary.dll .

Parte 2: enumeración de la información del tipo de UDT

Ahora es el momento de demostrar cómo podemos usar TypeLib Object Library para implementar un método PrintUDT .

Primero, comienza creando un nuevo proyecto EXE estándar y llámalo como quieras. Agregue una referencia al archivo UDTLibrary.dll que se creó en la Parte 1. Como solo quiero demostrar cómo funciona esto, usaremos la ventana Inmediato para probar el código que vamos a escribir.

Cree un nuevo Módulo, UDTUtils nombre UDTUtils y agregue el siguiente código:

''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 del método PrintUDT y un método de prueba simple

Parte 3: Hacerlo orientado a objetos

Los ejemplos anteriores proporcionan una demostración "rápida y sucia" de cómo usar la biblioteca de objetos de información de TypeLib para enumerar los campos de un UDT. En un escenario del mundo real, probablemente crearía una clase UDTMemberIterator que le permitiría iterar más fácilmente a través de los campos de UDT, junto con una función de utilidad en un módulo que crea un UDTMemberIterator para una instancia de UDT determinada. Esto le permitiría hacer algo como lo siguiente en su código, que está mucho más cerca del pseudo-código que publicó 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

De hecho, no es demasiado difícil hacer esto, y podemos volver a utilizar la mayoría del código de la rutina PrintUDT creada en la Parte 2.

Primero, cree un nuevo proyecto ActiveX y UDTTypeInformation nombre UDTTypeInformation o algo similar.

A continuación, asegúrese de que el objeto de inicio para el nuevo proyecto esté configurado en "(ninguno)".

Lo primero que debe hacer es crear una clase contenedora simple que oculte los detalles de la clase TLI.MemberInfo del código de llamada y facilite la obtención del nombre y el valor de un campo UDT. Llamé a esta clase UDTMember . La propiedad Instancing para 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 de contenedor UDTMember

Ahora necesitamos crear una clase de iterador, UDTMemberIterator , que nos permita usar VB''s For Each...In sintaxis para iterar los campos de una instancia de UDT. La propiedad de Instancing para esta clase debe establecerse en PublicNotCreatable (más PublicNotCreatable definiremos un método de utilidad que creará instancias en nombre del código de llamada).

EDITAR: (15/2/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 para hacer esta clase iterable para que For Each se pueda usar con ella, deberá establecer ciertos Atributos del Procedimiento en los métodos Item y _NewEnum (como se indica en los comentarios del código). Puede cambiar los Atributos del procedimiento desde el menú Herramientas (Herramientas-> Atributos del 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 luego podemos iterar con For Each . Cree un nuevo módulo llamado Utils y agregue el siguiente código:

''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 .

Finalmente, compile el proyecto y cree un nuevo proyecto para probarlo.

En su proyecto de prueba, agregue una referencia al recién creado UDTTypeInformation.dll y el UDTLibrary.dll creado en la Parte 1 y pruebe el siguiente código 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: Probando la clase UDTMemberIterator .