Self-Inspeksie van VB6 UDTs
-
23-08-2019 - |
Vra
Ek het'n gevoel die antwoord hierop is gaan om te wees "nie moontlik nie", maar ek sal dit gee'n skoot...Ek is in die onbenydenswaardige posisie van die wysiging van'n nalatenskap VB6 app met'n paar verbeterings.Die omskakeling na'n slimmer taal is nie'n opsie nie.Die app maak staat op'n groot versameling van die gebruiker gedefinieerde tipes om data te skuif rond.Ek wil graag om te definieer'n gemeenskaplike funksie wat jy kan neem'n verwysing na enige van hierdie tipes en uittreksel van die data vervat.
In die pseudo-kode, hier is wat ek is op soek na:
Public Sub PrintUDT ( vData As Variant )
for each vDataMember in vData
print vDataMember.Name & ": " & vDataMember.value
next vDataMember
End Sub
Dit lyk soos hierdie inligting moet beskikbaar wees om COM iewers...Enige VB6 ghoeroes daar buite sorg te neem van'n skoot?
Dankie,
Dan
Oplossing
In teenstelling met wat ander sê, dit IS moontlik om te kry run-time tipe inligting vir die UDT is in VB6 (hoewel dit is nie'n ingeboude in die taal-funksie).Microsoft se TypeLib Inligting Voorwerp Biblioteek (tlbinf32.dll) kan jy om te programmaties inspekteer COM tipe inligting op'n run-time.Jy moet reeds hierdie komponent as jy Visual Studio geïnstalleer:toe te voeg tot'n bestaande VB6 projek, gaan na Projek->Verwysings en check die inskrywing gemerk "TypeLib Inligting." Let daarop dat jy sal hê om te versprei en te registreer tlbinf32.dll in jou aansoek se opstel van die program.
Jy kan inspekteer UDT gevalle met behulp van die TypeLib Inligting komponent op run-tyd, so lank as jou UDT se is verklaar Public
en gedefinieer word binne'n Public
die klas.Dit is nodig in orde te maak VB6 genereer COM-versoenbaar tipe inligting vir jou UDT se (wat kan dan word vervat is met die verskillende klasse in die TypeLib Inligting komponent).Die maklikste manier om te voldoen aan hierdie vereiste sou wees om te sit al jou UDT is in'n openbare UserTypes
klas wat sal saamgestel word in'n ActiveX DLL of ActiveX EXE.
Opsomming van'n werkende voorbeeld
Hierdie voorbeeld bevat drie dele:
- Deel 1:Die skep van'n ActiveX DLL projek wat sal bevat al die openbare verklarings UDT
- Deel 2:Skep'n voorbeeld
PrintUDT
metode te demonstreer hoe jy kan opnoemen die velde van'n UDT geval - Deel 3:Die skep van'n persoonlike iterator klas wat toelaat dat jy maklik itereer deur die velde van enige openbare UDT en kry veld name en waardes.
Die werkende voorbeeld
Deel 1:Die ActiveX DLL
Soos ek reeds genoem het, jy nodig het om te maak jou UDT se openbare-toeganklik te maak ten einde om hulle te opnoemen die gebruik van die TypeLib Inligting komponent.Die enigste manier om dit te bereik is om te sit jou UDT is in'n openbare klas binne'n ActiveX DLL of ActiveX EXE-projek.Ander projekte in jou aansoek dat die behoefte om toegang tot jou UDT se sal dan verwysing van hierdie nuwe komponent.
Om te volg saam met hierdie voorbeeld, begin deur die skep van'n nuwe ActiveX DLL projek en noem dit UDTLibrary
.
Volgende, die naam van die Class1
klas module (hierdie is bygevoeg by verstek deur die IDE) te UserTypes
en voeg twee gebruiker-gedefinieerde tipes van die klas, Person
en 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
dien as'n houer vir ons UDT se
Volgende, verander die Instancing eiendom vir die UserTypes
klas te "2-PublicNotCreatable".Daar is geen rede vir enige iemand om te instansieer die UserTypes
klas direk, want dit is net optree as'n openbare houer vir ons UDT is.
Ten slotte, maak seker dat die Project Startup Object
(onder Projek->Eienskappe) is ingestel om te "(Geen)" en stel die projek.Jy moet nou'n nuwe lêer genaamd UDTLibrary.dll
.
Deel 2:Opname UDT Tipe Inligting
Nou is dit tyd om te demonstreer hoe ons dit kan gebruik TypeLib Voorwerp Biblioteek te implementeer'n PrintUDT
metode.
Eerste, begin deur die skep van'n nuwe Standaard EXE-projek en noem dit wat jy wil.Voeg'n verwysing na die lêer UDTLibrary.dll
dit is geskep in Deel 1.Sedert ek wil net om te demonstreer hoe dit werk, sal ons gebruik maak van die Onmiddellike venster om die toets van die kode wat ons sal skryf.
Skep'n nuwe Module, noem dit UDTUtils
en voeg die volgende kode om dit te:
'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
Lys 2:'n voorbeeld PrintUDT
metode en'n eenvoudige toets metode
Deel 3:Maak dit Objek-Georiënteerde
Die bogenoemde voorbeelde verskaf'n "vinnige en vuil" demonstrasie van hoe om te gebruik die TypeLib Inligting Voorwerp Biblioteek te opnoemen die velde van'n UDT.In'n werklike-wêreld scenario, sou ek waarskynlik die skep van'n UDTMemberIterator
klas wat sal toelaat dat jy meer maklik te itereer deur die velde van die UDT, saam met'n nut funksie in'n module wat'n UDTMemberIterator
vir'n gegewe UDT geval.Dit sal toelaat dat jy om iets te doen soos die volgende in jou kode, wat is veel nader aan die pseudo-kode wat jy geplaas in jou vraag:
Dim member As UDTMember 'UDTMember wraps a TLI.MemberInfo instance'
For Each member In UDTMemberIteratorFor(someUDT)
Debug.Print member.Name & " : " & member.Value
Next
Dit is eintlik nie te moeilik om dit te doen, en ons kan weer gebruik die meeste van die kode van die PrintUDT
roetine geskep in Deel 2.
Eerste, die skep van'n nuwe ActiveX projek en noem dit UDTTypeInformation
of iets soortgelyk.
Volgende, maak seker dat die Startup Voorwerp vir die nuwe projek is ingestel op "(Nie)".
Die eerste ding om te doen is om te skep'n eenvoudige wrapper klas wat sal steek die besonderhede van die TLI.MemberInfo
klas van die roeping kode en maak dit maklik om te kry'n UDT se veld se naam en waarde.Ek het hierdie klas UDTMember
.Die Instancing eiendom vir hierdie klas moet wees 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
Lys 3:Die UDTMember
wrapper klas
Nou het ons nodig het om te skep'n iterator klas, UDTMemberIterator
, wat sal toelaat dat ons om te gebruik VB se For Each...In
sintaksis te itereer die velde van'n UDT geval.Die Instancing
eiendom vir hierdie klas moet ingestel word om PublicNotCreatable
(ons sal definieer'n nut metode later sal skep gevalle namens van die roeping van die kode).
EDIT: (2/15/09) ek het skoongemaak die kode'n bietjie meer.
'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
Lys 4:Die UDTMemberIterator
die klas.
Let daarop dat ten einde te maak van hierdie klas iterable so dat For Each
kan gebruik word met dit, sal jy het om te stel sekere Prosedure Eienskappe op die Item
en _NewEnum
metodes (soos in die kode kommentaar).Jy kan dit verander die Proses die Eienskappe van die Kieslys (Tools->Prosedure Eienskappe).
Ten slotte, ons moet'n nut funksie (UDTMemberIteratorFor
in die heel eerste kode voorbeeld in hierdie afdeling) wat sal die skep van'n UDTMemberIterator
vir'n UDT byvoorbeeld, wat kan ons dan itereer met For Each
.Skep'n nuwe module genoem Utils
en voeg die volgende kode:
'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
Notering 5:Die UDTMemberIteratorFor
nut funksie.
Ten slotte, stel die projek en die skep van'n nuwe projek, om dit uit te toets.
In jou toets projet, voeg'n verwysing na die nuut-geskepte UDTTypeInformation.dll
en die UDTLibrary.dll
geskep in Deel 1 en probeer om uit die volgende kode in'n nuwe 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
Notering 6:Die toets uit die UDTMemberIterator
die klas.
Ander wenke
@Dan,
Dit lyk soos jou probeer om te gebruik RTTI van'n UDT.Ek dink nie jy kan regtig kry dat die inligting sonder om te weet oor die UDT voor die run-time.Te kry wat jy begin om te probeer:
Begrip UDTs
As gevolg van nie met hierdie besinning vermoë.Ek sou my eie RTTI om my UDTs.
Te gee jy'n basislyn.Probeer om hierdie:
Type test
RTTI as String
a as Long
b as Long
c as Long
d as Integer
end type
Jy kan skryf'n program wat sal maak elke bron lêer en voeg Die RTTI met die naam van die tipe van die UDT.Waarskynlik sou beter wees om te sit al die UDTs in'n algemene lêer.
Die RTTI sou iets soos hierdie:
"String:Lang:Lang:Heelgetal"
Met behulp van die geheue van die UDT jy kan pak die waardes.
As jy verander al jou Tipes om Klasse.Jy het opsies.Die groot slaggat van die verandering van'n tipe van'n klas is wat jy het om te gebruik om die nuwe keyworld.Elke keer as daar'n verklaring van'n tipe veranderlike voeg nuwe.
Dan kan jy gebruik maak van die variant navraag of CallByName.VB6 nie anytype van besinning, maar jy kan maak lyste van geldige velde en toets om te sien of hulle teenwoordig is byvoorbeeld
Die Klas Toets het die volgende
Public Key As String
Public Data As String
Jy kan dan die volgende doen
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
As jy probeer om te gebruik om'n veld wat nie bestaan dan fout 438 sal opgewek word.CallByName jy kan gebruik om die snare te bel die veld en metodes van'n klas.
Wat VB6 doen wanneer jy verklaar Dowwe as die Nuwe is baie interessant en sal grootliks verminder foute in hierdie omskakeling.Jy sien hierdie
Dim T as New Test
is nie behandel word nie presies dieselfde as
Dim T as Test
Set T = new Test
Byvoorbeeld, dit sal werk
Dim T as New Test
T.Key = "A Key"
Set T = Nothing
T.Key = "A New Key"
Dit gee'n fout
Dim T as Test
Set T = New Test
T.Key = "A Key"
Set T = Nothing
T.Key = "A New Key"
Die rede hiervoor is dat in die eerste voorbeeld VB6 vlae T so dat wanneer'n lid is verkry dit kontroleer of die T is niks.As dit is dit sal outomaties skep'n nuwe weergawe van die Toets Klas en dan wys die veranderlike.
In die tweede voorbeeld VB nie by hierdie gedrag.
In die meeste projek het ons streng maak seker ons gaan Dowwe T as Toets, Stel T = Nuwe Toets.Maar in jou geval, want jy wil om te skakel Tipes in Klasse met die minste hoeveelheid van die newe-effekte met behulp van Dowwe T as Nuwe Toets is die pad om te gaan.Dit is omdat die Dowwe as Nuwe veroorsaak dat die veranderlike na te boots die manier tipes werk meer nou.