|
-
May 1st, 2024, 07:12 AM
#1
[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.
-
May 1st, 2024, 07:22 AM
#2
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.
-
May 1st, 2024, 11:28 AM
#3
Lively Member
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.
-
May 1st, 2024, 04:44 PM
#4
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
-
May 1st, 2024, 07:27 PM
#5
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by Elroy
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.
-
May 2nd, 2024, 03:28 AM
#6
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by Elroy
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.
 Originally Posted by Elroy
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.
-
May 2nd, 2024, 03:40 AM
#7
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by The trick
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?
-
May 2nd, 2024, 03:50 AM
#8
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by VanGoghGaming
Yeah but that only works with late-bound objects. How do you violate the COM contract to make it early-bound?
No. You can either use a typelib with needed interface (just use ActiveX DLL itself) 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.
-
May 2nd, 2024, 04:24 AM
#9
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by The trick
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!
Last edited by VanGoghGaming; May 2nd, 2024 at 04:28 AM.
-
May 2nd, 2024, 05:08 AM
#10
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by The trick
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).
 Originally Posted by The trick
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.
-
May 2nd, 2024, 05:10 AM
#11
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by VanGoghGaming
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:
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.
-
May 2nd, 2024, 09:33 AM
#12
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:
 Originally Posted by The trick
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).
-
May 2nd, 2024, 09:43 AM
#13
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by Elroy
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:
 Originally Posted by Elroy
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
-
May 2nd, 2024, 09:49 AM
#14
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.
-
May 2nd, 2024, 10:08 AM
#15
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.
-
May 2nd, 2024, 10:25 AM
#16
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.
-
May 2nd, 2024, 10:32 AM
#17
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.
-
May 2nd, 2024, 11:21 AM
#18
Lively Member
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by Elroy
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.
-
May 2nd, 2024, 02:42 PM
#19
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by TomCatChina
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.
-
May 2nd, 2024, 02:56 PM
#20
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.
-
May 2nd, 2024, 03:08 PM
#21
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by Elroy
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.
-
May 2nd, 2024, 06:41 PM
#22
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by VanGoghGaming
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.
-
May 2nd, 2024, 07:23 PM
#23
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.
Last edited by VanGoghGaming; May 2nd, 2024 at 08:23 PM.
-
May 3rd, 2024, 01:00 AM
#24
Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs
 Originally Posted by VanGoghGaming
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)).
-
Sep 1st, 2024, 06:04 PM
#25
Re: Early Bound Objects and Standard StdCall DLLs
 Originally Posted by The trick
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?
-
Sep 1st, 2024, 11:52 PM
#26
Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs
Make a small demo please.
-
Sep 2nd, 2024, 02:05 AM
#27
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".
-
Sep 2nd, 2024, 04:01 AM
#28
Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs
 Originally Posted by VanGoghGaming
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.
-
Sep 3rd, 2024, 03:13 AM
#29
Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs
 Originally Posted by The trick
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!
-
Sep 3rd, 2024, 03:50 PM
#30
Fanatic Member
Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs
very interesting waiting for progress
-
Sep 4th, 2024, 03:31 AM
#31
Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs
 Originally Posted by yokesee
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!
Last edited by VanGoghGaming; Sep 4th, 2024 at 05:36 AM.
-
Sep 4th, 2024, 06:47 AM
#32
Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs
 Originally Posted by VanGoghGaming
P.S.: There must be a special place in hell for whoever designed the IDispatch interface! 
I'd sign that petition... 
Olaf
-
Sep 4th, 2024, 12:14 PM
#33
Re: [RESOLVED] Early Bound Objects and Standard StdCall DLLs
 Originally Posted by VanGoghGaming
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|