Selbstinspektion von VB6 UDTs
-
23-08-2019 - |
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
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.
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
behandeltDim 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.