Dynamic UDT TypeLibs
First and foremost, a huge thanks goes out to The Trick for showing me how to do most of this. Thanks also goes out to Fafalone for making the oleexp.tlb which exposes the necessary interfaces to VB6 which makes this possible. And I must thank Schmidt as well for some inspiration and the beginnings of some code snippets for copying UDTs in Variants to regular UDTs (and vice-versa).
Ok, what is this thing? The primary impetus behind it was to make an easy, fully functioning way to get UDTs into Variants, and to be able to use those UDTs when they're sitting in Variants. As it turns out, this whole thing has two pretty cool purposes:
- It does precisely what I set out to do, dynamically (during runtime) create a TypeLib that contains whatever UDTs we want in it. This allows us to create (or assign) Variants with those UDTs, and easily pass them to wherever we want (even Public procedures in objects).
- It took a trivial amount of code to also save the dynamic TypeLib that was created, as a TLB file. So, it's also (if you want) a TypeLib generator. This allows us to define all our UDTs, then save the TLB file, and then use it in a subsequent program. No need for MIDL nor any other tools. Furthermore, you can push the resulting TLB file into OleView if you'd like to disassemble it and see the actual IDL code.
All of the code is in the class module named UdtsToVariants.cls. Just grab this CLS file and toss it into any VBP project to start using it. You will need a reference in that project to the oleexp.tlb TypeLib.
It has its VB_PredeclaredId set to True, so there's no need to declare or instantiate it. Just start using it. And there should never be a need to instantiate more than one copy, so this works out perfectly. It just behaves like an extension of the VB6 language.
Prerequisite: As has been mentioned, this all makes use of the oleexp.tlb, version 5.4 or later. This is the only prerequisite, other than a reasonably late version of Windows. I didn't check to see when all of this stuff became available, but I do believe it's been available for quite some time.
Also, some might say "well, you're still using a TypeLib" file. Yes, that's true, but I'm not using a TypeLib file for my actual UDTs, which was what was important to me. I don't mind TypeLibs. I just mind TypeLibs that I constantly have to tweak on and reassemble with MIDL. This hides all of that.
Internally, the class name is UDT. Let me start by listing the Public procedures in this UDT class:
- CreateNewUdtInDynamicTypeLib
- StartNewUdt
- AddItemToNewUdt
- FinishNewUdt
- SaveDynamicTypeLib
- DynamicUdtCount
- NewEmptyVariant
- NewEmptyVariantUsingRecInfo
- FromVariant
- ToVariant
- ToVariantUsingRecInfo
If you're just interested in UDTs with intrinsic types and late-bound objects, you can get away with only using the very first one (CreateNewUdtInDynamicTypeLib). This CreateNewUdtInDynamicTypeLib internally calls StartNewUdt, AddItemToNewUdt, & FinishNewUdt. However, if you want nested UDTs and/or arrays as your UDT items, you must explicitly call the StartNewUdt, AddItemToNewUdt, & FinishNewUdt trio. You will call AddItemToNewUdt for each item within your UDT, and you must be sure to call FinishNewUdt when you're done adding items.
The SaveDynamicTypeLib is entirely optional. You will use this only if you wish to save your created dynamic TypeLib to disk as a TLB file. It's always saved in the App.Path folder and always named DynamicUdt.tlb. Once it's saved, you can rename and/or move it to wherever you like, and then use it in whatever project you like.
DynamicUdtCount is simply a count of the UDTs you've created in your dynamic TypeLib. It's not very useful, other than, internally, the code must track this.
The NewEmptyVariant procedure returns a Variant containing a new/empty copy of any UDT you've created in your TypeLib. What's particularly nice is, even when in a Variant, you can still address your UDT's items with the dot (.) syntax. You will lose the IntelliSense help for the items, but they will still work so long as you don't misspell them. You'll get a runtme error if you do.
The NewEmptyVariantUsingRecInfo does the same thing as NewEmptyVariant, but is uses an IRecordInfo object (rather than the UDT's name) to get its work done. Typically, we don't have to worry about these IRecordInfo object at all. However, when we don't use them, internally, they have to be looked up in a Collection. If we're after pure speed, it's faster to explicitly save our IRecordInfo objects, and then use them when we wish to create Variants (as it circumvents the Collection lookup).
In some cases, you may want to create a dynamic TypeLib of your UDTs, and then also mirror these with actual (traditional) UDT declarations (eg., Type MyUdt: i1 As Long: i2 As Long: End Type). When you've mirrored your UDTs like this, the FromVariant, ToVariant, & ToVariantUsingRecInfo become useful. The FromVariant procedure allows us to copy a UDT in a Variant to a UDT variable that's declared in the standard way. The ToVariant procedure does the precise opposite, taking a standard UDT variable and puts it into a Variant with that UDT. The ToVariantUsingRecInfo is just a faster way to do this part (if we're saving our IRecordInfo objects).
What does this currently not do? The only thing I can think of that it currently doesn't do is TypeLibs with early-binding. For instance, let's say we've got a Class1 in our project. With a standard UDT, we can do something like the following:
Code:
Private Type MyUdt
i As Long
o As Class1 ' Early bound.
End Type
Code:
Private Type MyUdt
i As Long
o As Object ' Late bound.
End Type
Rather than ramble on about what things do, let me continue by just showing several examples. I'll just use a Form1, and assume the UdtsToVariants.cls is included in the project, and that the oleexp.tlb is referenced.
Example #1, a simple UDT with a couple of intrinsic variables and an object:
Code:
Option Explicit
Private Sub Form_Load()
' Test of intrinsic types and late-bound objects.
Udt.CreateNewUdtInDynamicTypeLib "TestUdt1", "x", vbLong, "y", vbSingle, "s", vbString, "o", vbObject
Dim vTestUdt1 As Variant
vTestUdt1 = Udt.NewEmptyVariant("TestUdt1")
vTestUdt1.x = 5
vTestUdt1.y = 10
vTestUdt1.s = "asdf"
Set vTestUdt1.o = New StdFont
vTestUdt1.o.Name = "Courier New"
Debug.Print vTestUdt1.x, vTestUdt1.y, vTestUdt1.s, vTestUdt1.o.Name
' Prints out: 5 10 asdf Courier New
End Sub
- sUdtName As String
- ParamArray vUdtItemNamesAndTypes() As Variant
The sUdtName is obvious. It's just the name of our UDT we wish to create. The ParamArray vUdtItemNamesAndTypes() is a bit more complex, but not really if we're already familiar with creating standard UDTs. This is pairs of arguments. The first of each pair is a UDT item name, and then second is the item's type. Allowable types are: vbByte, vbInteger, vbLong, vbSingle, vbDouble, vbCurrency, vbDate, vbString, vbBoolean, vbObject, & vbVariant.
There's really not much more to it than that. And since our UDT is in a Variant, we can pass it anywhere we like. Furthermore, we can make (Let) assignments to it's items (as shown) just like a standard UDT.
Example #2, expanding on example #1 just a bit:
We've added an explicitly declared standard UDT that mirrors the UDT we're creating with the dynamic TypeLib. And we also show how data from a UDT in a Variant can be moved to a standard UDT variable.
Code:
Option Explicit
Private Type TestUdt1
x As Long
y As Single
s As String
o As Object
End Type
Private Sub Form_Load()
' Test of intrinsic types and late-bound objects.
Udt.CreateNewUdtInDynamicTypeLib "TestUdt1", "x", vbLong, "y", vbSingle, "s", vbString, "o", vbObject
Dim vTestUdt1 As Variant
vTestUdt1 = Udt.NewEmptyVariant("TestUdt1")
vTestUdt1.x = 5
vTestUdt1.y = 10
vTestUdt1.s = "asdf"
Set vTestUdt1.o = New StdFont
vTestUdt1.o.Name = "Courier New"
Debug.Print vTestUdt1.x, vTestUdt1.y, vTestUdt1.s, vTestUdt1.o.Name
' Prints out: 5 10 asdf Courier New
Dim myTestUdt1 As TestUdt1
Udt.FromVariant vTestUdt1, VarPtr(myTestUdt1)
' Now, we've copied the Variant version of the UDT into a standard UDT.
' Let's just change Y, for grins.
myTestUdt1.y = 13
Debug.Print myTestUdt1.x, myTestUdt1.y, myTestUdt1.s, myTestUdt1.o.Name
' Prints out: 5 13 asdf Courier New
End Sub
Code:
' In the module's header.
Declare Sub SomeApiCall Lib "some_library" (ByRef MyUdt As TestUdt1)
' In some procedure.
Udt.FromVariant vTestUdt1, VarPtr(myTestUdt1)
Call SomeApiCall(myTestUdt1)
For the next example, let's uninstantiate that StdFont object, and then put it back into the Variant UDT.
Example #3, putting our standard UDT back into a Variant UDT:
Code:
Option Explicit
Private Type TestUdt1
x As Long
y As Single
s As String
o As Object
End Type
Private Sub Form_Load()
' Test of intrinsic types and late-bound objects.
Udt.CreateNewUdtInDynamicTypeLib "TestUdt1", "x", vbLong, "y", vbSingle, "s", vbString, "o", vbObject
Dim vTestUdt1 As Variant
vTestUdt1 = Udt.NewEmptyVariant("TestUdt1")
vTestUdt1.x = 5
vTestUdt1.y = 10
vTestUdt1.s = "asdf"
Set vTestUdt1.o = New StdFont
vTestUdt1.o.Name = "Courier New"
Debug.Print vTestUdt1.x, vTestUdt1.y, vTestUdt1.s, vTestUdt1.o.Name
' Prints out: 5 10 asdf Courier New
Dim myTestUdt1 As TestUdt1
Udt.FromVariant vTestUdt1, VarPtr(myTestUdt1)
' Now, we've copied the Variant version of the UDT into a standard UDT.
' Let's just change Y, for grins.
myTestUdt1.y = 13
Debug.Print myTestUdt1.x, myTestUdt1.y, myTestUdt1.s, myTestUdt1.o.Name
' Prints out: 5 13 asdf Courier New
'vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Set myTestUdt1.o = Nothing
vTestUdt1 = Udt.ToVariant("TestUdt1", VarPtr(myTestUdt1))
Debug.Print vTestUdt1.x, vTestUdt1.y, vTestUdt1.s, TypeName(vTestUdt1.o)
' Prints out: 5 13 asdf Nothing
'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
End Sub
The latest UdtsToVariants.cls, along with a small test project, will always be attached to this post: