Frage

Ich habe das Gefühl, die Antwort auf diese Frage wird „nicht möglich“ sein, aber ich werde ihm einen Schuss geben ... Ich bin in der wenig beneidenswerten Lage ein Vermächtnis VB6 app mit einigen Verbesserungen zu modifizieren. Die Umstellung auf eine intelligentere Sprache ist keine Option. Die App stützt sich auf eine große Sammlung von benutzerdefinierten Typen Daten zu bewegen. Ich möchte eine gemeinsame Funktion definieren, die einen Verweis auf eine dieser Arten nehmen und extrahieren die Daten enthalten sind.
In Pseudo-Code, hier ist was ich suche:

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

Es scheint, wie diese Informationen verfügbar sein muss irgendwo auf COM ... Irgendwelche VB6-Gurus da draußen kümmern einen Schuss nehmen?

Danke,

Dan

War es hilfreich?

Lösung

Im Gegensatz zu dem, was andere gesagt haben, ist es möglich, Typinformationen für UDT in VB6 zu bekommen Laufzeit (obwohl es nicht ein eingebauter Sprache-Funktion ist). Microsofts TypeLib Informationen Objektbibliothek (Tlbinf32.dll) können Sie programmatisch inspizieren COM-Typinformationen zur Laufzeit. Sie sollten bereits diese Komponente, wenn Sie Visual Studio installiert haben: es zu einem bestehenden VB6 Projekt hinzuzufügen, gehen Sie auf Project-> Referenzen und prüfen Sie den Eintrag mit der Bezeichnung „TypeLib-Informationen.“ Beachten Sie, dass Sie haben werden zu verteilen und registrieren Tlbinf32.dll in Ihrer Anwendung der Setup-Programm.

Sie können UDT-Instanzen mit der TypeLib Informationskomponente zur Laufzeit, solange Ihr UDT deklariert Public und definiert innerhalb einer Public Klasse untersuchen. Dies ist notwendig, um VB6 erzeugen COM-kompatible Typinformationen für Ihren UDT (die dann mit verschiedenen Klassen in der TypeLib Informationskomponente aufgezählt werden können) zu machen. Der einfachste Weg, diese Anforderung zu erfüllen wäre all Ihren UDT in eine öffentliche UserTypes Klasse zu setzen, die in einen ActiveX-DLL oder ActiveX-EXE kompiliert werden.

Zusammenfassung eines Arbeitsbeispiels

In diesem Beispiel enthält drei Teile:

  • Teil 1 : Erstellen eines ActiveX-DLL-Projekt, das alle öffentlichen UDT Erklärungen enthalten
  • Teil 2 : ein Beispiel PrintUDT Methode erstellen zu zeigen, wie Sie die Felder einer UDT Instanz aufzählen können
  • Teil 3 :. Eine benutzerdefinierte Iterator-Klasse erstellen, die Sie leicht durchlaufen die Felder einer öffentlichen UDT und bekommen Feldnamen und Werte
  • erlaubt

Das Arbeitsbeispiel

Teil 1: Der ActiveX-DLL

Wie ich bereits erwähnt, müssen Sie Ihre UDT öffentlich-zugänglich, um sie aufzuzählen, um die TypeLib Informationen Komponente. Der einzige Weg, dies zu tun ist Ihren UDT in eine öffentliche Klasse in einem ActiveX-DLL oder ActiveX-EXE-Projekt zu setzen. Weitere Projekte in Ihrer Anwendung, die Ihre UDT zugreifen müssen dann diese neue Komponente verweisen.

Mit diesem Beispiel folgen zusammen, beginnen Sie ein neues ActiveX-DLL-Projekt erstellen und benennen Sie es UDTLibrary.

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

Listing 1: UserTypes.cls fungiert als Container für unsere UDT

Als nächstes ändern Sie die Instancing Eigenschaft für die UserTypes Klasse "2-PublicNotCreatable". Es gibt keinen Grund für jedermann die UserTypes Klasse direkt zu instanziiert, weil es einfach ist als öffentlicher Behälter für unsere UDT handeln.

Schließlich, stellen Sie sicher, dass der Project Startup Object (unter Project-> Eigenschaften ) auf auf "(None)" und das Projekt kompilieren. Sie sollten nun eine neue Datei namens UDTLibrary.dll haben.

Teil 2: Aufzählen UDT Type Information

Jetzt ist es Zeit zu zeigen, wie wir Bibliothek verwenden können TypeLib Objekt ein PrintUDT Verfahren zu implementieren.

Starten Sie zunächst durch ein neues Standard-EXE-Projekt erstellen und nennen Sie es, was Sie wollen. Fügen Sie einen Verweis auf die Datei UDTLibrary.dll, die in Teil erstellt wurden 1. Da ich will nur zeigen, wie das funktioniert, werden wir die Direkt-Fenster verwenden, um den Code zu testen wir schreiben.

Erstellen

ein neues Modul, 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: Making it Object-Oriented

Die obigen Beispiele stellen eine „quick and dirty“ Demonstration, wie die Ty verwendenpeLib Information Library Objekt die Felder eines UDT aufzuzählen. In einem realen Szenario, würde ich wahrscheinlich eine UDTMemberIterator Klasse erstellen, die Sie mehr erlauben würden, auf einfache Weise durch die Felder von UDT laufen, zusammen mit einer Nutzenfunktion in einem Modul, das eine UDTMemberIterator für eine gegebene UDT-Instanz erstellt. Dies würde es ermöglichen Sie so etwas wie die folgenden in Ihrem Code zu tun, der mit dem Pseudo-Code viel näher ist, dass Sie in Ihrer Frage gestellt:

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 wieder verwenden die meisten des Code aus der PrintUDT Routine in Teil 2 erstellt

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

Als nächstes stellen Sie sicher, dass das Startobjekt für das neue Projekt ist auf „(None)“.

Das erste, was zu tun ist, eine einfache Wrapper-Klasse zu erstellen, die die Details der TLI.MemberInfo Klasse von Telefonvorwahl verstecken und machen es einfach, ein UDT Feld Namen und Wert zu erhalten. Ich nannte diese Klasse UDTMember. Die Instancing Eigenschaft für diese Klasse sollte 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

Listing 3: Die UDTMember Wrapper-Klasse

Jetzt brauchen wir eine Iterator Klasse, UDTMemberIterator zu schaffen, die wir VB For Each...In Syntax verwenden kann, um die Felder einer UDT-Instanz durchlaufen. Die Instancing Eigenschaft für diese Klasse sollte PublicNotCreatable eingestellt werden (wir werden später ein Dienstprogramm Methode definieren, die Instanzen im Namen des Anrufers Code erstellen wird).

EDIT:. (2/15/09) ich den Code ein bisschen mehr aufgeräumt haben

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

Beachten Sie, dass, um diese Klasse iterable zu machen, so dass For Each mit ihr verwendet werden, werden Sie bestimmte Prozedurattribute auf den Item und _NewEnum Methoden setzen müssen (wie in den Code-Kommentaren vermerkt). Sie können das Verfahren Menü Attribute aus dem Tool ändern (Tools-> Prozedurattribute).

Schließlich brauchen wir eine Nutzenfunktion (UDTMemberIteratorFor im ersten Codebeispiel in diesem Abschnitt), die eine UDTMemberIterator für eine UDT Instanz schaffen, die wir mit For Each dann laufen kann. Erstellen Sie ein neues Modul namens Utils und fügen Sie den folgenden Code ein:

'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: Die UDTMemberIteratorFor Nutzenfunktion

Schließlich das Projekt kompiliert und ein neues Projekt erstellen um es zu testen.

In Ihrem Test projet, fügen Sie einen Verweis auf die neu erstellte UDTTypeInformation.dll und die UDTLibrary.dll in Teil 1 erstellt haben und versuchen, den folgenden Code in ein neues Modul:

'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: Testen der UDTMemberIterator Klasse aus

Andere Tipps

@ Dan,

Es sieht aus wie Ihre RTTI eines UDT zu nutzen versuchen. Ich glaube nicht, dass Sie wirklich, dass die Informationen, ohne zu wissen über die UDT vor der Laufzeit erhalten können. Um Ihnen den Einstieg versuchen:

Legendes UDTs
Wegen der nicht diese Reflexionsfähigkeit. Ich würde meine eigene RTTI meiner UDT erstellen.

Um Ihnen eine Basis. Versuchen Sie folgendes:

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, die jede Quelldatei öffnen und die RTTI mit dem Namen des Typs UDT hinzuzufügen. Wahrscheinlich wäre besser, alle in einer gemeinsamen Datei, die die UDT zu setzen.

Die RTTI wäre so etwas wie dies:

"String: Lang: Lang: Lang: Integer"

, um den Speicher des UDT Verwenden Sie die Werte extrahieren kann.

Wenn Sie alle Typen für die Klassen ändern. Sie haben Optionen. Die große Gefahr des zu einer Klasse von einem Typ zu ändern ist, dass Sie die neue Keyworld verwenden. Jedes Mal, wenn eine Deklaration einer Variable vom Typ hinzufügen neu.

Dann können Sie die Variante Stichwort oder CallByName verwenden. VB6 nicht anytype die Reflexion, aber Sie können Listen von gültigen Feldern machen und testen, um zu sehen, ob sie zum Beispiel vorhanden ist

Der Klasse-Test hat die folgende

Public Key As String
Public Data As String

Sie können dann gehen Sie wie folgt

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, die nicht existiert, Fehler 438 angehoben wird. CallByName können Sie Zeichenfolgen verwenden, um das Feld und Methoden einer Klasse zu nennen.

Was VB6 tut, wenn Sie Dim deklarieren als neu ist recht interessant und wird sehr Bugs in dieser Umwandlung minimieren. Sie sehen dies

Dim T as New Test

ist nicht genau das gleiche wie

behandelt
Dim T as Test
Set T = new Test

Zum Beispiel dies funktionieren wird,

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

Dies wird einen Fehler geben

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 in den ersten Beispiel VB6 Flaggen T, so dass jederzeit ein Mitglied zugegriffen wird es prüfen, ob die T nichts. Wenn es wird automatisch eine neue Instanz der Testklasse erstellen und dann die Variablen zuweisen.

Im zweiten Beispiel VB dieses Verhalten nicht hinzuzufügen.

In den meisten Projekt, das wir konsequent darauf achten, gehen wir Dim T als Test-Set T = Neuer Test. Aber in Ihrem Fall wollen, da Sie Arten in Klassen mit der geringsten Menge von Nebenwirkungen mit Dim T als neuer Test ist der Weg konvertieren zu gehen. Dies liegt daran, die Dim als neu die Variable verursachen eng an die Art und Weise Arten zu imitieren funktioniert mehr.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top