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
It seems like this info needs to be available to COM somewhere... Any VB6 gurus out there care to take a shot?
Thanks,
Dan
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.
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 aPublic
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 publicUserTypes
class that will be compiled into an ActiveX DLL or ActiveX EXE.Summary of a working example
This example contains three parts:
PrintUDT
method to demonstrate how you can enumerate the fields of a UDT instanceThe working example
Part 1: The ActiveX DLL
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.
To follow along with this example, start by creating a new ActiveX DLL project and name it
UDTLibrary
.Next, rename the
Class1
class module (this is added by default by the IDE) toUserTypes
and add two user-defined types to the class,Person
andAnimal
:Listing 1:
UserTypes.cls
acts as a container for our UDT'sNext, change the Instancing property for the
UserTypes
class to "2-PublicNotCreatable". There is no reason for anyone to instantiate theUserTypes
class directly, because it's simply acting as a public container for our UDT's.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 calledUDTLibrary.dll
.Part 2: Enumerating UDT Type Information
Now it's time to demonstrate how we can use TypeLib Object Library to implement a
PrintUDT
method.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.Create a new Module, name it
UDTUtils
and add the following code to it:Listing 2: An example
PrintUDT
method and a simple test methodPart 3: Making it Object-Oriented
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 aUDTMemberIterator
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: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.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)".
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 classUDTMember
. The Instancing property for this class should be PublicNotCreatable.Listing 3: The
UDTMember
wrapper classNow we need to create an iterator class,
UDTMemberIterator
, that will allow us to use VB'sFor Each...In
syntax to iterate the fields of a UDT instance. TheInstancing
property for this class should be set toPublicNotCreatable
(we will define a utility method later that will create instances on behalf of calling code).EDIT: (2/15/09) I've cleaned the code up a bit more.
Listing 4: The
UDTMemberIterator
class.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 theItem
and_NewEnum
methods (as noted in the code comments). You can change the Procedure Attributes from the Tools Menu (Tools->Procedure Attributes).Finally, we need a utility function (
UDTMemberIteratorFor
in the very first code example in this section) that will create aUDTMemberIterator
for a UDT instance, which we can then iterate withFor Each
. Create a new module calledUtils
and add the following code:Listing 5: The
UDTMemberIteratorFor
utility function.Finally, compile the project and create a new project to test it out.
In your test projet, add a reference to the newly-created
UDTTypeInformation.dll
and theUDTLibrary.dll
created in Part 1 and try out the following code in a new module:Listing 6: Testing out the
UDTMemberIterator
class.