VB6 UDT的自检 [英] Self Inspection of VB6 UDTs

查看:104
本文介绍了VB6 UDT的自检的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我觉得这是不可能的"答案,但是我会试一试... 我无法通过一些增强功能来修改旧版VB6应用程序.转换为更智能的语言不是一种选择. 该应用程序依赖大量用户定义的类型来移动数据.我想定义一个通用函数,该函数可以引用任何这些类型并提取其中包含的数据.
用伪代码,这就是我要寻找的东西:

I have a feeling the answer to this is going to be "not possible", but I'll give it a shot... I am in the unenviable position of modifying a legacy VB6 app with some enhancements. Converting to a smarter language isn't an option. The app relies on a large collection of user defined types to move data around. I would like to define a common function that can take a reference to any of these types and extract the data contained.
In pseudo code, here's what I'm looking for:

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

似乎此信息需要在某个地方的COM上可用...那里有任何VB6专家愿意试一试吗?

It seems like this info needs to be available to COM somewhere... Any VB6 gurus out there care to take a shot?

谢谢

推荐答案

与其他人所说的相反,可以在VB6中获取UDT的运行时类型信息(尽管它不是内置语言功能) . Microsoft的 TypeLib信息对象库(tlbinf32.dll)允许您以编程方式检查运行时的COM类型信息.如果已安装Visual Studio,则应该已经具有此组件:将其添加到现有的VB6项目中,请转到 Project-> References 并检查标有"TypeLib Information"的条目.请注意,您将必须在应用程序的安装程序中分发并注册tlbinf32.dll.

Contrary to what others have said, it IS possible to get run-time type information for UDT's in VB6 (although it is not a built-in language feature). Microsoft's TypeLib Information Object Library (tlbinf32.dll) allows you to programmatically inspect COM type information at run-time. You should already have this component if you have Visual Studio installed: to add it to an existing VB6 project, go to Project->References and check the entry labeled "TypeLib Information." Note that you will have to distribute and register tlbinf32.dll in your application's setup program.

只要您的UDT被声明为Public并且在Public类中定义,您就可以在运行时使用TypeLib信息组件检查UDT实例.为了使VB6为您的UDT生成COM兼容类型信息,这是必需的(然后可以用TypeLib Information组件中的各种类枚举).满足此要求的最简单方法是将所有UDT放入公共UserTypes类中,该类将被编译为ActiveX DLL或ActiveX EXE.

You can inspect UDT instances using the TypeLib Information component at run-time, as long as your UDT's are declared Public and are defined within a Public class. This is necessary in order to make VB6 generate COM-compatible type information for your UDT's (which can then be enumerated with various classes in the TypeLib Information component). The easiest way to meet this requirement would be to put all your UDT's into a public UserTypes class that will be compiled into an ActiveX DLL or ActiveX EXE.

此示例包含三个部分:

  • 第1部分:创建一个ActiveX DLL项目,其中将包含所有公共UDT声明
  • 第2部分:创建示例PrintUDT方法以演示如何枚举UDT实例的字段
  • 第3部分:创建一个自定义的迭代器类,使您可以轻松地遍历任何公共UDT的字段并获取字段名称和值.
  • Part 1: Creating an ActiveX DLL project that will contain all the public UDT declarations
  • Part 2: Creating an example PrintUDT method to demonstrate how you can enumerate the fields of a UDT instance
  • Part 3: Creating a custom iterator class that allows you easily iterate through the fields of any public UDT and get field names and values.

工作示例

正如我已经提到的,您需要使您的UDT公开访问,以便使用TypeLib Information组件进行枚举.完成此操作的唯一方法是将您的UDT放入ActiveX DLL或ActiveX EXE项目中的公共类中.然后,应用程序中需要访问UDT的其他项目将引用此新组件.

As I already mentioned, you need to make your UDT's public-accessible in order to enumerate them using the TypeLib Information component. The only way to accomplish this is to put your UDT's into a public class inside an ActiveX DLL or ActiveX EXE project. Other projects in your application that need to access your UDT's will then reference this new component.

要继续执行本示例,请首先创建一个新的ActiveX DLL项目并将其命名为UDTLibrary.

To follow along with this example, start by creating a new ActiveX DLL project and name it UDTLibrary.

下一步,将Class1类模块(IDE默认添加)重命名为UserTypes,并向该类添加两个用户定义的类型PersonAnimal:

Next, rename the Class1 class module (this is added by default by the IDE) to UserTypes and add two user-defined types to the class, Person and 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的容器

Listing 1: UserTypes.cls acts as a container for our UDT's

接下来,将UserTypes类的 Instancing 属性更改为"2-PublicNotCreatable".任何人都没有理由直接实例化UserTypes类,因为它只是充当我们的UDT的公共容器.

Next, change the Instancing property for the UserTypes class to "2-PublicNotCreatable". There is no reason for anyone to instantiate the UserTypes class directly, because it's simply acting as a public container for our UDT's.

最后,确保将Project Startup Object(在 Project-> Properties 下)设置为(无)"并编译该项目.现在,您应该有一个名为UDTLibrary.dll的新文件.

Finally, make sure the Project Startup Object (under Project->Properties) is set to to "(None)" and compile the project. You should now have a new file called UDTLibrary.dll.

现在是时候演示如何使用TypeLib对象库实现PrintUDT方法了.

Now it's time to demonstrate how we can use TypeLib Object Library to implement a PrintUDT method.

首先,首先创建一个新的Standard EXE项目,然后根据需要进行调用.添加对在第1部分中创建的文件UDTLibrary.dll的引用.由于我仅想演示其工作原理,因此我们将使用即时"窗口来测试将要编写的代码.

First, start by creating a new Standard EXE project and call it whatever you like. Add a reference to the file UDTLibrary.dll that was created in Part 1. Since I just want to demonstrate how this works, we will use the Immediate window to test the code we will write.

创建一个新模块,将其命名为UDTUtils并向其中添加以下代码:

Create a new Module, name it UDTUtils and add the following code to it:

'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方法和简单的测试方法

Listing 2: An example PrintUDT method and a simple test method

以上示例提供了如何使用TypeLib信息对象库枚举UDT字段的快速而肮脏的"演示.在实际情况中,我可能会创建一个UDTMemberIterator类,该类使您可以更轻松地遍历UDT的字段,以及在模块中为给定UDT实例创建UDTMemberIterator的实用程序函数.这将使您可以在代码中执行类似以下的操作,这与您在问题中发布的伪代码非常接近:

The above examples provide a "quick and dirty" demonstration of how to use the TypeLib Information Object Library to enumerate the fields of a UDT. In a real-world scenario, I would probably create a UDTMemberIterator class that would allow you to more easily iterate through the fields of UDT, along with a utility function in a module that creates a UDTMemberIterator for a given UDT instance. This would allow you to do something like the following in your code, which is much closer to the pseudo-code you posted in your question:

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

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

执行此操作实际上并不难,我们可以重复使用第2部分中创建的PrintUDT例程中的大多数代码.

It's actually not too hard to do this, and we can re-use most of the code from the PrintUDT routine created in Part 2.

首先,创建一个新的ActiveX项目并将其命名为UDTTypeInformation或类似名称.

First, create a new ActiveX project and name it UDTTypeInformation or something similar.

接下来,确保将新项目的启动对象设置为(无)".

Next, make sure that the Startup Object for the new project is set to "(None)".

要做的第一件事是创建一个简单的包装器类,该类将对调用代码隐藏TLI.MemberInfo类的详细信息,并使获取UDT字段的名称和值变得容易.我称此类为UDTMember.此类的 Instancing 属性应为 PublicNotCreatable .

The first thing to do is to create a simple wrapper class that will hide the details of the TLI.MemberInfo class from calling code and make it easy to get a UDT's field's name and value. I called this class UDTMember. The Instancing property for this class should be 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包装器类

Listing 3: The UDTMember wrapper class

现在,我们需要创建一个迭代器类UDTMemberIterator,该类将允许我们使用VB的For Each...In语法来迭代UDT实例的字段.此类的Instancing属性应设置为PublicNotCreatable(稍后我们将定义一个实用程序方法,该方法将代表调用代码创建实例).

Now we need to create an iterator class, UDTMemberIterator, that will allow us to use VB's For Each...In syntax to iterate the fields of a UDT instance. The Instancing property for this class should be set to PublicNotCreatable (we will define a utility method later that will create instances on behalf of calling code).

编辑:(2/15/09)我整理了一些代码.

(2/15/09) I've cleaned the code up a bit more.

'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类.

Listing 4: The UDTMemberIterator class.

请注意,为了使此类可迭代,以便可以与For Each一起使用,必须在Item_NewEnum方法上设置某些过程属性(如代码注释中所述).您可以从工具"菜单(工具"->过程属性")更改过程属性.

Note that in order to make this class iterable so that For Each can be used with it, you will have to set certain Procedure Attributes on the Item and _NewEnum methods (as noted in the code comments). You can change the Procedure Attributes from the Tools Menu (Tools->Procedure Attributes).

最后,我们需要一个实用程序功能(在本节的第一个代码示例中为UDTMemberIteratorFor),它将为UDT实例创建UDTMemberIterator,然后可以使用For Each进行迭代.创建一个名为Utils的新模块,并添加以下代码:

Finally, we need a utility function (UDTMemberIteratorFor in the very first code example in this section) that will create a UDTMemberIterator for a UDT instance, which we can then iterate with For Each. Create a new module called Utils and add the following code:

'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实用程序功能.

Listing 5: The UDTMemberIteratorFor utility function.

最后,编译项目并创建一个新项目以对其进行测试.

Finally, compile the project and create a new project to test it out.

在测试项目中,添加对第1部分中新创建的UDTTypeInformation.dllUDTLibrary.dll的引用,并在新模块中尝试以下代码:

In your test projet, add a reference to the newly-created UDTTypeInformation.dll and the UDTLibrary.dll created in Part 1 and try out the following code in a new module:

'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类.

Listing 6: Testing out the UDTMemberIterator class.

这篇关于VB6 UDT的自检的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆