|
-
Dec 1st, 2014, 12:43 PM
#1
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
Bonnie, if this is a Sub from a VB class you want to call, and you know the method's name, far easier:
:: Copy pointer to uninitialized object and call the Object.SubName, remove pointer from that object, done
Now, for a bit of fun, let's do a simple example of calling the object's IDispatch.Invoke method instead
1) Create new class, leave default name & add these 2 methods
Code:
Public Sub TestMe()
MsgBox "Test"
End Sub
Public Function TestMe2() As Long
MsgBox "Test2" & vbNewLine & "vRtn should equal " & ObjPtr(Me)
TestMe2 = ObjPtr(Me)
End Function
2) Within the form add a command button & this sample code. Ensure project also includes the attached cUniversalDLLCalls class
Code:
Private Type DISPPARAMS
rgVarg As Long ' pointer to array of variant agruments
rgDispID As Long ' pointer to array of DISPIDs of named argument
cArgs As Long ' number of arguments
cNamedArgs As Long ' number of named arguments
End Type
' sample of getting the IDataObject GUID
Private Sub Command1_Click()
Const IUnknownRelease As Long = 8&
Const IDispatchIDsOfNames As Long = 20&
Const IDispatchInvoke As Long = 24&
Dim IID_Dispatch As Long, lDispID As Long
Dim aGUID(0 To 3) As Long, sName As String
Dim DP As DISPPARAMS, vRtn As Variant
Dim c As New cUniversalDLLCalls, tc As Class1
Set tc = New Class1
sName = "{00020400-0000-0000-C000-000000000046}" ' GUID for IDispatch
c.CallFunction_DLL "ole32.dll", "IIDFromString", STR_NONE, CR_LONG, CC_STDCALL, StrPtr(sName), VarPtr(aGUID(0))
' ensure object implements IDispatch...
c.CallFunction_COM ObjPtr(tc), 0&, CR_LONG, CC_STDCALL, VarPtr(aGUID(0)), VarPtr(IID_Dispatch)
If IID_Dispatch <> 0& Then
Erase aGUID()
sName = "TestMe2" ' << change to another known method in the class
Call c.CallFunction_COM(IID_Dispatch, IDispatchIDsOfNames, CR_LONG, CC_STDCALL, VarPtr(aGUID(0)), VarPtr(sName), 1&, 0&, VarPtr(lDispID))
If lDispID <> 0 Then
Call c.CallFunction_COM(IID_Dispatch, IDispatchInvoke, CR_LONG, CC_STDCALL, lDispID, VarPtr(aGUID(0)), 0&, 1&, VarPtr(DP), VarPtr(vRtn), 0&, 0&)
Debug.Print "return value if any: "; vRtn
Else
Debug.Print "method ["; sName; "] not found"
End If
' Release the interface at some point. QueryInterface calls AddRef internally
c.CallFunction_COM IID_Dispatch, IUnknownRelease, CR_LONG, CC_STDCALL
End If
End Sub
change sName = "TestMe2" to sName = "TestMe" to test the other sub. Note: that if parameters exist, then this gets more difficult and the DISPPARAMS structure needs to be filled out completely
Edited: If wanting to pursue this further, here's a bit of help with the DISPPARAMS structure
1) declare a variable vArgs() As Variant, size it to the number of parameters the method expects
2) populate it in reverse order and ensure the correct vartype of each param, example:
method's params: (Index As Integer, NewValue As Long)
NewValue would be: vArgs(0) = 15&, CLng(15), just ensure Long vartype
Index would be: vArgs(1) = 1%, CInt(1), just ensure Integer vartype 3) the structure members: DP.cArgs = nrArgs: DP.rgVarg = VarPtr(vArgs(0))
4) The 3rd from last parameter in that IDispatchInvoke sample call above is 1&. That is the constant for DISPATCH_METHOD, aka, vbMethod
So how do we NOT pass an optional parameter to a VB class? Per docs, we must pass Variant of type Error with a specific error code in place of the optional parameter. How that parameter is coded determines whether VB sees it as passed or not passed. The following shows how to NOT provide the final Optional parameter of a method. Remember last parameter is 1st in the array:
Code:
Const DISP_E_PARAMNOTFOUND As Long = &H80020004
vArgs(0) = DISP_E_PARAMNOTFOUND
' tweak to force variant type of Error. Can use CopyMemory API if desired, but...
c.CallFunction_DLL "kernel32.dll", "RtlMoveMemory", STR_NONE, CR_None, CC_STDCALL, _
VarPtr(vArgs(0)), VarPtr(vbError), 2&
' Note: Proper way would be to have VB give us a Variant of Error vartype, i.e.,
' vArgs(0)= CVErr(DISP_E_PARAMNOTFOUND) but that errors
Ok, that was fun. If anyone is asking what's difference between a parameter and a named parameter? See this MSDN link. If you wish to use named parameters/arguments with IDispatch, you have to get the DISPID of each named argument when you made the call to DispatchIDsOfNames, passing a array of names & a return array for the DISPIDs. These are then populated separately in the DISPPARAMS members. Here is a bit more info. Bottom line, more work but doable.
Last but not least, haven't tried messing with methods that take ParamArrays, I'll let someone else play with that if they wish
Last edited by LaVolpe; Dec 1st, 2014 at 06:43 PM.
-
Dec 2nd, 2014, 04:43 AM
#2
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
 Originally Posted by LaVolpe
Bonnie, if this is a Sub from a VB class you want to call, and you know the method's name, far easier:
:: Copy pointer to uninitialized object and call the Object.SubName, remove pointer from that object, done
But, would that work across threads (specifically, from a thread where the VB6 runtime environment hasn't been initialized)?
Anyway, thanks again for your examples and links! I'm sure they'll prove invaluable in helping me figure out a solution to this multi-threading problem.
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)
-
Dec 6th, 2014, 04:02 PM
#3
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
 Originally Posted by Bonnie West
But, would that work across threads (specifically, from a thread where the VB6 runtime environment hasn't been initialized)?
You can initialize COM onto the thread via OleInitialiize or CoInitialize/Ex. Don't know if that helps or not.
-
Dec 6th, 2014, 05:01 PM
#4
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
 Originally Posted by LaVolpe
You can initialize COM onto the thread via OleInitialiize or CoInitialize/Ex. Don't know if that helps or not.
That doesn't help - unless the COM-Class (or -Dll) supports (within its hidden ClassFactory-creation-routine) so called MTAs
(MultiThreaded Apartments) ... VB-generated COM-Dlls do not support this mode - the best you can switch on in our
VB-Compiler (when creating ActiveX-dlls) is "Apartment-Threaded" - which will support at least the creation of COM-
instances on different STAs (within the same Process) - this is due to "legacies" into the (transactional threading)
of DCOM and COM+ hosting-processes, where VB-Classes (VB-generated COM-Dlls) once played a major role.
Such a started STA (which fully initializes its ThreadLocalStorage only, when you e.g. create a VB-Class-instance
on the new Thread in question) runs basically with an "isolated Memory-Allocator", which e.g. creates separate
allocations for all the Public-Variables which are defined in *.bas-Modules inside a given VB-Dll-Project anew -
on each new STA-Thread which sees such a new (first) ClassInstance of a given VB-Dll (one can log that e.g.
with an appropriate ThreadID-writing function in the Dlls Sub Main, which is also jumped into for each STA-thread
anew, on first instantiation of a VBClass from a given Dll on this new given STA-thread...
So the TLS-issues is the main-culprit for the unpredictable behaviour (and crashes) we see, when we want to talk
"across STAs directly" - that's also the reason why we see problems, when VB-defined callbacks are jumped into
from System-APIs which run on different (System-)Threads.
MTAs (which VB doesn't support) are far more robust in this regard - but then you will have to handle Thread-
synchronizing yourself again.
So, to talk across STAs in a safe way, one has to use the System-provided Marshaling-Functions for that:
CoMarshalInterThreadInterfaceInStream -> http://msdn.microsoft.com/en-us/libr...=vs.85%29.aspx
CoGetInterfaceAndReleaseStream -> http://msdn.microsoft.com/en-us/libr...=vs.85%29.aspx
which will ensure a synchronous communication - as well as serialization of any passed Params into complete Data-Copies - with each
Method-call to the other Target-Interface in question.
For a bit more flexibility I've implemented my own Variant-Array-based serialization-routines for the STA-Threading-Support
in vbRichClient5 (which communicates over Pipes, achieving a somewhat better performance than normal COM-marshaling this way).
Olaf
-
Jun 6th, 2023, 02:21 AM
#5
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
 Originally Posted by LaVolpe
Bonnie, if this is a Sub from a VB class you want to call, and you know the method's name, far easier:
:: Copy pointer to uninitialized object and call the Object.SubName, remove pointer from that object, done
1) declare a variable vArgs() As Variant, size it to the number of parameters the method expects
2) populate it in reverse order and ensure the correct vartype of each param, example:
[indent]method's params: (Index As Integer, NewValue As Long)
Code:
Function CallComByMethod(Obj1 As Object, MethodName As String, ParamArray ArgP() As Variant) As Variant
Const IUnknownRelease As Long = 8&
Const IDispatchIDsOfNames As Long = 20&
Const IDispatchInvoke As Long = 24&
Dim IID_Dispatch As Long, lDispID As Long
Dim aGUID(0 To 3) As Long, sName As String
Dim DP As DISPPARAMS, vRtn As Variant
Dim C As New cUniversalDLLCalls
sName = "{00020400-0000-0000-C000-000000000046}" ' GUID for IDispatch
C.CallFunction_DLL "ole32.dll", "IIDFromString", STR_NONE, CR_LONG, CC_STDCALL, StrPtr(sName), VarPtr(aGUID(0))
' ensure object implements IDispatch...
C.CallFunction_COM ObjPtr(Obj1), 0&, CR_LONG, CC_STDCALL, VarPtr(aGUID(0)), VarPtr(IID_Dispatch)
If IID_Dispatch <> 0& Then
Erase aGUID()
Call C.CallFunction_COM(IID_Dispatch, IDispatchIDsOfNames, CR_LONG, CC_STDCALL, VarPtr(aGUID(0)), VarPtr(MethodName), 1&, 0&, VarPtr(lDispID))
If lDispID > 0 Then
Dim Arg() As Variant
Dim i As Long, ub As Long
ub = UBound(ArgP)
If ub <> -1 Then
ReDim Arg(ub)
For i = 0 To ub
'以相反的顺序填充它并确保每个参数的正确 vartype
Arg(i) = ArgP(ub - i)
'Arg(i) = VarPtr(ArgP(ub - i))
Next
DP.rgVarg = VarPtr(ArgP(0))
'DP.rgVarg = VarPtr(Arg(0))
DP.cArgs = ub + 1
End If
Call C.CallFunction_COM(IID_Dispatch, IDispatchInvoke, CR_LONG, CC_STDCALL, lDispID, VarPtr(aGUID(0)), 0&, 1&, VarPtr(DP), VarPtr(vRtn), 0&, 0&)
Debug.Print "return value if any: "; vRtn
CallComByMethod = vRtn
Else
Debug.Print "method ["; sName; "] not found"
End If
' Release the interface at some point. QueryInterface calls AddRef internally
C.CallFunction_COM IID_Dispatch, IUnknownRelease, CR_LONG, CC_STDCALL
End If
End Function
, ParamArray ArgP() As Variant
How about reversing the order of the parameters, because my parameters also return values, so I can't create a variant copy.
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
|