Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>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 <a href="http://msdn.microsoft.com/en-us/magazine/bb985086.aspx" rel="noreferrer">TypeLib Information Object Library</a> (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 <em>Project->References</em> and check the entry labeled "TypeLib Information." Note that you will have to distribute and register tlbinf32.dll in your application's setup program.</p> <p>You can inspect UDT instances using the TypeLib Information component at run-time, as long as your UDT's are declared <code>Public</code> and are defined within a <code>Public</code> 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 <code>UserTypes</code> class that will be compiled into an ActiveX DLL or ActiveX EXE.</p> <h2>Summary of a working example</h2> <p>This example contains three parts:</p> <ul> <li><strong>Part 1</strong>: Creating an ActiveX DLL project that will contain all the public UDT declarations</li> <li><strong>Part 2</strong>: Creating an example <code>PrintUDT</code> method to demonstrate how you can enumerate the fields of a UDT instance</li> <li><strong>Part 3</strong>: Creating a custom iterator class that allows you easily iterate through the fields of any public UDT and get field names and values.</li> </ul> <hr> <p><strong>The working example</strong></p> <h3>Part 1: The ActiveX DLL</h3> <p>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.</p> <p>To follow along with this example, start by creating a new ActiveX DLL project and name it <code>UDTLibrary</code>.</p> <p>Next, rename the <code>Class1</code> class module (this is added by default by the IDE) to <code>UserTypes</code> and add two user-defined types to the class, <code>Person</code> and <code>Animal</code>:</p> <pre><code>' 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 </code></pre> <p><strong>Listing 1: <code>UserTypes.cls</code> acts as a container for our UDT's</strong></p> <p>Next, change the <em>Instancing</em> property for the <code>UserTypes</code> class to "2-PublicNotCreatable". There is no reason for anyone to instantiate the <code>UserTypes</code> class directly, because it's simply acting as a public container for our UDT's.</p> <p>Finally, make sure the <code>Project Startup Object</code> (under <em>Project->Properties</em>) is set to to "(None)" and compile the project. You should now have a new file called <code>UDTLibrary.dll</code>.</p> <h3>Part 2: Enumerating UDT Type Information</h3> <p>Now it's time to demonstrate how we can use TypeLib Object Library to implement a <code>PrintUDT</code> method.</p> <p>First, start by creating a new Standard EXE project and call it whatever you like. Add a reference to the file <code>UDTLibrary.dll</code> 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.</p> <p>Create a new Module, name it <code>UDTUtils</code> and add the following code to it:</p> <pre><code>'UDTUtils.bas' Option Explicit Public Sub PrintUDT(ByVal someUDT As Variant) ' Make sure we have a UDT and not something else... ' If VarType(someUDT) &lt;&gt; 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 '" &amp; TypeName(someUDT) &amp; "'" 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 &amp; " : " &amp; 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 </code></pre> <p><strong>Listing 2: An example <code>PrintUDT</code> method and a simple test method</strong></p> <h3>Part 3: Making it Object-Oriented</h3> <p>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 <code>UDTMemberIterator</code> 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 <code>UDTMemberIterator</code> 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:</p> <pre><code>Dim member As UDTMember 'UDTMember wraps a TLI.MemberInfo instance' For Each member In UDTMemberIteratorFor(someUDT) Debug.Print member.Name &amp; " : " &amp; member.Value Next </code></pre> <p>It's actually not too hard to do this, and we can re-use most of the code from the <code>PrintUDT</code> routine created in Part 2. </p> <p>First, create a new ActiveX project and name it <code>UDTTypeInformation</code> or something similar.</p> <p>Next, make sure that the Startup Object for the new project is set to "(None)".</p> <p>The first thing to do is to create a simple wrapper class that will hide the details of the <code>TLI.MemberInfo</code> class from calling code and make it easy to get a UDT's field's name and value. I called this class <code>UDTMember</code>. The <em>Instancing</em> property for this class should be <em>PublicNotCreatable</em>.</p> <pre><code>'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 </code></pre> <p><strong>Listing 3: The <code>UDTMember</code> wrapper class</strong></p> <p>Now we need to create an iterator class, <code>UDTMemberIterator</code>, that will allow us to use VB's <code>For Each...In</code> syntax to iterate the fields of a UDT instance. The <code>Instancing</code> property for this class should be set to <code>PublicNotCreatable</code> (we will define a utility method later that will create instances on behalf of calling code).</p> <p><strong>EDIT:</strong> (2/15/09) I've cleaned the code up a bit more.</p> <pre><code>'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-&gt;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-&gt;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) &lt;&gt; 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 '" &amp; TypeName(someUDT) &amp; "'" 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 </code></pre> <p><strong>Listing 4: The <code>UDTMemberIterator</code> class.</strong></p> <p>Note that in order to make this class iterable so that <code>For Each</code> can be used with it, you will have to set certain Procedure Attributes on the <code>Item</code> and <code>_NewEnum</code> methods (as noted in the code comments). You can change the Procedure Attributes from the Tools Menu (Tools->Procedure Attributes).</p> <p>Finally, we need a utility function (<code>UDTMemberIteratorFor</code> in the very first code example in this section) that will create a <code>UDTMemberIterator</code> for a UDT instance, which we can then iterate with <code>For Each</code>. Create a new module called <code>Utils</code> and add the following code:</p> <pre><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 &amp; ":" &amp; 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 </code></pre> <p><strong>Listing 5: The <code>UDTMemberIteratorFor</code> utility function.</strong></p> <p>Finally, compile the project and create a new project to test it out.</p> <p>In your test projet, add a reference to the newly-created <code>UDTTypeInformation.dll</code> and the <code>UDTLibrary.dll</code> created in Part 1 and try out the following code in a new module:</p> <pre><code>'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 &amp; " : " &amp; 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 &amp; " : " &amp; member.Value Next End Sub </code></pre> <p><strong>Listing 6: Testing out the <code>UDTMemberIterator</code> class.</strong></p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload