我有一种感觉,答案是"不可能",但我会试一试......我处于一个不值得羡慕的位置,修改了一些增强功能的旧版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的运行时类型信息(尽管它不是内置语言功能).Microsoft的TypeLib信息对象库(tlbinf32.dll)允许您以编程方式在运行时检查COM类型信息.如果安装了Visual Studio,则应该已经拥有此组件:要将其添加到现有VB6项目,请转到Project-> References并检查标记为"TypeLib Information"的条目.请注意,您必须在应用程序的安装程序中分发和注册tlbinf32.dll.
只要声明了UDT Public
并在Public
类中定义了UDT实例,就可以在运行时使用TypeLib信息组件检查UDT实例.这是必要的,以便使VB6为您的UDT生成COM兼容的类型信息(然后可以使用TypeLib信息组件中的各种类枚举).满足此要求的最简单方法是将所有UDT放入UserTypes
将被编译为ActiveX DLL或ActiveX EXE 的公共类中.
此示例包含三个部分:
第1部分:创建将包含所有公共UDT声明的ActiveX DLL项目
第2部分:创建一个示例PrintUDT
方法来演示如何枚举UDT实例的字段
第3部分:创建一个自定义迭代器类,允许您轻松遍历任何公共UDT的字段并获取字段名称和值.
工作实例
正如我已经提到的,您需要使UDT是可公共访问的,以便使用TypeLib Information组件枚举它们.实现此目的的唯一方法是将您的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的容器
接下来,将类的Instancing属性更改UserTypes
为"2-PublicNotCreatable".任何人都没有理由UserTypes
直接实例化该类,因为它只是作为UDT的公共容器.
最后,确保Project Startup Object
(在Project-> Properties下)设置为"(None)"并编译项目.您现在应该有一个名为的新文件UDTLibrary.dll
.
现在是时候演示我们如何使用TypeLib对象库来实现一个PrintUDT
方法.
首先,首先创建一个新的标准EXE项目并随意调用它.添加对UDTLibrary.dll
在第1部分中创建的文件的引用.由于我只想演示它是如何工作的,我们将使用Immediate窗口来测试我们将要编写的代码.
创建一个新模块,为其命名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
方法和简单的测试方法
上面的示例提供了如何使用TypeLib信息对象库枚举UDT字段的"快速和脏"演示.在现实世界的场景中,我可能会创建一个UDTMemberIterator
类,使您可以更轻松地遍历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
第2部分中创建的例程中的大部分代码.
首先,创建一个新的ActiveX项目并命名它UDTTypeInformation
或类似的东西.
接下来,确保新项目的"启动对象"设置为"(无)".
首先要做的是创建一个简单的包装类,它将隐藏类的详细信息以TLI.MemberInfo
调用代码,并使得获取UDT字段的名称和值变得容易.我打电话给这堂课UDTMember
.此类的Instancing属性应为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
清单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
第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
类.