VB6 UDT 자체 검사
-
23-08-2019 - |
문제
이에 대한 대답은 "불가능"할 것 같지만 시도해 보겠습니다.나는 몇 가지 개선 사항을 포함하여 레거시 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를 사용하여 유형을 최소한 양의 부작용으로 클래스로 변환하려면 새로운 테스트가 진행되는 방법입니다. 이는 새로운 원인이 변수가 유형을 더 밀접하게 모방하도록 원인이기 때문입니다.