Domanda

Ho la sensazione che la risposta a questo sta per essere "non è possibile", ma darò un colpo ... Io sono nella non invidiabile posizione di modificare un'applicazione legacy VB6 con alcuni miglioramenti. Conversione in un linguaggio più intelligente non è un'opzione. L'applicazione si basa su una vasta collezione di tipi definiti dall'utente per spostare i dati in giro. Vorrei definire una funzione comune che può fare un riferimento a uno di questi tipi ed estrarre i dati contenuti.
In pseudo-codice, ecco quello che sto cercando:

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

Sembra che questa informazione deve essere disponibile su COM da qualche parte ... Qualsiasi guru VB6 là fuori si preoccupano di prendere un colpo?

Grazie,

Dan

È stato utile?

Soluzione

Al contrario di ciò che altri hanno detto, è possibile ottenere run-time informazioni sul tipo per UDT di in VB6 (anche se non è un built-in funzione di lingua). TypeLib Informazione Oggetto Library (Tlbinf32.dll) consente di ispezionare programmazione informazioni di tipo COM in fase di esecuzione. Si dovrebbe già avere questa componente se è stato installato Visual Studio: per aggiungerlo un progetto VB6 esistente, andare a Progetto-> Riferimenti e controllare la voce etichettata "TypeLib informazioni." Si noti che si dovrà distribuire e registrare Tlbinf32.dll nel programma di installazione dell'applicazione.

È possibile controllare le istanze UDT utilizzando il componente TypeLib informazioni a run-time, a patto che i vostri UDT di sono dichiarati Public e sono definite all'interno di una classe Public. Ciò è necessario al fine di rendere VB6 generare informazioni di tipo COM-compatibile per il vostro UDT (che possono poi essere enumerato con le varie classi del componente TypeLib informazioni). Il modo più semplice per soddisfare questo requisito sarebbe quello di mettere tutte le UDT di in una classe UserTypes pubblico che verrà compilato in una DLL ActiveX o EXE ActiveX.

Sintesi di un esempio di lavoro

In questo esempio contiene tre parti:

  • Parte 1 : Creazione di un progetto DLL ActiveX che conterrà tutte le dichiarazioni UDT pubblico
  • Parte 2 : Creazione di un metodo di esempio PrintUDT per dimostrare come è possibile enumerare i campi di un'istanza UDT
  • Parte 3 :. Creazione di una classe iteratore personalizzato che permette di eseguire iterazioni facilmente attraverso i campi di qualsiasi UDT pubblico e ottenere nomi ei valori del campo

L'esempio di lavoro

Parte 1: La DLL ActiveX

Come ho già detto, è necessario fare del vostro UDT pubblico accessibili al fine di enumerare loro utilizzando il componente TypeLib informazioni. L'unico modo per farlo è quello di mettere i UDT di in una classe pubblica all'interno di un progetto DLL ActiveX o EXE ActiveX. Altri progetti nell'applicazione che hanno bisogno di accedere ai UDT di Will quindi fare riferimento a questo nuovo componente.

Per seguire insieme a questo esempio, iniziare con la creazione di un nuovo progetto DLL ActiveX e denominarlo UDTLibrary.

Avanti, rinominare il modulo di classe Class1 (questo viene aggiunto per impostazione predefinita dal IDE) per UserTypes e aggiungere due tipi definiti dall'utente alla classe, Person e 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: Atti UserTypes.cls come contenitore per il nostro UDT

Avanti, modificare il Instancing di proprietà per la classe UserTypes a "2-PublicNotCreatable". Non c'è alcuna ragione per chiunque di istanziare direttamente la classe UserTypes, perché è semplicemente agisce come un contenitore di pubblico per i nostri UDT di.

Infine, assicurarsi che il Project Startup Object (in Progetto-> Proprietà ) è impostato su "(Nessuno)" e compilare il progetto. Ora si dovrebbe avere un nuovo file chiamato UDTLibrary.dll.

Parte 2: enumerazione UDT Informazioni Tipo

Ora è il momento di dimostrare come possiamo utilizzare TypeLib Object Library per implementare un metodo PrintUDT.

In primo luogo, iniziare con la creazione di un nuovo progetto EXE standard e lo chiamano quello che vuoi. Aggiungere un riferimento al file UDTLibrary.dll che è stato creato nella Parte 1. Dal momento voglio solo dimostrare come funziona, useremo la finestra immediata per testare il codice che scriveremo.

Crea un nuovo modulo, il nome è UDTUtils e aggiungere il seguente codice a esso:

'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

Listato 2: Un metodo esempio PrintUDT e un metodo semplice test

Parte 3: Rendere più orientata agli oggetti

Gli esempi sopra riportati forniscono una dimostrazione "veloce e sporco" di come utilizzare il TypeLib Informazione libreria di oggetti per enumerare i campi di un tipo definito dall'utente. In uno scenario del mondo reale, probabilmente creare una classe UDTMemberIterator che permetterebbe di eseguire iterazioni più facilmente attraverso i campi di UDT, insieme a una funzione di utilità in un modulo che crea un UDTMemberIterator per una determinata istanza UDT. Ciò consentirebbe di fare qualcosa di simile al seguente nel codice, che è molto più vicino alla pseudo-codice che avete inviato nella tua domanda:

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

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

In realtà non è troppo difficile fare questo, e possiamo riutilizzare maggior parte del codice dalla routine PrintUDT creato nella parte 2.

In primo luogo, creare un nuovo progetto ActiveX e denominarlo UDTTypeInformation o qualcosa di simile.

Avanti, assicurarsi che l'oggetto di avvio per il nuovo progetto è impostato su "(nessuno)".

La prima cosa da fare è quello di creare un semplice classe wrapper che nascondere i dettagli della classe TLI.MemberInfo dal codice chiamante e rendere più facile per ottenere nome e il valore di campo di un tipo definito dall'utente. Ho chiamato questa classe UDTMember. Il Instancing di proprietà per questa classe deve essere 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: La classe UDTMember involucro

Ora abbiamo bisogno di creare una classe iteratore, UDTMemberIterator, che ci permetterà di utilizzare la sintassi For Each...In di VB per scorrere i campi di un'istanza di tipo definito dall'utente. La proprietà Instancing per questa classe deve essere impostato su PublicNotCreatable (definiremo un metodo di utilità dopo che creare istanze per conto di chiamare codice).

EDIT:. (2/15/09) Ho pulito il codice un po 'più

'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:. La classe UDTMemberIterator

Si noti che al fine di rendere questa classe iterabile in modo che For Each può essere utilizzato con esso, si dovrà impostare determinata procedura Attributi sui metodi Item e _NewEnum (come indicato nei commenti di codice). È possibile modificare il Attributi routine dal menu Strumenti (Strumenti-> Attributi routine).

Infine, è necessaria una funzione di utilità (UDTMemberIteratorFor nel primo esempio di codice in questa sezione) che creerà un UDTMemberIterator per un'istanza UDT, che possiamo poi iterare con For Each. Creare un nuovo modulo denominato Utils e aggiungere il seguente codice:

'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 funzione di utilità UDTMemberIteratorFor

Infine, compilare il progetto e creare un nuovo progetto di provarlo.

Nel vostro projet prova, aggiungere un riferimento al UDTTypeInformation.dll di recente creazione e la UDTLibrary.dll creato nella parte 1 e provare il seguente codice in un nuovo modulo:

'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

6:. Testare la classe UDTMemberIterator

Altri suggerimenti

@ Dan,

Sembra che la tua cercando di utilizzare RTTI di un tipo definito dall'utente. Non credo che si può realmente ottenere che le informazioni senza conoscere l'UDT prima di run-time. Per iniziare provare:

UDT Comprendere
Causa di non avere questa capacità di riflessione. Vorrei creare il mio RTTI ai miei UDT.

Per darvi una linea di base. Prova questo:

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

È possibile scrivere un programma di utilità che aprirà tutti i file di origine e aggiungere il RTTI con il nome del tipo per l'UDT. Probabilmente sarebbe meglio mettere tutti gli UDT in un file comune.

Il RTTI sarebbe qualcosa di simile a questo:

"Stringa: Lungo: Lungo: Lungo: Integer"

Utilizzando la memoria del tipo definito dall'utente è possibile estrarre i valori.

Se si cambia tutti i tipi di classi. Avete opzioni. Il grande scoglio di cambiare da un tipo a una classe è che si deve utilizzare il nuovo Keyworld. Ogni volta che c'è una dichiarazione di una variabile di tipo aggiungere nuova.

Quindi è possibile utilizzare la parola chiave variante o CallByName. VB6 non ha anyType di riflessione, ma si può fare liste di campi valide e test per vedere se sono presenti, ad esempio

Il test di classe ha la seguente

Public Key As String
Public Data As String

È quindi possibile effettuare le seguenti

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

Se si tenta di utilizzare un campo che non esiste, allora errore verrà sollevato 438. CallByName consente di utilizzare le stringhe di chiamare il campo e metodi di una classe.

Cosa VB6 fa quando si dichiara Dim come nuovo è molto interessante e notevolmente ridurre al minimo i bug in questa conversione. Si vede questo

Dim T as New Test

Non è trattata esattamente lo stesso di

Dim T as Test
Set T = new Test

Per esempio questo funzionerà

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

Questo vi darà un errore di

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

La ragione di questo è che nelle prime bandiere esempio VB6 T in modo che ogni volta che un membro si accede è verificare se la T è nulla. Se è creerà automaticamente una nuova istanza della classe di test e quindi assegnare la variabile.

Nel secondo esempio VB non aggiunge questo comportamento.

Nella maggior parte del progetto abbiamo rigorosamente essere sicuri di andare Dim T come Test, insieme T = Nuovo Test. Ma nel tuo caso, poiché si desidera convertire Tipi in Classi con il minor numero di effetti collaterali che utilizzano Dim T come nuovo test è la strada da percorrere. Questo perché la Dim come nuova causa la variabile per imitare i tipi modo in cui funziona più da vicino.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top