Results 1 to 33 of 33

Thread: [RESOLVED] Early Bound Objects and Standard StdCall DLLs

  1. #1

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

    Resolved [RESOLVED] Early Bound Objects and Standard StdCall DLLs

    Do we have a way to return an instantiated object from a standard DLL, and then use it early-bound?

    I'm thinking the answer is "no", but I thought I'd ask anyway. Visions of a dummy-class or a typelib with the object's interface in it are swirling around in my head.

    Also, if this is figured out, I need to make sure the COM object's RefCount goes to zero and uninstantiates when the IDE's stop button is clicked.

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

    Why would I want this? I just think standard DLLs are nicer than ActiveX DLLs in that we don't have to register them nor mess with our manifest to use them in a "SxS" fashion.
    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
    10,909

    Re: Early Bound Objects and Standard StdCall DLLs

    You know? Just thinking about this more, maybe returning a "handle" to the object is the way to go. Then, a set of calls could be developed that use that "handle" to figure out which object we're referring (internally in the DLL).

    That's the way many other things work, such as GDI+, etc.

    However, GDI+ likes you to uninitialize it. I'm wondering if a COM object maintained inside a standard DLL gets uninstantiated if the IDE stop button is clicked. I wouldn't care if Class_Terminate didn't get raised. But I would hope it would get uninstantiated. (I'm guessing that purging the DLL from memory would take care of it, but I'm not positive about that.)
    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
    Lively Member
    Join Date
    Feb 2024
    Posts
    71

    Re: Early Bound Objects and Standard StdCall DLLs

    Do we have a way to return an instantiated object from a standard DLL, and then use it early-bound?
    1. Yes,you can.

    I'm wondering if a COM object maintained inside a standard DLL gets uninstantiated if the IDE stop button is clicked.
    2. If the IDE stop button is clicked,the VB IDE will free all objects and unload all std dlls.


  4. #4
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,295

    Re: Early Bound Objects and Standard StdCall DLLs

    The following does compile for an obj in a separate registered activex obj,

    Code:
    Private Declare Function test Lib "test.dll" () As CFileStream
    Private Sub Form_Load()
        Dim fs As CFileStream
        Set fs = test()
    End Sub
    have not setup the standard dll to test at runtime or a self hosted object without registration.

    vb will have to know about the typelib i doubt you can shortcut both the registry and SXS with this.
    at least to compile it would need to be registered on the dev machine.

    you would need to set binary compatibility for sure, any change in the clsid or interface will be a boom
    or class does not support expected interface errors.

    Might be ok if return type was IUnknown and used through IDispatch/late bound.

    can not recommend std dlls made in vb..

    might work, will be playing with fire which is sometimes fun..wouldnt depend on it

  5. #5
    Angel of Code Niya's Avatar
    Join Date
    Nov 2011
    Posts
    9,017

    Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by Elroy View Post
    Do we have a way to return an instantiated object from a standard DLL, and then use it early-bound?
    The answer is a resounding yes! In fact, one of the primary stated goals of COM is to facilitate interoperability between slightly or fundamentally incompatible systems. As long as both systems agree to adhere to COM contracts they can communicate seamlessly. However, in practice, implementing these contracts is something of a dark art few have been able to master.
    Treeview with NodeAdded/NodesRemoved events | BlinkLabel control | Calculate Permutations | Object Enums | ComboBox with centered items | .Net Internals article(not mine) | Wizard Control | Understanding Multi-Threading | Simple file compression | Demon Arena

    Copy/move files using Windows Shell | I'm not wanted

    C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter

    There's just no reason to use garbage like InputBox. - jmcilhinney

    The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber

  6. #6
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,797

    Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by Elroy View Post
    Do we have a way to return an instantiated object from a standard DLL, and then use it early-bound?
    Early bounding is just a contract (interface). You can even violate the COM rules and return a similar interface with different IID.

    Quote Originally Posted by Elroy View Post
    Why would I want this? I just think standard DLLs are nicer than ActiveX DLLs in that we don't have to register them nor mess with our manifest to use them in a "SxS" fashion.
    You can see some VB6-projects here where you can instantiate objects from VB6-ActiveX DLL without any registration/SxS.

  7. #7
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,622

    Question Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by The trick View Post
    You can see some VB6-projects here where you can instantiate objects from VB6-ActiveX DLL without any registration/SxS.
    Yeah but that only works with late-bound objects. How do you violate the COM contract to make it early-bound?

  8. #8

  9. #9
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,622

    Talking Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by The trick View Post
    No. You can either use a typelib with needed interface (just use ActiveX DLL itself)
    I don't quite understand this, how can you use the typelib from the ActiveX DLL without registration? It doesn't work to declare an object As ClassNameFromDLL without registration. Ah, you mean export the typelib and use it in another project, now I get it...

    or just use a class with the same structure of the methods - VB6 creates the same VTable for similar classes only IID are different. You could return a raw pointer to object and put it to your class interface variable.
    This works great, just tested it. Very, very clever!

  10. #10

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

    Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by The trick View Post
    You can see some VB6-projects here where you can instantiate objects from VB6-ActiveX DLL without any registration/SxS.
    This is good to know. I thought that might be possible. I've just never done it before. Just some swapping of ObjPtr values around (and appreciating our RefCount values, so as to not crash).

    Quote Originally Posted by The trick View Post
    You can see some VB6-projects here where you can instantiate objects from VB6-ActiveX DLL without any registration/SxS.
    And yeah, I've done this before, using some of FireHacker's code (which I believe is a friend of yours). This is probably the simplest way to do this. Just compile my stuff into an Ax.DLL, and then distribute a BAS stub that knows how to instantiate objects from that Ax.DLL if it's in the same folder as my project. That way, I'd be assured that they'd be early-bound. I like it. And that Ax.DLL could be compiled with optimization (even if the parent project wasn't).

    ---------

    I'm clearly getting to the point where I have to be reminded of some of the things I've done in the past.
    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
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,797

    Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by VanGoghGaming View Post
    I don't quite understand this, how can you use the typelib from the ActiveX DLL without registration? It doesn't work to declare an object As ClassNameFromDLL without registration. Ah, you mean export the typelib and use it in another project, now I get it...
    Let me little bit explain.
    For example you have an AX-Dll written in VB6 with the class named CMyClass. You create a Std-EXE project where you want to use this class. You just specify your AX-DLL in the project references which actually loads the embedded typelib from this DLL to get the interfaces/types/etc. definitions. This doesn't require registration of such AX-DLL in end-user machine (in most cases). You can write in your STD-EXE code like:

    Code:
    Dim C as CMyClass
    This doesn't require registration on end-user machine. The only thing requires the registration is:

    Code:
    Set C = New CMyClass
    Because of VB6 hides the class/interface difference it may lead some misunderstands. When you specify a type of an object variable after Dim - it's always an interface whereas the word after New keyword (without Dim) is a CoClass. So you can use any interfaces without registration (mostly, because there are marshalling things in rare cases).

    You can replace the object creation with New keyword to your own object creation which doesn't require registration.

  12. #12
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,622

    Talking Re: Early Bound Objects and Standard StdCall DLLs

    Yes, crystal clear now and much better than moving around the object pointer to a dummy class interface!

    In fact you've already explained this 8 years ago, although the current explanation is much clearer and easier to understand:

    Quote Originally Posted by The trick View Post
    Couple words. You can use early binding as well. If i'm not wrong you can add your DLL to references (or TLB) and use an Interface to working with an object. It doesn't require any dependencies in an end exe (if you don't use New keyword with classes that contains in a Dll).

  13. #13
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,622

    Talking Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by Elroy View Post
    I'm clearly getting to the point where I have to be reminded of some of the things I've done in the past.
    I think this happens to everyone at one point or another. You also had a working version of this in that same 8-year-old thread:

    Quote Originally Posted by Elroy View Post
    Okay, I've now tested and Trick's procedures work flawlessly, and no need for any references to call a VB6 ActiveX.dll. I cut Trick's procedures down to suit my needs, and thought I'd post my version here. I've just got it in a standard BAS module.
    We all need to rewrite TheTrick's code to make it more digestible for our humble needs: RegFree usage of ActiveX DLLs without Manifests

  14. #14

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

    Re: Early Bound Objects and Standard StdCall DLLs

    Already got this all up and running, with the help of this BAS module (that I've extensively used elsewhere).

    And already tested it with my BST work, and it works perfectly.

    Code:
    
    '
    ' For working with ActiveX.dll libraries without registration.
    ' Krivous Anatolii Anatolevich (The trick), 2015, was the one who originally worked this out,
    ' in coordination with FireHacker.
    ' Minor refactoring done by Elroy.
    '
    Option Explicit
    '
    Private Declare Function CLSIDFromString Lib "ole32.dll" (ByVal lpszCLSID As Long, ByRef clsid As GUID) As Long
    Private Declare Function LoadLibraryW Lib "kernel32" (ByVal lpLibFileName As Long) As Long
    Private Declare Function GetModuleHandleW Lib "kernel32" (ByVal lpModuleName As Long) As Long
    Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
    Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
    Private Declare Function DispCallFunc Lib "OleAut32" (ByVal pvInstance As Any, ByVal oVft As Long, ByVal cc As Integer, ByVal vtReturn As Integer, ByVal cActuals As Long, ByRef prgvt As Any, ByRef prgpvarg As Any, ByRef pvargResult As Variant) As Long
    Private Declare Function LoadTypeLibEx Lib "OleAut32" (ByVal szFile As Long, ByVal regkind As Long, ByRef pptlib As IUnknown) As Long
    Private Declare Function CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal length As Long) As Long
    '
    Private Type GUID
        data1 As Long
        data2 As Integer
        data3 As Integer
        data4(7) As Byte
    End Type
    '
    Private iidClsFctr      As GUID
    Private iidUnk          As GUID
    Private isInit          As Boolean
    '
    
    Public Function NewObjectFromActivexDll(ByRef sSpecToDll As String, ByRef sClassName As String) As IUnknown
        Set NewObjectFromActivexDll = CreateObjectEx2(sSpecToDll, sSpecToDll, sClassName)
    End Function
    
    Public Sub UnloadActivexDll(ByRef sSpecToDll As String)
        ' Unload DLL if not used.
        ' Be sure all the objects are uninstantiated first.
        '
        Dim hLib    As Long
        Dim lpAddr  As Long
        Dim ret     As Long
        Dim spot    As Long
        '
        spot = 1
        If isInit Then
            spot = 2
            hLib = GetModuleHandleW(StrPtr(sSpecToDll))
            If hLib <> 0 Then
                spot = 3
                lpAddr = GetProcAddress(hLib, "DllCanUnloadNow")
                If lpAddr <> 0 Then
                    spot = 4
                    ret = DllCanUnloadNow(lpAddr)
                    If ret = 0 Then
                        FreeLibrary hLib
                        Exit Sub
                    End If
                End If
            End If
        End If
    End Sub
    
    '******************************************************************************
    '******************************************************************************
    '******************************************************************************
    '
    ' Private from here down.
    '
    '******************************************************************************
    '******************************************************************************
    '******************************************************************************
    
    Private Function CreateObjectEx2(ByRef pathToDll As String, _
                                     ByRef pathToTLB As String, _
                                     ByRef className As String) As IUnknown
        ' Create object by Name.
        ' The DLL can be used as the TLB with VB6 ActiveX.DLL files.
        '
        Const REGKIND_NONE        As Long = 2&
        Const TKIND_COCLASS       As Long = 5&
        Dim typeLib As IUnknown
        Dim typeInf As IUnknown
        Dim ret     As Long
        Dim pAttr   As Long
        Dim tKind   As Long
        Dim clsid   As GUID
        '
        ret = LoadTypeLibEx(StrPtr(pathToTLB), REGKIND_NONE, typeLib)
        If ret Then
            Err.Raise ret
            Exit Function
        End If
        ret = ITypeLib_FindName(typeLib, className, 0, typeInf, 0, 1)
        If typeInf Is Nothing Then
            Err.Raise &H80040111, , "Class not found in type library"
            Exit Function
        End If
        ITypeInfo_GetTypeAttr typeInf, pAttr
        CopyMemory tKind, ByVal pAttr + &H28, 4&
        If tKind <> TKIND_COCLASS Then
            Err.Raise &H80040111, , "Class not found in type library"
            Exit Function
        End If
        '
        ' Get the clsid.
        CopyMemory clsid, ByVal pAttr, Len(clsid)
        ITypeInfo_ReleaseTypeAttr typeInf, pAttr
        '
        ' Now create the object using the clsid.
        Set CreateObjectEx2 = CreateObjectEx1(pathToDll, clsid)
    End Function
    
    Private Function CreateObjectEx1(ByRef path As String, _
                                     ByRef clsid As GUID) As IUnknown
        ' Create object by CLSID and path.
        '
        Const IID_IClassFactory   As String = "{00000001-0000-0000-C000-000000000046}"
        Const IID_IUnknown        As String = "{00000000-0000-0000-C000-000000000046}"
        Dim hLib    As Long
        Dim lpAddr  As Long
        Dim isLoad  As Boolean
        Dim ret     As Long
        Dim out     As IUnknown
        '
        hLib = GetModuleHandleW(StrPtr(path))
        If hLib = 0 Then
            hLib = LoadLibraryW(StrPtr(path))
            If hLib = 0 Then
                Err.Raise 53, , Error$(53) & " """ & path & """"
                Exit Function
            End If
            isLoad = True
        End If
        lpAddr = GetProcAddress(hLib, "DllGetClassObject")
        If lpAddr = 0 Then
            If isLoad Then FreeLibrary hLib
            Err.Raise 453, , "Can't find dll entry point DllGetClasesObject in """ & path & """"
            Exit Function
        End If
        If Not isInit Then
            CLSIDFromString StrPtr(IID_IClassFactory), iidClsFctr
            CLSIDFromString StrPtr(IID_IUnknown), iidUnk
            isInit = True
        End If
        ret = DllGetClassObject(lpAddr, clsid, iidClsFctr, out)
        If ret <> 0 Then
            If isLoad Then FreeLibrary hLib
            Err.Raise ret
            Exit Function
        End If
        '
        ' Create instance.
        ret = IClassFactory_CreateInstance(out, 0, iidUnk, CreateObjectEx1)
        '
        Set out = Nothing
        If ret Then
            If isLoad Then FreeLibrary hLib
            Err.Raise ret
            Exit Function
        End If
    End Function
    
    Private Function DllGetClassObject(ByVal funcAddr As Long, _
                                       ByRef clsid As GUID, _
                                       ByRef IID As GUID, _
                                       ByRef out As IUnknown) As Long
        ' Call "DllGetClassObject" function using a pointer.
        '
        Dim params(2)   As Variant
        Dim types(2)    As Integer
        Dim List(2)     As Long
        Dim resultCall  As Long
        Dim pIndex      As Long
        Dim pReturn     As Variant
        '
        params(0) = VarPtr(clsid)
        params(1) = VarPtr(IID)
        params(2) = VarPtr(out)
        '
        For pIndex = 0 To UBound(params)
            List(pIndex) = VarPtr(params(pIndex)):   types(pIndex) = VarType(params(pIndex))
        Next
        resultCall = DispCallFunc(0&, funcAddr, 4&, vbLong, 3, types(0), List(0), pReturn)
        If resultCall Then
            Err.Raise 5
            Exit Function
        End If
        DllGetClassObject = pReturn
    End Function
    
    Private Function DllCanUnloadNow(ByVal funcAddr As Long) As Long
        ' Call "DllCanUnloadNow" function using a pointer.
        '
        Dim resultCall  As Long
        Dim pReturn     As Variant
        '
        resultCall = DispCallFunc(0&, funcAddr, 4&, vbLong, 0, ByVal 0&, ByVal 0&, pReturn)
        If resultCall Then
            Err.Raise 5
            Exit Function
        End If
        DllCanUnloadNow = pReturn
    End Function
    
    Private Function IClassFactory_CreateInstance(ByVal obj As IUnknown, _
                                                  ByVal pUnkOuter As Long, _
                                                  ByRef riid As GUID, _
                                                  ByRef out As IUnknown) As Long
        ' Call "IClassFactory:CreateInstance" method.
        '
        Dim params(2)   As Variant
        Dim types(2)    As Integer
        Dim List(2)     As Long
        Dim resultCall  As Long
        Dim pIndex      As Long
        Dim pReturn     As Variant
        '
        params(0) = pUnkOuter
        params(1) = VarPtr(riid)
        params(2) = VarPtr(out)
        '
        For pIndex = 0 To UBound(params)
            List(pIndex) = VarPtr(params(pIndex)):   types(pIndex) = VarType(params(pIndex))
        Next
        resultCall = DispCallFunc(obj, &HC, 4&, vbLong, 3, types(0), List(0), pReturn)
        If resultCall Then
            Err.Raise resultCall
            Exit Function
        End If
        IClassFactory_CreateInstance = pReturn
    End Function
    
    Private Function ITypeLib_FindName(ByVal obj As IUnknown, _
                                       ByRef szNameBuf As String, _
                                       ByVal lHashVal As Long, _
                                       ByRef ppTInfo As IUnknown, _
                                       ByRef rgMemId As Long, _
                                       ByRef pcFound As Integer) As Long
        ' Call "ITypeLib:FindName" method.
        '
        Dim params(4)   As Variant
        Dim types(4)    As Integer
        Dim List(4)     As Long
        Dim resultCall  As Long
        Dim pIndex      As Long
        Dim pReturn     As Variant
        '
        params(0) = StrPtr(szNameBuf)
        params(1) = lHashVal
        params(2) = VarPtr(ppTInfo)
        params(3) = VarPtr(rgMemId)
        params(4) = VarPtr(pcFound)
        '
        For pIndex = 0 To UBound(params)
            List(pIndex) = VarPtr(params(pIndex)):   types(pIndex) = VarType(params(pIndex))
        Next
        resultCall = DispCallFunc(obj, &H2C, 4&, vbLong, 5, types(0), List(0), pReturn)
        If resultCall Then
            Err.Raise resultCall
            Exit Function
        End If
        ITypeLib_FindName = pReturn
    End Function
    
    Private Sub ITypeInfo_GetTypeAttr(ByVal obj As IUnknown, _
                                      ByRef ppTypeAttr As Long)
        ' Call "ITypeInfo:GetTypeAttr" method.
        '
        Dim resultCall  As Long
        Dim pReturn     As Variant
        '
        pReturn = VarPtr(ppTypeAttr)
        resultCall = DispCallFunc(obj, &HC, 4&, vbEmpty, 1, vbLong, VarPtr(pReturn), 0)
        If resultCall Then
            Err.Raise resultCall
            Exit Sub
        End If
    End Sub
    
    Private Sub ITypeInfo_ReleaseTypeAttr(ByVal obj As IUnknown, _
                                          ByVal ppTypeAttr As Long)
        ' Call "ITypeInfo:ReleaseTypeAttr" method.
        '
        Dim resultCall  As Long
        '
        resultCall = DispCallFunc(obj, &H4C, 4&, vbEmpty, 1, vbLong, VarPtr(CVar(ppTypeAttr)), 0)
        If resultCall Then
            Err.Raise resultCall
            Exit Sub
        End If
    End Sub
    
    
    The name of the ActiveX DLL I'm createing is AxBst.DLL. And I am setting a reference to it in the project that uses it, so I can stay early-bound. However, if I was willing to stay late-bound, I wouldn't even need this reference.

    I'm doing speed tests right now to see if this AxBst.DLL is just as fast as keeping all the code in a single project.




    It's trivial to use:

    Code:
    
    Dim BST As BST_LngKey_NoVal     ' This is our actual binary tree.
    Set BST = NewObjectFromActivexDll(App.path & "\AxBst.dll", "BST_LngKey_NoVal")
    
    Last edited by Elroy; May 2nd, 2024 at 09:56 AM.
    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.

  15. #15

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

    Re: Early Bound Objects and Standard StdCall DLLs

    Ohhh, and for your actual AX.DLL, if you intend to actually set a reference to it in your Std-EXE project (to achieve early-binding), be sure to set "Binary Compatibility" (with itself) in the AX.DLL's project. If you don't, you'll have to re-set the reference in the Std-EXE every time you recompile the AX.DLL.

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

    Also, another fun-fact. Since I (somewhat recently) built my new computer, I've been running the VB6 IDE in a non-administrator mode, with UAC remaining on.

    Setup that way, when I compile an AX.DLL, I get an "Error Accessing The System Registry". I just ignore that, and everything works absolutely fine.

    What appears to be happening is, the UAC isn't letting the VB6 IDE register the AX.DLL when it compiles it. But that's fantastic, because I don't want it registered anyway. So, an added benefit to UAC ... I'm keeping my registry cleaner.
    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.

  16. #16

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

    Re: Early Bound Objects and Standard StdCall DLLs

    Ok, I've run my speed-tests, and I'm not thrilled with the results.

    The test is basically taking my BST_LngKey_NoVal.cls, and using it to randomly create a tree (from 1 to 31 nodes), and then randomly deleting those nodes until they're gone ... and then doing all of that 10,000 times.

    If I compile the BST_LngKey_NoVal.cls into an Ax.DLL, and then access it that way, it takes ~2.50 second.

    If I just include the BST_LngKey_NoVal.cls class in my Std-EXE project, and use it directly, it takes ~1.25 seconds.

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

    I'm not sure where the slowdown is happening for the Ax.DLL. Maybe it's not really early-bound? Or maybe re-instantiating the class each of the 10,000 times with the NewObjectFromActivexDll procedure is causing the slowdown?

    Hmmm, I've got a BST.Clear method. I'll try that instead of re-instantiating each time through the loop.
    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.

  17. #17

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

    Re: Early Bound Objects and Standard StdCall DLLs

    Ok, the re-instantiation was definitely a part of it.

    Using BST.Clear (instead of re-instantiating in each iteration of the 10,000 loop), the no-Ax.DLL version still takes ~1.25 second.

    However, the Ax.DLL version now only takes ~1.50 seconds.

    So, instantiating an object with calls to NewObjectFromActivexDll definitely takes longer than instantiating an object from a class in our project. So, we should try to avoid 1,000s of re-instantiations when using AX.DLLs, especially when using them in this un-registered way.

    ----------

    And even if we don't do 1,000s of re-instantiations, it still takes a bit longer to use an Ax.DLL than it does to use a class within our project. But, sometimes, the benefits of an Ax.DLL may be worth it (such as knowing it's compiled with optimizations, even though our main project may not be).

    ----------

    I'm calling this thread resolved.
    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
    Lively Member
    Join Date
    Feb 2024
    Posts
    71

    Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by Elroy View Post
    Ok, I've run my speed-tests, and I'm not thrilled with the results.

    The test is basically taking my BST_LngKey_NoVal.cls, and using it to randomly create a tree (from 1 to 31 nodes), and then randomly deleting those nodes until they're gone ... and then doing all of that 10,000 times.

    If I compile the BST_LngKey_NoVal.cls into an Ax.DLL, and then access it that way, it takes ~2.50 second.

    If I just include the BST_LngKey_NoVal.cls class in my Std-EXE project, and use it directly, it takes ~1.25 seconds.

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

    I'm not sure where the slowdown is happening for the Ax.DLL. Maybe it's not really early-bound? Or maybe re-instantiating the class each of the 10,000 times with the NewObjectFromActivexDll procedure is causing the slowdown?

    Hmmm, I've got a BST.Clear method. I'll try that instead of re-instantiating each time through the loop.
    I think maybe u should create a tree with 10000 nodes to get a reasonable result.

  19. #19

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

    Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by TomCatChina View Post
    I think maybe u should create a tree with 10000 nodes to get a reasonable result.
    Yes TCC, my speed-tests do use the BSTs in ways they're probably not typically used ... creating 1000s of trees. Probably, in most circumstances, just one tree is created, and then it's used to see if items exist or not.

    However, when inserting / deleting items, the same "does item exist" code is executed, so my test are (to a large degree) exercising the same code that would typically be used.

    Things would definitely slow down if I created trees with 10,000 nodes. But I don't believe the number of nodes is where the differences are between using an Ax.DLL versus an in-project class.

    But, I will test. It won't take much for me to do that test.
    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

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

    Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs

    I ran both the Ax.DLL and the in-project-class (all compiled, all optimized).

    I randomly generated a tree from between 5000 and 9999 nodes, and then randomly deleted them all ... and did that 10 times.

    Using the Ax.DLL too ~3.25 seconds.
    Using the in-project class took ~2.50 seconds.

    I tested both several times, and the timing of neither varied more than a couple-hundredths of a second.

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

    So, for me, that shows it's not about instantiating/uninstantiating.

    It's something about calling code in an Ax.DLL versus calling code in an in-project class.

    In the Ax.DLL, I did have to make all of the class's exposed methods as "Public" for me to be able to call them. Whereas they're "Friend" in the in-project class. Something about the differences in those mechanisms is probably the difference.
    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.

  21. #21
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,622

    Talking Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by Elroy View Post
    So, instantiating an object with calls to NewObjectFromActivexDll definitely takes longer than instantiating an object from a class in our project. So, we should try to avoid 1,000s of re-instantiations when using AX.DLLs, especially when using them in this un-registered way.
    The only instantiation code you need for new objects is the call to "IClassFactory_CreateInstance". All the rest can be executed only once at the beginning and the results cached in module-level variables. That should substantially speed up subsequent re-instantiations.

  22. #22

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

    Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by VanGoghGaming View Post
    The only instantiation code you need for new objects is the call to "IClassFactory_CreateInstance". All the rest can be executed only once at the beginning and the results cached in module-level variables. That should substantially speed up subsequent re-instantiations.
    Well, with that last timing test, I just did 10 re-instantiations, and ~7500 node creations and deletions. And it was still about 30% slower when using the Ax.DLL.

    Also, I looked into LoadLibraryW, and it seems that it returns the handle to the "already loaded library" if it's already loaded. So, I don't think there's any harm in the way it's done. Also, Trick is calling GetModuleHandleW as a double-check before calling LoadLibraryW. Strictly speaking, I'm not sure that's necessary, but he uses the return of GetModuleHandleW if it's not zero.

    Done any other way, we might wind up needing to keep a list of our loaded libraries. Why not let the Windows OS do that, since it seems to be doing it anyway.

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

    In my larger primary application, it's entirely possible that I might simultaneously load several Ax.DLL files, so a single initialized flag wouldn't work. And, I'd like to maintain that possibility with this BST Ax.DLL file.
    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.

  23. #23
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,622

    Lightbulb Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs

    Yes, it's good practice to check with "GetModuleHandle" before calling "LoadLibrary". If the library had already been loaded then "LoadLibrary" will not load it again but simply return the handle (same as "GetModuleHandle") and also increment an internal "RefCount" used when calling "FreeLibrary". However we almost never need to call "FreeLibrary" since nowadays there is enough RAM available to keep them loaded until the application is closed (also we need to keep ActiveX DLLs loaded to be able to instantiate objects from them).

    You mentioned the code was slow when trying to instantiate many objects and this can be addressed by using the same instance of "ClassFactory" once obtained instead of starting over each time. Here is the same version of TheTrick's code compacted and commented for easier understanding (sorry I don't have the pretty colors like your code ):

    Code:
    Option Explicit
    
    Private Const DllGetClassObject As String = "DllGetClassObject"
    
    Private Enum ConstantsEnum
        MEMBERID_NIL = -1
        S_OK
        S_FALSE
        REGKIND_NONE
        CC_STDCALL = 4
        PTR_SIZE = 4
    End Enum
    
    Private Enum vtbInterfaceOffsets
        ITypeLib_FindName = 11 * PTR_SIZE
        ITypeInfo_GetTypeAttr = 3 * PTR_SIZE
        ITypeInfo_ReleaseTypeAttr = 19 * PTR_SIZE
        IClassFactory_CreateInstance = 3 * PTR_SIZE
    End Enum
    
    Private Declare Function LoadLibraryW Lib "kernel32" (ByVal lpLibFileName As Long) As Long
    Private Declare Function GetModuleHandleW Lib "kernel32" (ByVal lpModuleName As Long) As Long
    Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
    Private Declare Function DispCallFunc Lib "oleaut32" (ByVal pvInstance As Long, ByVal oVft As Long, ByVal cc As Long, ByVal vtReturn As VbVarType, ByVal cActuals As Long, prgvt As Any, prgpvarg As Any, pvargResult As Variant) As Long
    Private Declare Function LoadTypeLibEx Lib "oleaut32" (ByVal lpszFile As Long, ByVal RegKind As Long, pptLib As IUnknown) As Long
    
    Private IClassFactory As IUnknown, ParamTypes(0 To 10) As Integer, ParamValues(0 To 10) As Long, lParamCount As Long, lpInterface As Long, vParams As Variant, _
            sCurrentLib As String, lpDllGetClassObject As Long, IID_IClassFactory(0 To 1) As Currency, IID_IUnknown(0 To 1) As Currency
    
    Public Sub Main()
    Dim objRegFree1 As MyClass, objRegFree2 As MyClass
        Set objRegFree1 = RegFree(App.Path & "\Bin\MyActiveX.dll", "MyClass")
        Set objRegFree2 = RegFree ' second and subsequent instantiations should be much faster as the ClassFactory is already created
        objRegFree1.CallSomeMethod
        objRegFree2.CallSomeMethod
    End Sub
    
    Private Function RegFree(Optional sLibName As String, Optional sClassName As String, Optional bNewClass As Boolean) As Object
    Dim RegFreeIUnknown As IUnknown, ITypeLib As IUnknown, ITypeInfo As IUnknown, rgMemId As Long, pcFound As Long, lpTypeAttr As Long
        If bNewClass Then Set IClassFactory = Nothing ' Start over and instantiate objects from a new class name or from another ActiveX DLL
        If IClassFactory Is Nothing Then ' Once "IClassFactory" is instantiated we can keep using it to create new objects
            If LoadTypeLibEx(StrPtr(sLibName), REGKIND_NONE, ITypeLib) = S_OK Then ' REGKIND_NONE calls LoadTypeLib without the registration process enabled
                pcFound = 1 ' We want to find only one instance of this class name (there shouldn't be duplicates anyway)
                InvokeObj ITypeLib, ITypeLib_FindName, StrPtr(sClassName), 0&, VarPtr(ITypeInfo), VarPtr(rgMemId), VarPtr(pcFound) ' Search the TypeLib for our class name
                If rgMemId = MEMBERID_NIL Then ' If the class name is found then "rgMemId" will return MEMBERID_NIL
                    If sLibName <> sCurrentLib Then
                        sCurrentLib = sLibName: lpDllGetClassObject = GetModuleHandleW(StrPtr(sCurrentLib)) ' Check if the library had already been loaded
                        If lpDllGetClassObject = 0 Then lpDllGetClassObject = LoadLibraryW(StrPtr(sCurrentLib)) ' If not then we load it
                        lpDllGetClassObject = GetProcAddress(lpDllGetClassObject, DllGetClassObject) ' Get the pointer to the DllGetClassObject function
                        If IID_IClassFactory(1) = 0 Then IID_IClassFactory(0) = 0.0001@: IID_IClassFactory(1) = 504403158265495.5712@: IID_IUnknown(1) = IID_IClassFactory(1) ' These IIDs are very similar so we hold them in "Currency" constants
                    End If
                    InvokeObj ITypeInfo, ITypeInfo_GetTypeAttr, VarPtr(lpTypeAttr) ' The first member of the "TypeAttr" structure is the class GUID so we don't need to CopyMemory its contents
                    If lpTypeAttr Then InvokeObj Nothing, lpDllGetClassObject, lpTypeAttr, VarPtr(IID_IClassFactory(0)), VarPtr(IClassFactory) ' Call DllGetClassObject to retrieve the class object from the DLL object handler
                    InvokeObj ITypeInfo, ITypeInfo_ReleaseTypeAttr, lpTypeAttr ' Release the previously allocated "TypeAttr" structure
                End If
            End If
        End If
        If InvokeObj(IClassFactory, IClassFactory_CreateInstance, 0&, VarPtr(IID_IUnknown(0)), VarPtr(RegFreeIUnknown)) = S_OK Then ' Create an instance of this class
            Set RegFree = RegFreeIUnknown ' Get the IDispatch implementation of this class
        End If
    End Function
    
    Private Function InvokeObj(Interface As IUnknown, vtbOffset As Long, ParamArray ParamsArray() As Variant) As Variant
    Dim lRet As Long
        InvokeObj = S_FALSE: lpInterface = ObjPtr(Interface): vParams = ParamsArray ' Make a copy of the array of parameters to get rid of any VT_BYREF members
        For lParamCount = 0 To UBound(vParams): ParamTypes(lParamCount) = VarType(vParams(lParamCount)): ParamValues(lParamCount) = VarPtr(vParams(lParamCount)): Next lParamCount
        If lpInterface Then ' Call the object's method found at "vtbOffset" in its VTable
            lRet = DispCallFunc(lpInterface, vtbOffset, CC_STDCALL, vbLong, lParamCount, ParamTypes(0), ParamValues(0), InvokeObj)
        ElseIf vtbOffset > 1024 Then ' The object is "Nothing" so here we call a function pointer instead
            lRet = DispCallFunc(lpInterface, vtbOffset, CC_STDCALL, vbLong, lParamCount, ParamTypes(0), ParamValues(0), InvokeObj)
        End If
        If lRet Then Debug.Print Hex$(lRet) ' Display a helpful error code if DispCallFunc was called with an incorrect number or type of parameters (and it didn't crash right away!)
    End Function
    If you look at the "RegFree" function above, all the work of loading the TypeLib, searching the class name and creating the ClassFactory is done only once in the main "If block" and then subsequent objects are instantiated super fast.

    If you need to instantiate other objects from other ActiveX DLLs then simply call "RegFree" with the optional "bNewClass" parameter and it will start over from the initial step. I've commented each line of code as best I could.

  24. #24
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,797

    Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by VanGoghGaming View Post
    If you look at the "RegFree" function above, all the work of loading the TypeLib, searching the class name and creating the ClassFactory is done only once in the main "If block" and then subsequent objects are instantiated super fast.

    If you need to instantiate other objects from other ActiveX DLLs then simply call "RegFree" with the optional "bNewClass" parameter and it will start over from the initial step. I've commented each line of code as best I could.
    Yes, this is good approach. Alternatively you could export a flat function from AxDLL and create the object there (you only have to initialize the project context of DLL(CContextHolder.cls)).

  25. #25
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,622

    Question Re: Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by The trick View Post
    or just use a class with the same structure of the methods - VB6 creates the same VTable for similar classes only IID are different. You could return a raw pointer to object and put it to your class interface variable.
    I've been playing with this idea some more and it generally works fine creating a dummy class with the same structure of methods and then initializing it (with "vbaObjSetAddref") with the pointer from an object loaded "RegFree" from an ActiveX DLL.

    However I've found a case where this doesn't work and that is when the object is declared "WithEvents". Even though the dummy class contains an identical event declaration, "vbaObjSetAddref" will fail silently (the object remains Nothing). I was wondering if this could be resolved somehow?

  26. #26

  27. #27
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,622

    Lightbulb Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs

    I think this is not possible with a dummy class because it seems events are not part of the class interface. Looking at the ActiveX DLL in OleView I see the event is declared in a separate interface called "dispinterface".

    So even though the ActiveX class and the dummy class have the same VTable layout, their "dispinterfaces" will have different IIDs... It looks like events will work only if the ActiveX TypeLib is loaded via the "References" menu in IDE. Then they will also work in the compiled executable when the ActiveX DLL is instantiated "RegFree".

  28. #28
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,797

    Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by VanGoghGaming View Post
    I think this is not possible with a dummy class because it seems events are not part of the class interface. Looking at the ActiveX DLL in OleView I see the event is declared in a separate interface called "dispinterface".

    So even though the ActiveX class and the dummy class have the same VTable layout, their "dispinterfaces" will have different IIDs... It looks like events will work only if the ActiveX TypeLib is loaded via the "References" menu in IDE. Then they will also work in the compiled executable when the ActiveX DLL is instantiated "RegFree".
    It isn't enough to call vbaObjSetAddref to handle the events.

  29. #29
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,622

    Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by The trick View Post
    It isn't enough to call vbaObjSetAddref to handle the events.
    Yeah, I've gathered as much just out of academic curiosity. It seems that trying to handle events without a TypeLib is a lot of hassle. If I understood correctly I'd have to retrieve the IConnectionPoint interface from the RegFree object and then create a light-weight "sink" object that responds to the IID of the event and implement the Invoke method of IDispatch. Doesn't sound like a lot of fun!

  30. #30
    Fanatic Member
    Join Date
    Jun 2016
    Location
    España
    Posts
    630

    Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs

    very interesting waiting for progress

  31. #31
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,622

    Thumbs up Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by yokesee View Post
    very interesting waiting for progress
    Okay, just for my number one fan, yokesee, I've put together a demo project in the CodeBank forum: How to Raise Events from late-bound objects!

    P.S.: There must be a special place in hell for whoever designed the IDispatch interface!

  32. #32
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,454

    Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by VanGoghGaming View Post
    P.S.: There must be a special place in hell for whoever designed the IDispatch interface!
    I'd sign that petition...

    Olaf

  33. #33
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,797

    Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs

    Quote Originally Posted by VanGoghGaming View Post
    Yeah, I've gathered as much just out of academic curiosity. It seems that trying to handle events without a TypeLib is a lot of hassle. If I understood correctly I'd have to retrieve the IConnectionPoint interface from the RegFree object and then create a light-weight "sink" object that responds to the IID of the event and implement the Invoke method of IDispatch. Doesn't sound like a lot of fun!
    I mean other. VB6 already have the ability to hide all the low-level IConnectionPoint things (GetMemEvent / SetMemEvent)

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