Question

Je sens la réponse à cela va être « pas possible », mais je vais donner un coup de feu ... Je suis dans la position peu enviable de modifier une application VB6 héritée avec quelques améliorations. La conversion en une langue plus intelligente est pas une option. L'application repose sur une grande collection de types définis par l'utilisateur pour déplacer des données. Je voudrais définir une fonction commune qui peut prendre une référence à l'un de ces types et extraire les données contenues.
Dans le code pseudo, voici ce que je cherche:

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

Il semble que cette information doit être disponible pour COM quelque part ... Tous les gourous VB6 soins là-bas pour prendre une photo?

Merci,

Dan

Était-ce utile?

La solution

Contrairement à ce que d'autres ont dit, il est possible d'obtenir la gestion du temps des informations de type pour son UDT dans VB6 (bien qu'il ne soit pas une caractéristique de langage intégré). TypeLib Informations objet bibliothèque (tlbinf32.dll) vous permet d'inspecter programme COM informations de type à l'exécution. Vous devriez déjà avoir cette composante si vous avez installé Visual Studio: pour l'ajouter à un projet VB6 existant, allez à Projet-> Références et vérifiez l'entrée intitulée « TypeLib l'information. » Notez que vous devrez distribuer et inscrire tlbinf32.dll dans le programme d'installation de votre application.

Vous pouvez consulter les instances UDT utilisant le composant d'information TypeLib au moment de l'exécution, aussi longtemps que vos UDT sont déclarés Public et sont définies dans une classe Public. Ceci est nécessaire afin de rendre VB6 générer des informations de type COM compatible pour votre UDT (qui peut ensuite être dénombrées à différentes classes dans le composant d'information TypeLib). La meilleure façon de répondre à cette exigence serait de mettre tous vos UDT dans une classe publique UserTypes qui sera compilé dans un DLL ActiveX ou EXE ActiveX.

Résumé d'un exemple de travail

Cet exemple contient trois parties:

  • Partie 1 : Création d'un projet DLL ActiveX qui contiendra toutes les déclarations de UDT publique
  • Partie 2 : Création d'un exemple de méthode PrintUDT pour montrer comment vous pouvez énumérer les champs d'une instance UDT
  • Partie 3 :. Création d'une classe itérateur personnalisée qui vous permet d'itérer facilement à travers les champs de tout UDT public et obtenir les noms des champs et des valeurs

L'exemple de travail

Partie 1: La DLL ActiveX

Comme je l'ai déjà mentionné, vous devez rendre public accessible de votre UDT afin de les énumérer en utilisant le composant d'information TypeLib. La seule façon d'y arriver est de mettre vos UDT dans une classe publique dans un projet DLL ActiveX ou EXE ActiveX. D'autres projets dans votre application qui ont besoin d'accéder à vos UDT renverront alors ce nouveau composant.

Pour suivre cet exemple, commencez par créer un nouveau projet DLL ActiveX et nommez-le UDTLibrary.

Ensuite, renommer le module de classe Class1 (ce qui est ajouté par défaut par l'EDI) pour UserTypes et ajouter deux types définis par l'utilisateur à la classe, Person et 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

Liste 1: agit UserTypes.cls comme un conteneur pour notre UDT de

Ensuite, changer le instanciation propriété pour la classe UserTypes à "2-PublicNotCreatable". Il n'y a aucune raison pour quiconque de instancier la classe UserTypes directement, parce qu'il est tout simplement agir comme contenant du public pour nos années UDT.

Enfin, assurez-vous que le Project Startup Object (sous Projet-> Propriétés ) est réglé sur "(Aucun)" et compiler le projet. Vous devriez maintenant avoir un nouveau fichier appelé UDTLibrary.dll.

Partie 2: UDT Énumération Type d'information

Maintenant, il est temps de montrer comment nous pouvons utiliser TypeLib bibliothèque d'objets pour mettre en œuvre une méthode PrintUDT.

Tout d'abord, commencez par créer un nouveau projet EXE standard et appeler ce que vous voulez. Ajoutez une référence au fichier UDTLibrary.dll qui a été créé dans la partie 1. Comme je veux juste montrer comment cela fonctionne, nous utiliserons la fenêtre immédiate pour tester le code que nous allons écrire.

Créer un nouveau module, nommez-UDTUtils et ajoutez le code suivant à elle:

'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

Liste 2: Un exemple de méthode PrintUDT et une méthode de test simple

Partie 3: Rendre Object-Oriented

Les exemples ci-dessus fournissent une démonstration « rapide et sale » de la façon d'utiliser le TypeLib information bibliothèque d'objets d'énumérer les champs d'un UDT. Dans un scénario réel, je serais probablement créer une classe UDTMemberIterator qui vous permettra d'itérer plus facilement à travers les champs de UDT, ainsi que d'une fonction d'utilité dans un module qui crée un UDTMemberIterator pour une instance donnée UDT. Cela vous permettra de faire quelque chose comme ce qui suit dans votre code, ce qui est beaucoup plus proche de la pseudo-code affiché dans votre question:

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

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

Il est en fait pas trop difficile de le faire, et nous pouvons réutiliser la plupart du code de la routine PrintUDT créé dans la partie 2.

Tout d'abord, créez un nouveau projet ActiveX et nommez-le UDTTypeInformation ou quelque chose de similaire.

Ensuite, assurez-vous que l'objet de démarrage pour le nouveau projet est réglé sur « (Aucun) ».

La première chose à faire est de créer une classe simple wrapper qui permet de masquer les détails de la classe TLI.MemberInfo d'appeler le code et le rendre facile d'obtenir le nom et la valeur de champ d'un UDT. J'ai appelé cette UDTMember de classe. instanciation propriété pour cette classe doit être 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

Liste 3: La classe wrapper UDTMember

Maintenant, nous devons créer une classe iterator, UDTMemberIterator, qui nous permettra d'utiliser la syntaxe de For Each...In de VB pour itérer les champs d'une instance UDT. La propriété Instancing pour cette classe doit être réglé sur PublicNotCreatable (nous allons définir une méthode d'utilité plus tard qui va créer des instances au nom de code d'appel).

EDIT:. (15/02/09) J'ai nettoyé le code un peu plus

'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

Liste 4:. La classe UDTMemberIterator

Notez que pour rendre cette classe itérables afin que For Each peut être utilisé avec, vous devrez définir certains attributs de procédure sur les méthodes de Item et _NewEnum (comme il est indiqué dans les commentaires de code). Vous pouvez modifier la procédure Attributs dans le menu Outils (Outils-> Procédure Attributs).

Enfin, nous avons besoin d'une fonction d'utilité (UDTMemberIteratorFor dans le premier exemple de code dans cette section) qui va créer un UDTMemberIterator pour une instance UDT, que nous pouvons itérer avec For Each. Créer un nouveau module appelé Utils et ajoutez le code suivant:

'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

Listing 5:. La fonction utilitaire UDTMemberIteratorFor

Enfin, compiler le projet et créer un nouveau projet pour le tester.

Dans votre projet de test, ajoutez une référence au UDTTypeInformation.dll nouvellement créé et le UDTLibrary.dll créé dans la partie 1 et essayez le code suivant dans un nouveau module:

'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

Liste 6:. Tester la classe UDTMemberIterator

Autres conseils

@ Dan,

On dirait que vous essayez d'utiliser RTTI d'un UDT. Je ne pense pas que vous pouvez vraiment obtenir cette information sans savoir au sujet de l'UDT avant l'exécution. Pour vous aider à démarrer essayer:

Comprendre UDT
En raison de ne pas avoir cette capacité de réflexion. Je voudrais créer mon propre RTTI à mes UDT.

Pour vous donner une ligne de base. Essayez ceci:

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

Vous pouvez écrire un utilitaire qui ouvrira tous les fichiers source et ajoutez RTTI avec le nom du type à l'UDT. Probablement serait préférable de mettre tous les UDT dans un fichier commun.

Le RTTI serait quelque chose comme ceci:

"String: Long: Long: Long: Entier"

Utilisation de la mémoire de l'UDT, vous pouvez extraire les valeurs.

Si vous changez tous vos types de classes. Vous avez des options. Le grand écueil de passer d'un type à une classe est que vous devez utiliser la nouvelle Keyworld. Chaque fois qu'il ya une déclaration d'un add variable de type nouveau.

Ensuite, vous pouvez utiliser le mot-clé de variante ou CallByName. VB6 n'a pas anyType de réflexion, mais vous pouvez faire des listes de champs valides et tester pour voir si elles sont présentes par exemple

Le test de classe a les éléments suivants

Public Key As String
Public Data As String

Vous pouvez alors effectuer les opérations suivantes

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 vous essayez d'utiliser un champ qui ne en erreur existe pas 438 ressusciteront. CallByName vous permet d'utiliser des chaînes pour appeler le champ et les méthodes d'une classe.

Qu'est-ce que VB6 fait quand vous déclarez Dim comme nouveau est tout à fait intéressant et réduira considérablement les bugs dans cette conversion. Vous voyez ce

Dim T as New Test

ne sont pas traités exactement le même que

Dim T as Test
Set T = new Test

Par exemple, cela fonctionne

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

Cela vous donnera une erreur

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

La raison est que, dans les premiers drapeaux VB6 exemple T de telle sorte que chaque fois qu'un membre est accédaient vérifier si le T est rien. Si elle est elle crée automatiquement une nouvelle instance de la classe de test, puis assigner la variable.

Dans le second exemple VB n'ajoute pas ce comportement.

Dans la plupart des projets que nous faisons avec rigueur que nous allons Dim T comme test, Set T = Nouveau test. Mais dans votre cas puisque vous voulez convertir les types en classes avec le moins d'effets secondaires en utilisant Dim T comme un nouveau test est le chemin à parcourir. En effet, le Dim comme une nouvelle cause la variable d'imiter les types de fonctionnement de plus près.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top