문제

이에 대한 대답은 "불가능"할 것 같지만 시도해 보겠습니다.나는 몇 가지 개선 사항을 포함하여 레거시 VB6 앱을 수정해야 하는 불가피한 위치에 있습니다.더 똑똑한 언어로 변환하는 것은 선택 사항이 아닙니다.이 앱은 데이터를 이동하기 위해 대규모의 사용자 정의 유형 컬렉션을 사용합니다.이러한 유형에 대한 참조를 취하고 포함된 데이터를 추출할 수 있는 공통 함수를 정의하고 싶습니다.
의사 코드에서 내가 찾고 있는 것은 다음과 같습니다.

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

이 정보는 COM 어딘가에서 사용할 수 있어야 할 것 같습니다...VB6 전문가가 한 번 시도해 볼까?

감사해요,

도움이 되었습니까?

해결책

다른 사람들의 말과는 달리 VB6에서는 UDT에 대한 런타임 유형 정보를 얻는 것이 가능합니다(내장된 언어 기능은 아니지만).마이크로소프트의 TypeLib 정보 개체 라이브러리 (tlbinf32.dll)을 사용하면 런타임에 COM 유형 정보를 프로그래밍 방식으로 검사할 수 있습니다.Visual Studio가 설치되어 있는 경우 이 구성 요소가 이미 있어야 합니다.기존 VB6 프로젝트에 추가하려면 다음으로 이동하세요. 프로젝트->참조 "Typelib 정보"라는 항목을 확인하십시오. 응용 프로그램 설정 프로그램에 tlbinf32.dll을 배포하고 등록해야합니다.

UDT가 선언되어 있는 한 런타임에 TypeLib 정보 구성 요소를 사용하여 UDT 인스턴스를 검사할 수 있습니다. Public 그리고 Public 수업.이는 VB6이 UDT에 대한 COM 호환 유형 정보를 생성하도록 하기 위해 필요합니다(이 정보는 TypeLib 정보 구성 요소의 다양한 클래스로 열거될 수 있습니다).이 요구 사항을 충족하는 가장 쉬운 방법은 모든 UDT를 공개로 두는 것입니다. UserTypes ActiveX DLL 또는 ActiveX EXE로 컴파일될 클래스입니다.

실제 사례 요약

이 예는 세 부분으로 구성됩니다.

  • 1 부:모든 공개 UDT 선언을 포함할 ActiveX DLL 프로젝트 만들기
  • 2 부:예제 만들기 PrintUDT UDT 인스턴스의 필드를 열거하는 방법을 보여주는 메서드
  • 3부:공용 UDT의 필드를 쉽게 반복하고 필드 이름과 값을 가져올 수 있는 사용자 정의 반복자 클래스를 만듭니다.

실제 사례

1 부:ActiveX DLL

이미 언급했듯이 TypeLib 정보 구성 요소를 사용하여 UDT를 열거하려면 UDT를 공개적으로 액세스할 수 있도록 만들어야 합니다.이를 달성하는 유일한 방법은 UDT를 ActiveX DLL 또는 ActiveX EXE 프로젝트 내의 공용 클래스에 넣는 것입니다.UDT에 액세스해야 하는 응용 프로그램의 다른 프로젝트는 이 새 구성 요소를 참조합니다.

이 예제를 따라가려면 먼저 새 ActiveX DLL 프로젝트를 만들고 이름을 지정합니다. UDTLibrary.

다음으로 이름을 바꿉니다. Class1 클래스 모듈(IDE에 의해 기본적으로 추가됨) UserTypes 두 개의 사용자 정의 유형을 클래스에 추가합니다. Person 그리고 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

목록 1: UserTypes.cls UDT의 컨테이너 역할을 합니다.

다음으로 인스턴스화 에 대한 재산 UserTypes 클래스를 "2-PublicNotCreatable"로 변경합니다.누구도 인스턴스화할 이유가 없습니다. UserTypes 클래스를 직접 사용합니다. 이는 단순히 UDT의 공개 컨테이너 역할을 하기 때문입니다.

마지막으로, Project Startup Object (아래에 프로젝트->속성)을 "(없음)"으로 설정하고 프로젝트를 컴파일합니다.이제 다음과 같은 새 파일이 있어야 합니다. UDTLibrary.dll.

2 부:UDT 유형 정보 열거

이제 TypeLib 개체 라이브러리를 사용하여 다음을 구현하는 방법을 시연할 시간입니다. PrintUDT 방법.

먼저, 새로운 표준 EXE 프로젝트를 생성하고 원하는 대로 이름을 지정하세요.파일에 대한 참조 추가 UDTLibrary.dll 1부에서 만든 것입니다.저는 이것이 어떻게 작동하는지 보여주고 싶기 때문에 직접 실행 창을 사용하여 작성할 코드를 테스트하겠습니다.

새 모듈을 만들고 이름을 지정하세요. UDTUtils 그리고 다음 코드를 추가하세요.

'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

목록 2:예 PrintUDT 방법과 간단한 테스트 방법

파트 3:객체지향으로 만들기

위의 예는 TypeLib 정보 개체 라이브러리를 사용하여 UDT 필드를 열거하는 방법에 대한 "빠르고 더러운" 데모를 제공합니다.실제 시나리오에서는 아마도 UDTMemberIterator UDT 필드를 더 쉽게 반복할 수 있게 해주는 클래스와 함께 UDT 필드를 생성하는 모듈의 유틸리티 함수 UDTMemberIterator 특정 UDT 인스턴스에 대해.이렇게 하면 코드에서 다음과 같은 작업을 수행할 수 있으며, 이는 질문에 게시한 의사 코드에 훨씬 더 가깝습니다.

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

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

실제로 이 작업을 수행하는 것은 그리 어렵지 않으며 다음 코드의 대부분을 재사용할 수 있습니다. PrintUDT Part 2에서 만든 루틴입니다.

먼저 새 ActiveX 프로젝트를 만들고 이름을 지정합니다. UDTTypeInformation 또는 비슷한 것.

다음으로, 새 프로젝트의 시작 개체가 "(없음)"으로 설정되어 있는지 확인하세요.

가장 먼저 해야 할 일은 다음의 세부 사항을 숨기는 간단한 래퍼 클래스를 만드는 것입니다. TLI.MemberInfo 호출 코드에서 클래스를 생성하고 UDT 필드의 이름과 값을 쉽게 얻을 수 있도록 합니다.나는 이 수업을 불렀다 UDTMember.그만큼 인스턴스화 이 클래스의 속성은 다음과 같아야 합니다. 공개생성 불가능.

'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

목록 3:그만큼 UDTMember 래퍼 클래스

이제 반복자 클래스를 만들어야 합니다. UDTMemberIterator, 그러면 VB를 사용할 수 있게 됩니다. For Each...In UDT 인스턴스의 필드를 반복하는 구문입니다.그만큼 Instancing 이 클래스의 속성은 다음과 같이 설정되어야 합니다. PublicNotCreatable (나중에 코드 호출을 대신하여 인스턴스를 생성하는 유틸리티 메서드를 정의할 것입니다.)

편집하다: (2/15/09) 코드를 좀 더 정리했습니다.

'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

목록 4:그만큼 UDTMemberIterator 수업.

이 클래스를 반복 가능하게 만들려면 For Each 함께 사용할 수 있는 경우에는 특정 프로시저 속성을 설정해야 합니다. Item 그리고 _NewEnum (코드 주석에 언급된 대로)도구 메뉴(도구->프로시저 속성)에서 프로시저 속성을 변경할 수 있습니다.

마지막으로 유틸리티 함수(UDTMemberIteratorFor 이 섹션의 첫 번째 코드 예제에서) UDTMemberIterator UDT 인스턴스의 경우 For Each.라는 새 모듈을 만듭니다. Utils 그리고 다음 코드를 추가하세요:

'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

목록 5:그만큼 UDTMemberIteratorFor 유틸리티 기능.

마지막으로 프로젝트를 컴파일하고 새 프로젝트를 만들어 테스트해 보세요.

테스트 프로젝트에서 새로 생성된 항목에 대한 참조를 추가하세요. UDTTypeInformation.dll 그리고 UDTLibrary.dll Part 1에서 생성한 다음 새 모듈에서 다음 코드를 사용해 보세요.

'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:테스트해보기 UDTMemberIterator 수업.

다른 팁

@단,

UDT의 RTTI를 사용하려고하는 것 같습니다. 런타임 전에 UDT에 대해 알지 못하고 그 정보를 실제로 얻을 수 있다고 생각하지 않습니다. 시작하려면 시도해보십시오.

UDT 이해
이 반사 능력이 없기 때문입니다. 나는 내 UDT에 내 자신의 RTTI를 만들 것입니다.

기준선을 제공합니다. 이 시도:

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

모든 소스 파일을 열고 유형 이름으로 UDT에 RTTI를 추가하는 유틸리티를 작성할 수 있습니다. 아마도 모든 udts를 공통 파일에 넣는 것이 좋습니다.

RTTI는 다음과 같습니다.

"문자열 : Long : Long : Long : Integer"

UDT의 메모리를 사용하면 값을 추출 할 수 있습니다.

모든 유형을 클래스로 변경하면 옵션이 있습니다. 유형에서 클래스로 변경하는 큰 함정은 새로운 Keyworld를 사용해야한다는 것입니다. 유형 변수 선언이 새로 추가 될 때마다 새로 추가하십시오.

그런 다음 변형 키워드 또는 CallByName을 사용할 수 있습니다. VB6은 반사 유형이 없지만 유효한 필드 목록을 작성하고 예를 들어 존재하는지 테스트 할 수 있습니다.

수업 테스트에는 다음이 있습니다

Public Key As String
Public Data As String

그런 다음 다음을 수행 할 수 있습니다

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

존재하지 않는 필드를 사용하려고하면 오류 438이 올라갑니다. CallbyName을 사용하면 문자열을 사용하여 클래스의 필드와 방법을 호출 할 수 있습니다.

Dim을 새로운 것으로 선언 할 때 VB6이하는 일은 매우 흥미롭고이 변환에서 버그를 크게 최소화 할 것입니다. 당신은 이것을 봅니다

Dim T as New Test

정확히 동일하게 취급되지 않습니다

Dim T as Test
Set T = new Test

예를 들어 이것은 작동합니다

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

이것은 오류가 발생합니다

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

그 이유는 첫 번째 예제에서 vb6 플래그 t를 통해 멤버가 액세스 할 때마다 t가 아무것도 없는지 확인하기 때문입니다. 그렇다면 테스트 클래스의 새 인스턴스를 자동으로 작성한 다음 변수를 지정합니다.

두 번째 예에서는 VB 가이 동작을 추가하지 않습니다.

대부분의 프로젝트에서 우리는 테스트대로 DIM T를 세우고 T = New Test를 설정합니다. 그러나 귀하의 경우 Dim T를 사용하여 유형을 최소한 양의 부작용으로 클래스로 변환하려면 새로운 테스트가 진행되는 방법입니다. 이는 새로운 원인이 변수가 유형을 더 밀접하게 모방하도록 원인이기 때문입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top