17 Stimmen

Selbstinspektion von VB6 UDTs

Ich habe das Gefühl, die Antwort auf diese Frage wird "nicht möglich" sein, aber ich werde es versuchen... Ich bin in der wenig beneidenswerten Lage, eine alte VB6-Anwendung mit einigen Erweiterungen zu modifizieren. Die Umstellung auf eine intelligentere Sprache ist keine Option. Die Anwendung stützt sich auf eine große Sammlung von benutzerdefinierten Typen, um Daten zu bewegen. Ich möchte eine gemeinsame Funktion definieren, die einen Verweis auf einen dieser Typen aufnehmen und die enthaltenen Daten extrahieren kann.
In Pseudocode ausgedrückt, suche ich nach folgendem Muster:

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

Es scheint, als ob diese Informationen für COM irgendwo verfügbar sein müssen... Jeder VB6-Gurus da draußen Pflege zu nehmen einen Schuss?

Danke,

Dan

0 Stimmen

Ist dieser benutzerdefinierte Typ eine Klasse/Typ?

0 Stimmen

Ups, Entschuldigung. Ich habe erst nach dem Posten bemerkt, dass der Name der Funktion PrintUDT ist. Es sollte also ein Typ sein (eine Art Struktur). Richtig?

0 Stimmen

40voto

Mike Spross Punkte 7801

Im Gegensatz zu dem, was andere gesagt haben, ist es möglich, Laufzeit-Typ-Informationen für UDTs in VB6 zu erhalten (obwohl es keine eingebaute Sprachfunktion ist). Microsofts TypeLib Informationsobjekt-Bibliothek (tlbinf32.dll) ermöglicht es Ihnen, COM-Typinformationen zur Laufzeit programmatisch zu überprüfen. Sie sollten diese Komponente bereits haben, wenn Sie Visual Studio installiert haben: Um sie zu einem bestehenden VB6-Projekt hinzuzufügen, gehen Sie zu Projekt->Referenzen und markieren Sie den Eintrag "TypeLib Information". Beachten Sie, dass Sie die Datei tlbinf32.dll im Setup-Programm Ihrer Anwendung verteilen und registrieren müssen.

Sie können UDT-Instanzen mit der TypeLib-Informationskomponente zur Laufzeit untersuchen, solange Ihre UDTs deklariert sind Public und werden innerhalb einer Public Klasse. Dies ist notwendig, damit VB6 COM-kompatible Typinformationen für Ihre UDTs erzeugen kann (die dann mit verschiedenen Klassen in der TypeLib-Informationskomponente aufgezählt werden können). Der einfachste Weg, diese Anforderung zu erfüllen, wäre, alle Ihre UDT's in eine öffentliche UserTypes Klasse, die zu einer ActiveX-DLL oder ActiveX-EXE kompiliert wird.

Zusammenfassung eines Arbeitsbeispiels

Dieses Beispiel besteht aus drei Teilen:

  • Teil 1 : Erstellen eines ActiveX DLL-Projekts, das alle öffentlichen UDT-Deklarationen enthält
  • Teil 2 : Ein Beispiel erstellen PrintUDT Methode, um zu demonstrieren, wie Sie die Felder einer UDT-Instanz aufzählen können
  • Teil 3 : Erstellen einer benutzerdefinierten Iterator-Klasse, mit der Sie einfach durch die Felder eines beliebigen öffentlichen UDT iterieren und Feldnamen und Werte abrufen können.

Das Arbeitsbeispiel

Teil 1: Die ActiveX-DLL

Wie bereits erwähnt, müssen Sie Ihre UDTs öffentlich zugänglich machen, um sie mit der TypeLib-Informationskomponente aufzählen zu können. Die einzige Möglichkeit, dies zu erreichen, besteht darin, Ihre UDTs in eine öffentliche Klasse innerhalb einer ActiveX-DLL oder eines ActiveX-EXE-Projekts aufzunehmen. Andere Projekte in Ihrer Anwendung, die auf Ihre UDTs zugreifen müssen, verweisen dann auf diese neue Komponente.

Um diesem Beispiel zu folgen, erstellen Sie zunächst ein neues ActiveX-DLL-Projekt und nennen Sie es UDTLibrary .

Als nächstes benennen Sie die Class1 Klassenmodul (dies wird von der IDE standardmäßig hinzugefügt) zu UserTypes und fügen Sie der Klasse zwei benutzerdefinierte Typen hinzu, 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

Auflistung 1: UserTypes.cls dient als Container für unsere UDTs

Als nächstes ändern Sie die Instanzierung Eigenschaft für die UserTypes Klasse auf "2-PublicNotCreatable". Es gibt für niemanden einen Grund, die Klasse zu instanziieren UserTypes Klasse direkt, weil sie einfach als öffentlicher Container für unsere UDTs fungiert.

Vergewissern Sie sich schließlich, dass die Project Startup Object (unter Projekt->Eigenschaften ) auf "(None)" gesetzt ist und kompilieren Sie das Projekt. Sie sollten nun eine neue Datei namens UDTLibrary.dll .

Teil 2: Aufzählung von UDT-Typ-Informationen

Jetzt ist es an der Zeit zu demonstrieren, wie wir die TypeLib Object Library verwenden können, um eine PrintUDT método.

Beginnen Sie mit der Erstellung eines neuen Standard-EXE-Projekts und nennen Sie es nach Belieben. Fügen Sie einen Verweis auf die Datei UDTLibrary.dll die in Teil 1 erstellt wurde. Da ich nur demonstrieren möchte, wie das funktioniert, werden wir das Fenster "Sofort" verwenden, um den Code zu testen, den wir schreiben werden.

Erstellen Sie ein neues Modul und nennen Sie es UDTUtils und fügen Sie den folgenden Code hinzu:

'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

Listing 2: Ein Beispiel PrintUDT Verfahren und ein einfaches Testverfahren

Teil 3: Objektorientiert werden

Die obigen Beispiele sind eine "schnelle und schmutzige" Demonstration der Verwendung der TypeLib Information Object Library zur Aufzählung der Felder eines UDT. In einem realen Szenario würde ich wahrscheinlich eine UDTMemberIterator Klasse, die eine einfachere Iteration durch die UDT-Felder ermöglicht, zusammen mit einer Dienstprogrammfunktion in einem Modul, die eine UDTMemberIterator für eine bestimmte UDT-Instanz. Dies würde es Ihnen ermöglichen, in Ihrem Code etwas wie das Folgende zu tun, was dem Pseudocode, den Sie in Ihrer Frage gepostet haben, viel näher kommt:

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

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

Es ist eigentlich nicht allzu schwer, dies zu tun, und wir können die meisten der Code aus dem wiederverwenden PrintUDT Routine, die in Teil 2 erstellt wurde.

Erstellen Sie zunächst ein neues ActiveX-Projekt und nennen Sie es UDTTypeInformation oder etwas Ähnliches.

Vergewissern Sie sich anschließend, dass das Startobjekt für das neue Projekt auf "(None)" gesetzt ist.

Als Erstes müssen wir eine einfache Wrapper-Klasse erstellen, die die Details der TLI.MemberInfo Klasse aus dem aufrufenden Code zu entfernen und es einfach zu machen, den Namen und den Wert eines UDT-Feldes zu erhalten. Ich nannte diese Klasse UDTMember . Die Instanzierung Eigenschaft für diese Klasse sollte sein 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

Auflistung 3: Die UDTMember Wrapper-Klasse

Nun müssen wir eine Iterator-Klasse erstellen, UDTMemberIterator die es uns ermöglichen wird, VB's For Each...In Syntax, um die Felder einer UDT-Instanz zu iterieren. Die Instancing Eigenschaft für diese Klasse sollte auf PublicNotCreatable (wir werden später eine Utility-Methode definieren, die Instanzen im Namen des aufrufenden Codes erzeugt).

EDITAR: (15.2.09) Ich habe den Code noch ein wenig aufgeräumt.

'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

Listing 4: Die UDTMemberIterator classe.

Um diese Klasse iterierbar zu machen, ist zu beachten, dass For Each verwendet werden kann, müssen Sie bestimmte Prozedur-Attribute in der Item y _NewEnum Methoden (wie in den Code-Kommentaren angegeben). Sie können die Prozedur-Attribute über das Menü "Werkzeuge" ändern (Werkzeuge->Prozedur-Attribute).

Schließlich benötigen wir eine Nutzenfunktion ( UDTMemberIteratorFor im allerersten Codebeispiel in diesem Abschnitt), die eine UDTMemberIterator für eine UDT-Instanz, die wir dann iterieren können mit For Each . Erstellen Sie ein neues Modul namens Utils und fügen Sie den folgenden Code hinzu:

'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

Auflistung 5: Die UDTMemberIteratorFor Nutzenfunktion.

Kompilieren Sie schließlich das Projekt und erstellen Sie ein neues Projekt, um es zu testen.

Fügen Sie in Ihrem Testprojekt einen Verweis auf das neu erstellte UDTTypeInformation.dll und die UDTLibrary.dll die Sie in Teil 1 erstellt haben, und probieren Sie den folgenden Code in einem neuen Modul aus:

'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

Listing 6: Ausprobieren der UDTMemberIterator classe.

2voto

RS Conley Punkte 7136

Wenn Sie alle Ihre Typen in Klassen ändern. Sie haben Optionen. Der große Fallstrick beim Wechsel von einem Typ zu einer Klasse ist, dass Sie die neue Schlüsselwelt verwenden müssen. Jedes Mal, wenn eine Typvariable deklariert wird, fügen Sie new.

Dann können Sie das Schlüsselwort variant oder CallByName verwenden. VB6 hat keine Art von Reflexion, aber Sie können Listen von gültigen Feldern erstellen und testen, ob sie vorhanden sind, z.B.

Der Klassentest hat folgenden Inhalt

Public Key As String
Public Data As String

Sie können dann wie folgt vorgehen

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

Wenn Sie versuchen, ein Feld zu verwenden, das nicht existiert, wird der Fehler 438 ausgelöst. Mit CallByName können Sie Strings verwenden, um das Feld und die Methoden einer Klasse aufzurufen.

Was VB6 macht, wenn man Dim als New deklariert, ist recht interessant und wird die Fehler bei dieser Konvertierung stark minimieren. Sie sehen dies

Dim T as New Test

wird nicht genau so behandelt wie

Dim T as Test
Set T = new Test

Das funktioniert zum Beispiel so

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

Dies führt zu einer Fehlermeldung

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

Der Grund dafür ist, dass VB6 im ersten Beispiel T markiert, so dass jedes Mal, wenn auf ein Mitglied zugegriffen wird, geprüft wird, ob T nichts ist. Wenn dies der Fall ist, wird automatisch eine neue Instanz der Testklasse erstellt und die Variable zugewiesen.

Im zweiten Beispiel fügt VB dieses Verhalten nicht hinzu.

In den meisten Projekten stellen wir rigoros sicher, dass wir Dim T as Test, Set T = New Test gehen. Aber in Ihrem Fall, da Sie Typen in Klassen mit der geringsten Menge an Seiteneffekten konvertieren wollen, ist Dim T as New Test der richtige Weg. Dies liegt daran, dass Dim as New dazu führt, dass die Variable die Funktionsweise von Typen besser nachahmt.

2voto

Gutzofter Punkte 2005

@Dan,

Es sieht so aus, als ob Sie versuchen, RTTI eines UDTs zu verwenden. Ich glaube nicht, dass Sie diese Informationen wirklich erhalten können, ohne die UDT vor der Laufzeit zu kennen. Für den Anfang versuchen Sie es:

UDTs verstehen
Weil sie diese Reflexionsfähigkeit nicht haben. Ich würde meine eigene RTTI für meine UDTs erstellen.

Um Ihnen einen Anhaltspunkt zu geben. Versuchen Sie dies:

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

Sie können ein Dienstprogramm schreiben, das jede Quelldatei öffnet und die RTTI mit dem Namen des Typs in den UDT einfügt. Wahrscheinlich wäre es besser, alle UDTs in einer gemeinsamen Datei zu speichern.

Die RTTI würde in etwa so aussehen:

"String:Long:Long:Long:Integer"

Mit Hilfe des Speichers des UDT können Sie die Werte extrahieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X