Results 1 to 30 of 30

Thread: Dynamic UDT TypeLibs

  1. #1

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Resolved Dynamic UDT TypeLibs

    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.



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

    • 2/10/2023 The initial version released.
    Attached Files Attached Files
    Last edited by Elroy; Feb 12th, 2023 at 12:32 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  2. #2

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Dynamic UDT TypeLibs



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



    Example #4, returning to example #1, but adding one line of code:
    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
    
    
        'vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    
        Udt.SaveDynamicTypeLib
    
        '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    End Sub
    We've just added the Udt.SaveDynamicTypeLib line. As stated earlier, it's not necessary to do this for all of this to work. Also, as it's written, the DynamicUdt.tlb file is written into the App.Path, which may cause problems for some applications. You should only execute this SaveDynamicTypeLib call if you wish to use all of this as a utility to generate TypeLib files for your UDTs.

    I took the generated DynamicUdt.tlb and ran it through Oleview (which isn't necessary at all), and here's the disassembled TypeLib (reformatted just a touch):
    Code:
    [
      uuid(7CF35D85-0158-45D7-8E3E-0E9D492CD2C9),
      version(1.0)
    ]
    library 
    {
        // Forward declare all types defined in this typelib
    
        typedef [uuid(BCB917D3-B188-49B7-9202-B2D3D3392936)]
        struct tagTestUdt1 
        {
            long x;
            single y;
            BSTR s;
            IDispatch* o;
        } TestUdt1;
    };
    For those familiar with TypeLib source code, this should look familiar. For others, maybe not so much.

    The biggest advantage of this is that you could take this DynamicUdt.tlb file (possibly renamed, which wouldn't hurt anything), and use it in any other project. Doing that would obviate any/all need to explicitly declare these UDTs. Their UDT types would just be available to you. Furthermore, any UDT variable declared with these types could be place into a Variant, and passed anywhere you like. Also, if the UDT isn't in a Variant, it'll pass into API calls just fine (even when declared from the type in a TypeLib).

    If the TLB file isn't in the same folder as your VBP file, you may need to register it with the regtlib utility in Windows.



    Example #5, UDTs with arrays (either static or dynamic):

    This is where we can no longer use the CreateNewUdtInDynamicTypeLib call, and we must resort to the StartNewUdt, AddItemToNewUdt, FinishNewUdt calls. The StartNewUdt is easy. Just supply a UDT name and call it. Here is its declaration:
    Code:
    
    Public Sub StartNewUdt(sUdtName As String)
    AddItemToNewUdt is a bit more complex, and here's its declaration:
    Code:
    
    Public Sub AddItemToNewUdt(sItemName As String, ByVal iItemType As VbVarType, _
                               Optional iItemTypeModifier As UdtItemTypeModifiers, _
                               Optional sNestedUdtName As String, _
                               Optional iDimensions As Long, _
                               Optional iLBound1 As Long, Optional iUBound1 As Long, _
                               Optional iLBound2 As Long, Optional iUBound2 As Long, _
                               Optional iLBound3 As Long, Optional iUBound3 As Long, _
                               Optional iLBound4 As Long, Optional iUBound4 As Long, _
                               Optional iLBound5 As Long, Optional iUBound5 As Long, _
                               Optional iLBound6 As Long, Optional iUBound6 As Long)    ' iDimension & the bounds are used ONLY for static arrays.
    We call this once for each item in our UDT, and a different sItemName is specified each time.

    As with CreateNewUdtInDynamicTypeLib the iItemType can be any of the following: vbByte, vbInteger, vbLong, vbSingle, vbDouble, vbCurrency, vbDate, vbString, vbBoolean, vbObject, & vbVariant.
    In addition, iItemType can be vbUserDefinedType, but we'll get to that a bit later.

    We also get a new Enum that can be used when calling AddItemToNewUdt:
    Code:
    
    Public Enum UdtItemTypeModifiers
        vbStaticArray = &H10000     '  | Only one of these two can be specified.
        vbDynamicArray = &H20000    ' /
    End Enum
    This is used to create either static or dynamic arrays for a specific UDT item, and is specified in the iItemTypeModifier argument. If you're using this AddItemToNewUdt approach, and it's not an array, leave this argument blank.

    And finally, when we've added all our UDT's items, we must call FinishNewUdt to finish things up. If this isn't called, bad things will happen. It's analogous to a Select Case with no End Select. However, I don't have control over VB6's syntax checker, so you just have to take care to do this.

    Code:
    
    Option Explicit
    
    Private Sub Form_Load()
    
        ' Test of static and dynamic arrays.
    
        Udt.StartNewUdt "TestUdt2"
            Udt.AddItemToNewUdt "sa", vbLong, vbStaticArray, , 2, 0, 5, 0, 3
            Udt.AddItemToNewUdt "da", vbLong, vbDynamicArray
        Udt.FinishNewUdt
    
        Dim vTestUdt2 As Variant
        vTestUdt2 = Udt.NewEmptyVariant("TestUdt2")
    
        ' Assign some numbers to static array.
        ' Note that these still throw array out of bounds errors if they need to.
    
        vTestUdt2.sa(0, 0) = 100
        vTestUdt2.sa(1, 1) = 111
    
        ' Redim our dynamic array.
        ' These will also throw out of bounds errors, and/or not dimensioned errors if they need to.
    
        ReDim vTestUdt2.da(-2 To 4, 3 To 5)
    
        ' Assign some numbers.
        vTestUdt2.da(-2, 3) = 523
        vTestUdt2.da(4, 5) = 545
    
        ' Show results:
    
        Debug.Print vTestUdt2.sa(0, 0); vTestUdt2.sa(1, 1); vTestUdt2.da(-2, 3); vTestUdt2.da(4, 5)
        ' Output:  100  111  523  545
    
    End Sub
    For grins, I threw in a Udt.SaveDynamicTypeLib line in the above code and disassembled the resulting TLB. Here's what it looks like:
    Code:
    [
      uuid(AAA506AA-20D3-413D-8311-17CF5A4EE17A),
      version(1.0)
    ]
    library 
    {
        // Forward declare all types defined in this typelib
    
        typedef [uuid(61E4CDD1-725D-455E-BED0-98CA62489424)]
        struct tagTestUdt2 
        {
            long sa[6][4];
            SAFEARRAY(long) da;
        } TestUdt2;
    };
    I'm not sure why, but I just think it's super cool that VB6 can do this.



    A Bit More on SaveDynamicTypeLib:

    Let me take a moment to say a bit more about the SaveDynamicTypeLib call. There are three optional arguments to this call:

    1. Optional ByVal sGuid As String
    2. Optional iMajorVersion As Integer = 1
    3. Optional iMinorVersion As Integer = 0

    If you don't supply a GUID, a random one will be generated and supplied. However, if you are actually using this TLB file in another project, you may want to let it generate a GUID the first time, then see what it is and then save it, re-using it if/when you regenerate your TLB file. That way, you will maintain some degree of compatibility. Also, if you're registering your TLB file, it'll use that same GUID in the registry, rather than cluttering up your registry with a bunch of different GUID numbers.

    The iMajorVersion and iMinorVersion numbers are entirely at your discretion. In the disassembled TLB file, you can see version(1.0). To the left of the decimal is major, and to the right is minor.
    Last edited by Elroy; Feb 12th, 2023 at 12:30 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  3. #3

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Dynamic UDT TypeLibs



    Example #6, nested UDTs:

    The following is an example of nesting our TestUdt1 (from example #1) into TestUdt3. I've nested it both as a simple nesting, and also as a static array, just to illustrate how both can be done.

    As with typically declared UDTs, the UDT to be nested must be created before the parent can be created.
    Code:
    
    Option Explicit
    
    Private Sub Form_Load()
    
        ' Create our child UDT to be nested.
    
        Udt.CreateNewUdtInDynamicTypeLib "TestUdt1", "x", vbLong, "y", vbSingle, "s", vbString, "o", vbObject
    
    
        ' Create our parent UDT with TestUdt1 nested (once as non-array, and secondly as static array).
    
        Udt.StartNewUdt "TestUdt3"
            Udt.AddItemToNewUdt "u1", vbUserDefinedType, , "TestUdt1"
            Udt.AddItemToNewUdt "sa1", vbUserDefinedType, vbStaticArray, "TestUdt1", 1, 0, 5
        Udt.FinishNewUdt
    
    
        ' Create a Variant with our TestUdt3, set some values, and print.
    
        Dim v3 As Variant
        v3 = Udt.NewEmptyVariant("TestUdt3")
    
        v3.u1.x = 111
        v3.u1.y = 1.234!
        v3.u1.s = "asdf"
        Set v3.u1.o = New StdFont
        v3.u1.o.Name = "Courier New"
    
        ' We'll just pick on array element 3.
        v3.sa1(3).x = 222
        v3.sa1(3).y = 2.468!
        v3.sa1(3).s = "qwer"
        Set v3.sa1(3).o = New StdFont
        v3.sa1(3).o = "Segoe UI"
    
        Debug.Print v3.u1.x, v3.u1.y, v3.u1.s, v3.u1.o.Name
        ' Output:  111    1.234    asdf    Courier New
    
        Debug.Print v3.sa1(3).x, v3.sa1(3).y, v3.sa1(3).s, v3.sa1(3).o.Name
        ' Output:  222    2.468    qwer    Segoe UI
    
    End Sub
    
    And if we'd used a dynamic array rather than a static array, we'd need to use ReDim before we could actually use it.



    Arguments of the AddItemToNewUdt procedure:

    Here is the list of arguments to the AddItemToNewUdt procedure:

    • sItemName
    • iItemType
    • iItemTypeModifier
    • sNestedUdtName
    • iDimensions
    • iLBound1 & iUBound1 thru iLBound6 & iUBound6

    Only the first two are required, and they are fairly obvious. The iItemType has all the intrinsic variable types allowed (vbByte, vbInteger, vbLong, vbSingle, vbDouble, vbCurrency, vbDate, vbString, vbBoolean, vbObject, & vbVariant), and in addition, there's a new vbUserDefinedType allowed that wasn't allowed in the CreateNewUdtInDynamicTypeLib call. This vbUserDefinedType type is what allows us to nest UDTs, as seen above in example #6.

    The iItemTypeModifier is either vbStaticArray or vbDynamicArray. If we don't wish an array, leave it blank (no passed argument).

    The sNestedUdtName is used only if iItemType=vbUserDefinedType. Otherwise, sNestedUdtName is ignored.

    The iDimensions is used only if iItemTypeModifier=vbStaticArray. Otherwise, iDimensions is ignored. This is how we specify how many dimensions our static array has. Accomodations for up to 6 dimensions are provided. In theory, this could be expanded to more dimensions, but I felt 6 was enough for most typical circumstances. Also, more would require more arguments in the lower-bound and upper-bound specifications. (And just as an FYI, I couldn't use a ParamArray for the bounds because you can't combine "Optional" with "ParamArray".)

    As with iDimensions, the iLBound1 & iUBound1 thru iLBound6 & iUBound6 arguments are only used if iItemTypeModifier=vbStaticArray. Otherwise, they are ignored. Furthermore, only the ones corresponding to how many dimensions you've specified (iDimensions) are actually examined, with others ignored.

    As we can see, in the above example #6, all the arguments were used (with the exception of all the iLbound# & iUBound# arguments, where only the first set was used).



    Return of the FinishNewUdt procedure:

    As with the CreateNewUdtInDynamicTypeLib procedure, the FinishNewUdt procedure returns an IRecordInfo object that's associated with the TypeLib UDT just created. In most cases, you can safely discard this IRecordInfo return (same as for CreateNewUdtInDynamicTypeLib). This will only be used if you wish to call the alternate NewEmptyVariantUsingRecInfo or ToVariantUsingRecInfo procedures. And, the only reason to consider using these alternate procedures is because they're a bit faster than the NewEmptyVariant and ToVariant procedures. The NewEmptyVariant and ToVariant procedures have to do a Collection lookup whereas the NewEmptyVariantUsingRecInfo or ToVariantUsingRecInfo procedures don't. However, if you only have a few UDTs, the Collection lookups should be extremely fast.

    The only time it's worth considering using the NewEmptyVariantUsingRecInfo and/or ToVariantUsingRecInfo procedures is if you're extremely worried about speed.
    Last edited by Elroy; Feb 10th, 2023 at 03:53 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  4. #4

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Dynamic UDT TypeLibs



    And that's pretty much a wrap:

    That's about it. The rest is just combinations and permutations of what's already been outlined.

    And, to say again, I've done quite a bit of testing, and everything seems to work as expected ... including the reference count increase/decrease of objects, and their un-instantiation when those reference counts go to zero.

    To say again, using the IRecordInfo (exposed with the help of Fafalone's oleexp.tlb), this is all little more than code that just piggybacks on what VB6 is already doing under the hood.



    Bullet-point summary:

    • This is all about the UdtsToVariants.cls (with the actual class named "UDT" as seen in the above examples). And it has its VB_PredeclaredId set to True, so there's no need to declare or instantiate it.

    • To use this UdtsToVariants.cls in a project, that project must have a reference set to oleexp.tlb for version 5.4 or later.

    • Using this UDT class, we can dynamically create a TypeLib of our UDTs, and either directly use that TypeLib or save it as a TLB file and then use that file in other projects.

    • If saved, the TLB file is named DynamicUdt.tlb, and will be saved to the App.Path folder. Once saved, this TLB file can be renamed if you like. To be used in another project, it will need to be moved to that project's folder and/or registered (with the regtlib utility).

    • For flat (no arrays, no nesting) UDTs, the CreateNewUdtInDynamicTypeLib is the only call needed.

    • If your UDT contains arrays (either dynamic or static) and/or has nested UDTs, you can use the StartNewUdt, AddItemToNewUdt, FinishNewUdt set of calls to create your UDT.

    • Once UDTs are created in the dynamic TypeLib, the NewEmptyVariant call can be used to easily create new Variant variables with any of your UDTs.

    • Once we have a Variant containing a UDT, we can make assignments (Let ...) to its items just like a standard UDT.

    • If, in addition to creating your UDTs in the dynamic TypeLib, you also create "mirrored" declarations of your UDTs in the standard way, you can use the FromVariant & ToVariant calls to move your data from a Variant containing your UDT into a standard UDT variable, or vice-versa.

    • When in a Variant, your UDTs can be passed anywhere you like, as this was the original impetus for this whole endeavor. You can even put these Variants containing UDTs into a Collection if you so desired. Also, in the above examples, I scoped all the test Variants to the Form_Load procedure. However, these Variants with UDTS can be scoped to any level you like, and they'll work just fine.
    Last edited by Elroy; Feb 10th, 2023 at 06:48 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  5. #5

  6. #6
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,057

    Re: Dynamic UDT TypeLibs

    I wonder if any of the structures I have been manually parsing in vb6 compiled binaries are actually serialized irecord structures. Will have to compare.

  7. #7

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Dynamic UDT TypeLibs

    Quote Originally Posted by The trick View Post
    BTW you can use this method for serializing such UDTs.
    Maybe I'll add a "Serialized" method after this has been out there for a bit. That's not a bad idea.

    Got to finish the documentation first though.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  8. #8
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,673

    Re: Dynamic UDT TypeLibs

    Quote Originally Posted by dz32 View Post
    I wonder if any of the structures I have been manually parsing in vb6 compiled binaries are actually serialized irecord structures. Will have to compare.
    No, they don't.

    Quote Originally Posted by Elroy View Post
    Maybe I'll add a "Serialized" method after this has been out there for a bit. That's not a bad idea.

    Got to finish the documentation first though.
    It isn't serializing it's marshaling - sorry, my mistake. It's another thing. When you serialize an object you save its state and when you deserialize an object you create full copy of previous object. When you marshal an object you save an information to have access to the original object. You have a new cloned object in the first case and you have the reference to the original object in the second one. Although you can serialize non-object types without problems.

  9. #9
    Hyperactive Member
    Join Date
    Jun 2016
    Location
    España
    Posts
    506

    Re: Dynamic UDT TypeLibs

    awesome work.

  10. #10

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Dynamic UDT TypeLibs

    I'd like to make a post that outlines the different options we now have for getting UDTs into Variants:

    • Use the above UdtsToVariants.cls and make a dynamic (runtime) TypeLib, and then use it in the same project. Upside: Easy to use/understand by VB6 programmers. Downside: You must separately create Variant UDTs and standard UDT variables, possibly copying data between them with the methods in UdtsToVariants.cls.

    • Use the above UdtsToVariants.cls, and then save the TLB file, and then register and reference that in another project. Upside: Easy to use/understand by VB6 programmers. Downside: You must futz with a TLB file during development.

    • Just turn your project into an ActiveX.exe with a creatable class, and put your UDTs in this creatable class as Public UDTs. Upside: Fairly easy to use/understand by VB6 programmers. Downside: Your final executable is far from portable, requiring admin rights on first execution so it can register itself, and also possible registry clutter.

    • Create an ActiveX.dll that's used with your project which has a creatable class with your UDTs in it as Public UDTs. Upside: Fairly easy to use/understand by VB6 programmers. Downside: This ActiveX.dll must be distributed with your executable and registered (or setup with SxS, or dynamically loaded).

    • Write an IDL for your UDTs, and then assemble it into a TLB file with MIDL or MKTYPLIB, and then register and reference that in your project. Upside: This was the way things were originally intended to be done. Downsides: Requires knowledge of how to write C-like IDL files, requires knowledge of how to execute either MIDL or MKTYPLIB, and also requires futzing with a TLB file during development.

    • Create a project with your UDTs, and then compile it as an ActiveX.exe, telling it to create "Remote Server Files" which will cause it to create a TLB file when it's compiled. Upside: Easy to create TLB file with VB6. Downside: Creating a TLB file this way will cause it to need to be distributed (and registered) with your final project's executable. And this approach can also cause possible registry clutter.

    When someone asks about this in the future, I'll probably reference this post.

    ------------------------------------

    ADDED: And yes, as VanGough pointed out (and suggested in posts above), if you keep your TLB file with your project's source, and reference it their, you don't absolutely have to register it to use it. In my main project, I tend to keep my TLB files in a sub-folder of my main source code folder. (There are actually many sub-folders there for a variety of purposes.) But again, that last option (creating TLB files with the "Remote Server Files" option), is possibly the worst option in the list because of the various dependencies it wraps into the TLB file.
    Last edited by Elroy; Feb 12th, 2023 at 04:00 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  11. #11
    Frenzied Member VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    1,324

    Thumbs up Re: Dynamic UDT TypeLibs

    As far as I know the TLB does not need to be registered as long as it's present in the same folder as the executable. It just loads it from there on every execution. Very interesting info about the checkbox to produce "Remote Server Files". I just tried it and it does produce a TLB file as well as a VBR file (which is just a text file containing various registry keys describing the classes present in the ActiveX EXE). I don't know what's the purpose of this VBR file since the ActiveX EXE still needs to be run as administrator if you copy it to another location...

    On the other hand a TLB never needs to be distributed along with your executables. It is only needed if you distribute source code. All info in the TLB gets compiled in the executable when you make it.
    Last edited by VanGoghGaming; Feb 12th, 2023 at 01:59 PM.

  12. #12

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Dynamic UDT TypeLibs

    Quote Originally Posted by VanGoghGaming View Post
    As far as I know the TLB does not need to be registered as long as it's present in the same folder as the executable. It just loads it from there on every execution. Very interesting info about the checkbox to produce "Remote Server Files". I just tried it and it does produce a TLB file as well as a VBR file (which is just a text file containing various registry keys describing the classes present in the ActiveX EXE). I don't know what's the purpose of this VBR file since the ActiveX EXE still needs to be run as administrator if you copy it to another location...

    On the other hand a TLB never needs to be distributed along with your executables. It is only needed if you distribute source code. All info in the TLB gets compiled in the executable when you make it.
    If you create a TLB file using the "Remote Server Files" option, it DOES need to be distributed ... test it!
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  13. #13
    Frenzied Member VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    1,324

    Wink Re: Dynamic UDT TypeLibs

    Quote Originally Posted by Elroy View Post
    If you create a TLB file using the "Remote Server Files" option, it DOES need to be distributed ... test it!
    No need to test it, I trust you! And Olaf who said the same thing almost 7 years ago: https://www.vbforums.com/showthread....=1#post5082263

    It's crazy how this forum has the habit of resurfacing subjects already discussed years ago! (and that's not a bad thing, human memory needs constant refreshing!)
    Last edited by VanGoghGaming; Feb 12th, 2023 at 05:04 PM.

  14. #14
    Frenzied Member
    Join Date
    May 2014
    Location
    Kallithea Attikis, Greece
    Posts
    1,289

    Re: Dynamic UDT TypeLibs

    I check today, that we can use the class for UDT changing to make a new if we wish, and I do an experiment: I make a command button and I make there some udt, one of them already created in the form's udt class (as Elroy does) and the second with changed name. So after the call to new udt return the second one, and works, but the typelib (the class) not exist any more. I suspect that the iRecordinfo of variant (which exist on any variant, as the last 4 bytes) keep alive this object.

    This is the example (just use an OLEEXP, I didn't include it).
    Attached Files Attached Files

  15. #15
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,673

    Re: Dynamic UDT TypeLibs

    Quote Originally Posted by georgekar View Post
    I check today, that we can use the class for UDT changing to make a new if we wish, and I do an experiment: I make a command button and I make there some udt, one of them already created in the form's udt class (as Elroy does) and the second with changed name. So after the call to new udt return the second one, and works, but the typelib (the class) not exist any more. I suspect that the iRecordinfo of variant (which exist on any variant, as the last 4 bytes) keep alive this object.

    This is the example (just use an OLEEXP, I didn't include it).
    A COM object has reference counter so it's alive until the last reference is gone.

  16. #16
    Frenzied Member
    Join Date
    May 2014
    Location
    Kallithea Attikis, Greece
    Posts
    1,289

    Re: Dynamic UDT TypeLibs

    Yes, the IrecordInfo is an object and not need the typelib to survive from a return from a function where we make that. The problem will be if the object happen to defined in another process. When we call an object from ActiveX.exe, that object lives in another process and the system use RPC to access it. So a variant returned by that type of object (from a method) should have a iRecordInfo remote or something. I didn't try that.

    Is there any handy way to get from an object the list of puplic variants and enums ?
    Last edited by georgekar; Feb 13th, 2023 at 04:54 PM.

  17. #17

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Dynamic UDT TypeLibs

    Quote Originally Posted by georgekar View Post
    Yes, the IrecordInfo is an object and not need the typelib to survive from a return from a function where we make that. The problem will be if the object happen to defined in another process. When we call an object from ActiveX.exe, that object leave in another process and the system use RPC to access it. So a variant returned by that type of object (from a method) should have a iRecordInfo remote or something. I didn't try that.

    Is there any handy way to get from an object the list of puplic variants and enums ?
    Hi George,

    I certainly don't mind discussions of this stuff, and I'm interested myself. However, if you wouldn't mind, could you please possibly start a new post with this question over in the regular Q&A section? This isn't exactly relevant to the OP of this thread.

    Thanks,
    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  18. #18
    Frenzied Member
    Join Date
    May 2014
    Location
    Kallithea Attikis, Greece
    Posts
    1,289

    Re: Dynamic UDT TypeLibs

    You are right. Sorry.

  19. #19

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Dynamic UDT TypeLibs

    Quote Originally Posted by georgekar View Post
    You are right. Sorry.
    No problem. It just seemed like this discussion might go on for a while.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  20. #20
    Frenzied Member
    Join Date
    May 2014
    Location
    Kallithea Attikis, Greece
    Posts
    1,289

    Re: Dynamic UDT TypeLibs

    Elroy, do you try to pass an UDT as member of UDT? Maybe you have to use the modified class which I provide, which do something simple: It takes a filename using a random name. Also the class can be create any number of objects.
    In my Interpreter I have install a way to read/write fields (just for experiment), but not for using arguments to fields (although trick provide a way, I need some more information, to not allow the user to pass arguments to those fields who didn't have arguments). On way to know that, is when the user make an UDT, so I can handle it by using a json or xml to store the info, temporary until the program of user return to immediate mode (which means the actual program, the environment of M2000 as I named it, still running). So I need to read a class some info for UDT (and also Enum variable types, which for now I have also problem to get a list). For now I can read modules/functions/properties of a class and export a list of arguments, although when I found UDT I can't get the name. I am not using typelib to handle.


    This is my sub for getting info:
    Code:
    Public Function GetAllMembers(mList As FastCollection, obj As Object _
                   ) As Boolean
        Dim IDsp        As IDispatch.IDispatchM2000
        Dim IDsp1        As IDispatch.IDispatchM2000
        Dim riid        As IDispatch.IID
        Dim params      As IDispatch.DISPPARAMS
        Dim Excep       As IDispatch.EXCEPINFO
        Dim mAttr As TYPEATTR
        Set mList = New FastCollection
        Dim ppFuncDesc As Long, fncdsc As FUNCDESC, cFuncs As Long
        Dim ppVarDesc As Long, vardsc As VARDESC
        Dim ParamDesc As TPARAMDESC, hlp As Long, pRefType As Long
        Dim TypeDesc As TTYPEDESC, retval$
        Dim ret As Long, pctinfo As Long, ppTInfo As Long, typeInf As IUnknown
        Dim pAttr   As Long
        Dim tKind   As Long
        Set IDsp = obj
        Dim cFncs As Long, CVars As Long, ttt$
        Dim i As Long
        Dim j As Long
        Dim strNames() As String, strName As String, aName As String, count As Long
        Dim acc As Long, CTX As Long
        
        Const TYPEFLAG_FDUAL = &H40
        Const TYPEFLAG_FPREDECLID = &H8
        '' may have a GET and a LET for same name
        mList.AllowAnyKey
        
        
        ret = IDsp.GetTypeInfo(ByVal 0, ByVal 0, ppTInfo)
        
        If ppTInfo = 0 Or ret <> 0 Then
            If Err Then Err.Clear
            Exit Function
        End If
        If IDsp.GetTypeInfoCount(count) <> &H80004001 Then
        Debug.Print ">>>>>>>>>>", count
        End If
        Set typeInf = ResolveObjPtrNoRef(ppTInfo)
      
        ITypeInfo_GetTypeAttr typeInf, pAttr
        If pAttr = 0 Then Set typeInf = Nothing: Exit Function
        memcpy mAttr, ByVal pAttr, Len(mAttr)
        If (mAttr.wTypeFlags And TYPEFLAG_FPREDECLID) = &H8 Then
            ITypeInfo_ReleaseTypeAttr typeInf, pAttr
            Set typeInf = Nothing
            Exit Function
        End If
        If (mAttr.wTypeFlags And TYPEFLAG_FDUAL) Then
            If mAttr.typekind <> TKIND_DISPATCH Then
                ITypeInfo_GetRefTypeOfImplType typeInf, -1, pRefType
                ITypeInfo_ReleaseTypeAttr typeInf, pAttr
                ITypeInfo_GetRefTypeInfo typeInf, pRefType, ppTInfo
                Set typeInf = ResolveObjPtrNoRef(ppTInfo)
                ITypeInfo_GetTypeAttr typeInf, pAttr
                memcpy mAttr, ByVal pAttr, Len(mAttr)
            End If
        End If
    
    
        If TKIND_DISPATCH = mAttr.typekind Then
        
            cFuncs = mAttr.cFuncs '' mAttr.cVars
    
    
            For j = 0 To mAttr.cFuncs - 1
                ITypeInfo_GetFuncDesc typeInf, j, ppFuncDesc
                CpyMem fncdsc, ByVal ppFuncDesc, Len(fncdsc)
                acc = fncdsc.lprgelemdescParam
                ret = ITypeInfo_GetDocumentation(typeInf, fncdsc.memid, strName, vbNullString, CTX, vbNullString)
                
                mList.AddKey UCase(strName), ""
                Select Case fncdsc.invkind
                Case INVOKE_EVENTFUNC:
                strName = "Event " + strName
                Case INVOKE_FUNC:
                    If fncdsc.elemdesc.vt = 24 Then
                    strName = "Sub " + strName
                    Else
                    strName = "Function " + strName
                    End If
                Case INVOKE_PROPERTY_GET:
                    strName = "Property Get " + strName
                Case INVOKE_PROPERTY_PUT:
                    strName = "Property Let " + strName
                Case INVOKE_PROPERTY_PUTREF:
                    strName = "Property Set " + strName
                End Select
                mList.ToEnd            ' move to last
                ProcTask2 Basestack1
                hlp = fncdsc.cParams
                If hlp > 0 Then
                    cFncs = 0
                    ReDim strNames(fncdsc.cParams + 1) As String
                    aName = vbNullString
                    ret = ITypeInfo_GetDocumentation(typeInf, fncdsc.memid, aName, vbNullString, CTX, vbNullString)
                    ret = ITypeInfo_GetNames(typeInf, fncdsc.memid, strNames(), fncdsc.cParams + 1, cFncs)
                    If Not ret Then
                        strName = strName + "("
                        For i = 1 To hlp
                            If IsBadCodePtr(acc) = 0 Then
                                CopyBytes Len(ParamDesc), VarPtr(ParamDesc), ByVal acc + 8
                                CopyBytes Len(TypeDesc), VarPtr(TypeDesc), ByVal acc
                                
                            End If
                            acc = acc + 16
                            ttt$ = ""
                            retval$ = ""
                            If strNames(i) = "" Then strNames(i) = "Value"
                            If (ParamDesc.wParamFlags And PARAMFLAG_FRETVAL) = &H8 Then
                                retval$ = " as " + stringifyTypeDesc(TypeDesc, typeInf)
                            Else
                                If (ParamDesc.wParamFlags And PARAMFLAG_FIN) = &H1 Then ttt$ = "in "
                                If (ParamDesc.wParamFlags And PARAMFLAG_FOUT) = &H2 Then ttt$ = ttt$ + "out "
                                If i > (hlp - fncdsc.cParamsOpt) And fncdsc.cParamsOpt <> 0 Then
                                    strName = strName + "[" + ttt$ + strNames(i) + " " + stringifyTypeDesc(TypeDesc, typeInf) + "]"
                                Else
                                    If fncdsc.cParamsOpt = 0 And (ParamDesc.wParamFlags And PARAMFLAG_FOPT) > 0 Then
                                        strName = strName + "[" + ttt$ + strNames(i) + " " + stringifyTypeDesc(TypeDesc, typeInf) + "]"
                                    Else
                                        strName = strName + ttt$ + strNames(i) + " " + stringifyTypeDesc(TypeDesc, typeInf)
                                    End If
                                End If
                                If i < hlp Then strName = strName + ", "
                            End If
                        Next i
                        strName = strName + ")"
                    End If
                End If
                
                If retval$ = "" Then
                    If fncdsc.elemdesc.vt = 24 Then
                        mList.Value = strName
                    Else
                        CopyBytes Len(TypeDesc), VarPtr(TypeDesc), VarPtr(fncdsc.elemdesc.pTypeDesc)
                        mList.Value = strName + " as " + stringifyTypeDesc(TypeDesc, typeInf)
                    End If
                Else
                    mList.Value = strName + retval$
                End If
                ITypeInfo_ReleaseFuncDesc typeInf, ppFuncDesc
            Next j
            ReDim strNames(1) As String
        End If
        ITypeInfo_ReleaseTypeAttr typeInf, pAttr
        Set typeInf = Nothing
        Set IDsp = Nothing
        GetAllMembers = True
    End Function
    I use a way to call a function on an interface by using offset of vtable. So ITypeInfo_GetTypeAttr is a call on &HC offset for that object (if the obj is a ITypeInfo)
    See the double Varptr(), pReturn need one, but because point to ppTypeAttr need another from ppTypeAttr.
    I found this way from modUnregCOM from Trick (2015)

    Code:
    Public Sub ITypeInfo_GetTypeAttr( _
                ByVal obj As IUnknown, _
                ByRef ppTypeAttr As Long)   
        Dim resultCall  As Long
        Dim pReturn     As Variant
        ppTypeAttr = 0
        pReturn = VarPtr(ppTypeAttr)
        resultCall = DispCallFunc(obj, &HC, CC_STDCALL, vbEmpty, 1, vbLong, VarPtr(pReturn), 0)
        If ppTypeAttr = 0 Then Exit Sub
        If resultCall Then Err.Raise resultCall
    End Sub

  21. #21

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Dynamic UDT TypeLibs

    georgekar,

    I'm not sure I really understand your question.

    If you were to use my approach (showed to me by The Trick) seen above in that UdtsToVariants.cls code, you could certainly build all your UDT TypeLibs. Furthermore, you could explicitly save all the UDT names and the UDT item names. That would save you needing to dig them back out of the TypeLib, but I'm not sure if that's viable for you or not.

    I'm not using any of the UDT-in-Public-Class tricks that The Trick has been talking about. Rather, I'm directly using the CreateTypeLib2 function.

    Not sure if that helps or not.

    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  22. #22
    Frenzied Member
    Join Date
    May 2014
    Location
    Kallithea Attikis, Greece
    Posts
    1,289

    Re: Dynamic UDT TypeLibs

    I found information about passing arrays of UDT to c++ external functions:
    https://jeffpar.github.io/kbarchive/kb/171/Q171583/

    There is a list here https://jeffpar.github.io/kbarchive/id/vbwin/
    which have a lot of issues
    This is for not process UDT from late binding objects which return UDT
    https://jeffpar.github.io/kbarchive/kb/184/Q184898/
    As I think about, when say about early binding, say that the analogue with the use of DISPID for functions but for UDT fields, because the problem is at the compiling code where the field name can't be known so the compiler do not make a speculation and just call the way Trick show before, the way I use to read fields in M2000 using vbaVarLateMemCallLdRf, which is latebound, and may don't exist so I have to handle the error. I think VB6 to avoid to check for error, check if the UDT type is known only when assigning to a variable UDT.


    This is for activeX.exe, problems for UDT in Windows NT (maybe for later windows)
    https://jeffpar.github.io/kbarchive/kb/187/Q187922/

    You can't use Erase to erase a static Array in Variant UDT (you can in a variable which have an UDT):
    https://jeffpar.github.io/kbarchive/kb/193/Q193019/

    For Each to loop on a Variant containing array of UDT.
    https://jeffpar.github.io/kbarchive/kb/193/Q193261/


    Intresting HOWTO create DLL (c++) and typedef for UDT with variable length string for calling from VB6.
    https://jeffpar.github.io/kbarchive/kb/194/Q194609/

    Bug VB6 crash when get a UDT from c++ which have enum type field, but not crash when that field turn to Long. We can get an UDT from c++ without using typelib (we get UDT from functions from a c++ library)
    https://jeffpar.github.io/kbarchive/kb/221/Q221101/

    About padding (for Vb5, Vb6 & C)
    https://jeffpar.github.io/kbarchive/kb/249/Q249962/


    "A circular dependency compilation error can occur when a User Defined Type (UDT)
    is defined in a class module that implements another base class interface, and
    the base class module has references to the UDT."
    https://jeffpar.github.io/kbarchive/kb/255/Q255757/


    "If you copy a nested user defined type (UDT) with a fixed-length String array to
    a fixed-length String by using a RtlMoveMemory API call, you may not get the
    right result."
    https://jeffpar.github.io/kbarchive/kb/262/Q262667/


    >>>>>When you open a project and do anything that uses IntelliSense, you receive a
    "permission denied" error message when you try to recompile the project. The
    project defines a public User Defined Type (UDT) that it uses as a parameter to
    a public function, and binary compatibility is set.
    https://jeffpar.github.io/kbarchive/kb/282/Q282233/

  23. #23
    Frenzied Member
    Join Date
    May 2014
    Location
    Kallithea Attikis, Greece
    Posts
    1,289

    Re: Dynamic UDT TypeLibs

    My Interest is not to have UDT for fun, but for using them calling external functions/objects from activeX.dll (the most). Because I have the Group class which is like UDT (with nested UDT) but have methods so it is a kind of object, and we can get copies like udt or pointers to use them as objects. Also I have a "structure" type, like c types, which make a special "typelib" for using buffers of memory assigning types at specific offsets, so this is more like a udt. The difference is that it isn't a UDT which may have Arrays of type SafeArray.
    So I am in a stage of thinking what is the most "clever" to do with UDT. I am not in a hurry although. So I am waiting to think the best solution, doing experiments and reading this forum (always).

  24. #24
    PowerPoster
    Join Date
    Jan 2020
    Posts
    3,746

    Re: Dynamic UDT TypeLibs

    when i add DynamicUdt.tlb to new vb6 project,ide Crashed, suggesting that the TLB has no name.

    Additional questions:
    It will be more convenient if you can directly input the structure code of VB6, automatically parse and identify and create TLB.


    Is it possible to create a TLB file with code, and then add a CLASS in it, and wrap CLASS1 of VB6 into a TLB?
    The tools I've tried before just create a TLB file of the user structure

    Code:
        moCreateTLB.SetGuid uGuid
        moCreateTLB.SetVersion iMajorVersion, iMinorVersion
        If TlbName = "" Then TlbName = "ABC_UserDataType"
        moCreateTLB.SetName TlbName
    Last edited by xiaoyao; Jun 21st, 2023 at 05:29 PM.

  25. #25
    PowerPoster
    Join Date
    Jan 2020
    Posts
    3,746

    Re: Dynamic UDT TypeLibs

    Quote Originally Posted by georgekar View Post
    My Interest is not to have UDT for fun, but for using them calling external functions/objects from activeX.dll (the most). Because I have the Group class which is like UDT (with nested UDT) but have methods so it is a kind of object, and we can get copies like udt or pointers to use them as objects. Also I have a "structure" type, like c types, which make a special "typelib" for using buffers of memory assigning types at specific offsets, so this is more like a udt. The difference is that it isn't a UDT which may have Arrays of type SafeArray.
    So I am in a stage of thinking what is the most "clever" to do with UDT. I am not in a hurry although. So I am waiting to think the best solution, doing experiments and reading this forum (always).
    It would be great if you can provide the complete code for testing, upload a small project

  26. #26
    PowerPoster
    Join Date
    Jan 2020
    Posts
    3,746

    Re: Dynamic UDT TypeLibs

    Quote Originally Posted by Elroy View Post
    Dynamic UDT TypeLibs


    First and foremost, a huge thanks goes out to [U][URL="https://www.vbforums.com/member.php?246405-The-trick"]The The latest UdtsToVariants.cls, along with a small test project, will always be attached to this post:[
    Code:
        Dim uNested As UdtNest
        Udt.FromVariant UdtNested, VarPtr(uNested) ' Ok, we should now have a copy of UdtNested.
    can we use bind varaint point ,not copy data?FromVariant

    BindVariantPoint

  27. #27
    PowerPoster
    Join Date
    Jan 2020
    Posts
    3,746

    Re: Dynamic UDT TypeLibs

    Quote Originally Posted by VanGoghGaming View Post
    No need to test it, I trust you! And Olaf who said the same thing almost 7 years ago: https://www.vbforums.com/showthread....=1#post5082263

    It's crazy how this forum has the habit of resurfacing subjects already discussed years ago! (and that's not a bad thing, human memory needs constant refreshing!)
    If you can use a pointer, such as the address of the structure variable A in the module, the variable B in a process passed to another class retrieves the original structure, and directly manipulates the properties of the structure.
    In this way, the data of A and B are the same, and they are modified synchronously, which is convenient.

  28. #28
    PowerPoster
    Join Date
    Jan 2020
    Posts
    3,746

    Re: Dynamic UDT TypeLibs

    i have a test ,put user dtata type to Variant argument:

    Code:
     Dim vv As Variant
        vv = DataTypeToVV(VarPtr(U1))
        
        Call SubInClass1(vv)
        
        vvToDataType vv, VarPtr(U1)
    Code:
    Private Type UserInfo
        ID As Long
        v As Byte
        UserName As String
    End Type
    
    
    Private Sub Form_Load()
    IntDataType
    End Sub
    
    Sub IntDataType()
        Udt.SetTlbFile
        Udt.CreateNewUdtInDynamicTypeLib "UserInfo2", "ID", vbLong, "v", vbByte, "UserName", vbString
    End Sub
    Function DataTypeToVV(DataTypePtr As Long) As Variant
        DataTypeToVV = Udt.ToVariant("UserInfo2", DataTypePtr)
    End Function
    Sub vvToDataType(vv As Variant, DataTypePtr As Long)
        Udt.FromVariant vv, DataTypePtr
    End Sub
    
    Private Sub SubInClass1(vv As Variant)
        'Copy Data tO U2
        Dim U2 As UserInfo
        Udt.FromVariant vv, VarPtr(U2)
        'change Data
        U2.v = U2.v + 1
        U2.UserName = U2.UserName & "_NEW"
        'callback ,return new data '传回
        vv = Udt.ToVariant("UserInfo2", VarPtr(U2))
    End Sub
    
    
    Private Sub Command1_Click()
        Dim U1 As UserInfo
    
        U1.ID = 135
        U1.v = 1
        U1.UserName = "TOM"
    
        MsgBox "First Data:" & U1.ID & "," & U1.v & "," & U1.UserName
        
        Dim vv As Variant
        vv = DataTypeToVV(VarPtr(U1))
        
        Call SubInClass1(vv)
        
        vvToDataType vv, VarPtr(U1)
        
        MsgBox "New Data u1=" & U1.ID & "," & U1.v & "," & U1.UserName
    End Sub

  29. #29
    PowerPoster
    Join Date
    Jan 2020
    Posts
    3,746

    Re: Dynamic UDT TypeLibs

    I wrote a similar function, you can learn and refer to it together

    vb6 Pass structure data without copying,DataType by Pointer-VBForums
    https://www.vbforums.com/showthread....ype-by-Pointer

  30. #30
    Hyperactive Member
    Join Date
    Jan 2018
    Posts
    264

    Re: Dynamic UDT TypeLibs

    Very nice work Elroy. I would have loved to have this 20 years ago when I was dealing with sharing and serializing public UDTs between ActiveX exes, regular exes, and ActiveX dlls.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width