Quantcast
Channel: VBForums - CodeBank - Visual Basic 6 and earlier
Viewing all articles
Browse latest Browse all 1448

Dynamic UDT TypeLibs

$
0
0
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:

  1. 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).

  2. 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

Using this dynamic TypeLib code, you can't do that. However, if we're willing to do it as late-binding, it will all work just fine:
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


The only two calls I made into the UDT object were CreateNewUdtInDynamicTypeLib and NewEmptyVariant. Let me talk about CreateNewUdtInDynamicTypeLib a bit. Here are its arguments:
  • 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


This comes in particularly handy if you wish to use this UDTs-in-Variants, but you also wish to make API calls with the UDTs. If an API needs a UDT, you can't just pass the Variant containing the UDT. But you can just do something like the following:
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)

We've added an explicitly declared UDT (TestUdt1), and we've also copied our Variant UDT into a non-Variant UDT with the same items. This non-Variant UDT is a copy of the original Variant UDT. All intrinsics are copied, and the reference-count of the object is incremented.

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

When we put it back, we stepped on the last instantiated copy of our StdFont, so it's reference count went to zero, and it was uninstantiated. That's just the nuances of dealing with objects, and I won't go into that in detail here. I did check and test, and object are being correctly handled in all of this. Truth be told, it's all just piggy-backing onto the way VB6 already works. So, object arrays or nested objects should all work just fine (and I did test and found no problems).



The latest UdtsToVariants.cls, along with a small test project, will always be attached to this post:
Attached Files

Viewing all articles
Browse latest Browse all 1448

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>