Pergunta

Eu tenho um sentimento a resposta a esta vai ser "não é possível", mas vou dar-lhe um tiro ... Estou na posição nada invejável de modificar um aplicativo legado VB6 com algumas melhorias. Convertendo para uma linguagem mais inteligente não é uma opção. O aplicativo conta com uma grande coleção de tipos definidos pelo usuário para mover os dados. Gostaria de definir uma função comum que pode ter uma referência para qualquer um desses tipos e extrair os dados contidos.
No código pseudo, aqui está o que eu estou procurando:

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

Parece que esta informação deve estar disponível para algum lugar COM ... Qualquer VB6 gurus lá fora, o cuidado de tomar um tiro?

Obrigado,

Dan

Foi útil?

Solução

Ao contrário do que já foi dito, é possível obter informações sobre o tipo de tempo de execução para a UDT está em VB6 (embora não seja um built-in recurso de linguagem). TypeLib Informação de Microsoft Object Library (tlbinf32.dll) permite-lhe inspeccionar programaticamente COM informações de tipo em tempo de execução. Você já deve ter este componente se você tiver Visual Studio instalado: para adicioná-lo a um projeto VB6 existente, vá para Project-> referências e verificar a entrada rotulado "Informações TypeLib." Note que você terá que distribuir e registrar tlbinf32.dll no programa de configuração do aplicativo.

Você pode inspecionar as instâncias da UDT usando o componente de informação TypeLib em tempo de execução, desde que os seus da UDT são declarados Public e são definidos dentro de uma classe Public. Isto é necessário, a fim de fazer VB6 gerar informações de tipo COM-compatível para o seu UDT de (que pode então ser enumerados com várias classes no componente de informação TypeLib). A maneira mais fácil de cumprir este requisito seria colocar todo o seu UDT de em uma classe UserTypes público que serão compilados em uma DLL ActiveX ou EXE ActiveX.

Resumo de um exemplo de trabalho

Este exemplo contém três partes:

  • Parte 1 : Criar um projeto ActiveX DLL que irá conter todas as declarações a UDT pública
  • Parte 2 : Criando um método exemplo PrintUDT para demonstrar como você pode enumerar os campos de uma instância de UDT
  • Parte 3 :. Criar uma classe iterator personalizado que permite que você facilmente percorrer os campos de qualquer UDT pública e obter nomes e valores de campo

O exemplo de trabalho

Parte 1: O ActiveX DLL

Como já mencionado, você precisa fazer do seu UDT pública acessível, a fim de enumerá-las usando o componente de informação TypeLib. A única maneira de alcançar este objetivo é colocar o seu UDT de em uma classe pública dentro de um projeto ActiveX DLL ou EXE ActiveX. Outros projetos em seu aplicativo que precisa acessar seu UDT de então fazem referência a este novo componente.

Para acompanhar este exemplo, começar por criar um novo projeto ActiveX DLL e nomeá-la UDTLibrary.

Em seguida, mudar o nome do módulo de classe Class1 (este é adicionado por padrão pelo IDE) para UserTypes e adicionar dois tipos definidos pelo usuário para a 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

Listagem 1: UserTypes.cls atua como um recipiente para o nosso

da UDT

Em seguida, mudar o Instancing propriedade para a classe UserTypes para "2-PublicNotCreatable". Não há nenhuma razão para qualquer um para instanciar a classe UserTypes diretamente, porque ele está simplesmente agindo como um recipiente pública para o nosso UDT de.

Finalmente, certifique-se a Project Startup Object (em Projeto-> Propriedades ) é definida como a "(nenhum)" e compilar o projeto. Agora você deve ter um novo arquivo chamado UDTLibrary.dll.

Parte 2: Enumerando UDT Type Information

Agora é hora de demonstrar como podemos usar Object Library TypeLib para implementar um método PrintUDT.

Em primeiro lugar, começar por criar um novo projecto EXE padrão e chamá-lo o que quiser. Adicione uma referência para o UDTLibrary.dll arquivo que foi criado na Parte 1. Desde que eu só quero demonstrar como isso funciona, vamos utilizar a janela Immediate para testar o código vamos escrever.

Crie um novo módulo, nomeá-lo UDTUtils e adicione o seguinte código a ele:

'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

Listagem 2: Um método exemplo PrintUDT e um método simples teste

Parte 3: Tornando-Object-Oriented

Os exemplos acima fornecem uma demonstração "rápida e suja" de como usar o TyInformações peLib Object Library para enumerar os campos de um UDT. Em um cenário do mundo real, eu provavelmente iria criar uma classe UDTMemberIterator que lhe permitiria mais facilmente percorrer os campos da UDT, juntamente com uma função de utilidade em um módulo que cria um UDTMemberIterator de uma determinada ocorrência UDT. Isso permitirá que você a fazer algo como o seguinte em seu código, que é muito mais próximo do pseudo-código que você postou sua pergunta:

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

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

Na verdade não é muito difícil de fazer isso, e podemos voltar a usar a maior parte do código da rotina PrintUDT criado na Parte 2.

Primeiro, crie um novo projeto ActiveX e nomeie-UDTTypeInformation ou algo similar.

Em seguida, certifique-se de que o objecto de arranque para o novo projeto é definido como "(Nenhum)".

A primeira coisa a fazer é criar uma classe de invólucro simples que irá esconder os detalhes da classe TLI.MemberInfo de chamar código e torná-lo fácil de obter o nome e o valor do campo de um UDT. Eu chamei este UDTMember classe. O Instancing propriedade para esta classe deve ser 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

Listagem 3: A classe UDTMember invólucro

Agora precisamos criar uma classe de iterador, UDTMemberIterator, que nos permitirá usar sintaxe For Each...In do VB para iterate os campos de uma instância de UDT. A propriedade Instancing para esta classe deve ser definida para PublicNotCreatable (vamos definir um método de utilidade mais tarde que irá criar instâncias em nome de código de chamada).

EDIT:. (2/15/09) Eu limpo o código-se um pouco mais

'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

Listagem 4:. A classe UDTMemberIterator

Note que para fazer esta classe iterable para que For Each pode ser usado com ele, você terá que definir certos atributos de procedimento sobre os métodos Item e _NewEnum (como observado nos comentários de código). Você pode alterar o procedimento atributos a partir do menu Tools (Ferramentas-> atributos de procedimento).

Finalmente, precisamos de uma função de utilidade (UDTMemberIteratorFor no primeiro exemplo de código nesta seção) que irá criar um UDTMemberIterator para uma instância de UDT, que pode, então, iterate com For Each. Criar um novo módulo chamado Utils e adicione o código a seguir:

'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

Listagem 5:. A função de utilidade UDTMemberIteratorFor

Finalmente, compilar o projeto e criar um novo projeto para testá-lo.

Em seu projet teste, adicione uma referência para o UDTTypeInformation.dll recém-criado eo UDTLibrary.dll criado na Parte 1 e experimentar o seguinte código em um novo módulo:

'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

Listagem 6:. Testando a classe UDTMemberIterator

Outras dicas

@ Dan,

Parece que a sua tentativa de usar RTTI de um UDT. Eu não acho que você pode realmente obter essa informação sem saber sobre o UDT antes de run-time. Para começar tente:

UDTs Entendendo
Por causa de não ter essa capacidade de reflexão. Gostaria de criar minha própria RTTI aos meus UDTs.

Para dar-lhe uma linha de base. Tente isto:

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

Você pode escrever um utilitário que irá abrir todos os arquivos fonte e adicionar o RTTI com o nome do tipo para o UDT. Provavelmente seria melhor colocar todos os UDTs em um arquivo comum.

O RTTI seria algo como isto:

"String: Longo: Longo: Longo: Integer"

Usando a memória da UDT você pode extrair os valores.

Se você alterar todos os seus tipos de Classes. Você tem opções. A grande armadilha de mudar de um tipo para uma classe é que você tem que usar o novo Keyworld. Toda vez que há uma declaração de uma variável do tipo adicionar novos.

Em seguida, você pode usar a palavra-chave variante ou CallByName. não VB6 não tem anytype de reflexão, mas você pode fazer listas de campos válidos e teste para ver se eles estão presentes, por exemplo,

O Teste de classe tem o seguinte

Public Key As String
Public Data As String

Você pode então fazer o seguinte

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 você tentar usar um campo que não existe, em seguida, erro 438 será gerado. CallByName permite usar cordas para ligar para o campo e métodos de uma classe.

O que VB6 faz quando você declarar Dim como New é bastante interessante e irá minimizar consideravelmente bugs no essa conversão. Você vê este

Dim T as New Test

não for tratada exatamente o mesmo que

Dim T as Test
Set T = new Test

Por exemplo Este trabalho irá

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

Isso dará um erro

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

A razão para isso é que nas primeiras bandeiras exemplo VB6 T de modo que a qualquer momento um membro é acessado ele verificar se o T não é nada. Se é que vai criar automaticamente uma nova instância da classe de teste e, em seguida, atribuir a variável.

No segundo exemplo VB não adiciona esse comportamento.

Na maioria projecto que rigorosamente se certificar de que vamos Dim T como teste, aparelho de T = New Test. Mas no seu caso desde que você deseja converter tipos em Classes com o mínimo de efeitos colaterais usando Dim T como New Test é o caminho a percorrer. Isso ocorre porque o Dim como New causa a variável para imitar a forma como os tipos de trabalhos mais de perto.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top