Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
Dear Lavolpe,
Swapping of vtables of member functions works fine provided we find their correct vtable offset.Regarding
offsets you helped a lot to find out offset of member functions using zProbe for which I am thankful to you.
But when I try to replace vtables of member functions with different protrotypes with different parameters and different return types the application crashes.
Can you provide me
1.a generic example code of how to handle this issue without crashing the application.
2.how to handle different calling conventions _cdecl in VB without crash when I replace the vtable member function with module function. You can help me using my old class_vs_forms_vtable.zip which I am attaching.
It is a standard exe project with member functions having same prototype.
Thanking you once again.
regards,
Jsvenu
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
EDIT1: Hey, going through DispCallFunc has become pretty moot for what I was trying to do. Other far superior alternatives have emerged. So ... you can ignore this, if you like. Also, I haven't seen you around for a few days. I hope all is well. I'll imagine you're enjoying a piƱa colada on some Caribbean beach until I hear otherwise.
Say Keith,
I suspect you've been watching what I'm messing with regarding the Cairo Graphics. I'm just wondering how this DispCallFunc wrapper (for CDECL) would handle a String return. For instance...
...how could we handle that? I scanned the posts in this thread and didn't see anything, although I could have missed it. Also, the "status" is just a Long. It seems that this library has a typedef for just about everything.
Also, at the moment, I can't tell you whether it's ANSI (or ASCII) or Unicode. If it's Unicode, it's probably UTF-8, but that's yet another problem. First, I just need to get it into VB6, and then I'll figure out how to "translate" it to UCS-2-Unicode. I don't think these Cairo functions are anything where speed would be a concern, but there are a handful of them that return Strings.
Thanks,
Elroy
Last edited by Elroy; Aug 17th, 2018 at 08:53 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.
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
@jsvenu - You can't replace vtable functions with different function prototypes.. (different parameter counts or parameter types)
That breaks the interface contract, which every call is expecting.
Every COM Object is required to abide by an agreed upon interface definition.
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
Just FYI: DispCallFunc can handle CDECL, it does more than just StdCall. The convention type is one of the parameters to that API. A helper/thunk may be needed for CDECL callbacks into a VB project, but I don't think you were asking about that.
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
I was looking back into doing some CDECL calls, and tried this out. Surprisingly, it didn't work. The comments mention calling methods by ordinal, but I don't see a way to do that. One of Paul Caton's procedures had a simple solution:
Code:
Private Declare Function GetProcByOrdinal Lib "kernel32" Alias "GetProcAddress" (ByVal hModule As Long, ByVal nOrdinal As Long) As Long
.
.
If Left$(FunctionName, 1) = "#" Then
fPtr = GetProcByOrdinal(hMod, CLng(Mid$(FunctionName, 2))) ' get the function pointer by ordinal #
Else
fPtr = GetProcAddress(hMod, FunctionName) ' get the function pointer (Case-Sensitive)
End If
...
The attachment includes very simple examples of calling DLL functions and calling a COM virtual function.
...
Thanks for providing this code.
Just a word of warning to VBA developers, and other developers that access a VarType function that has the same 'unusual' behaviour that VBA's VarType function has, you may have to use code similar to the following code to properly get each variable's type:
I couldn't tell whether VB6 was affected by these issues. I think the VB6 documentation for VB6's VarType function might be shown here, but I couldn't tell for certain.
EDIT: Have now discovered that official VB6 documentation is hosted by Microsoft here.
Regards.
Last edited by MarkFern; Apr 17th, 2019 at 06:52 AM.
Reason: 1. Coding mistake. 2. Found VB6 documentation.
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
@MarkFern: TypeOf VarType(vParams(pIndex)) Is Object makes no sense. Just use IsObject function like this:
Code:
If IsObject(vParams(pIndex)) Then
vParamType(pIndex) = vbObject
Else
vParamType(pIndex) = VarType(vParams(pIndex))
End If
Note that converting this to a single expression w/ IIf has the side-effect of always evaluating VarType(vParams(pIndex)) which has the side-effect of always calling the default property/method of the object which for Word.Application instances can be an expensive out-of-process call.
VarType is not unique and evaluating default property is a standard behaviour, even built-in Array(oMyInstance1, oMyInstance2, ...) does it and this is very welcome side-effect because when you have an instance of a Recordset for instance and you try to wrap Array(rs!ID, rs!Name, rs!Address) you don't want the ADODB.Fields in this array but only their default Value property. If you pass the naked ADODB.Fields you risk rs.MoveNext actually *changing* the values in the array. Now *that* would be a big surprise!
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
@wqweto: Thanks for your useful reply.
Originally Posted by wqweto
@MarkFern: TypeOf VarType(vParams(pIndex)) Is Object makes no sense...
I made a coding mistake - should have written TypeOf vParams(pIndex) Is Object instead. TypeOf... Is Object works in VBA & VB6 - the VB6 version of the TypeOf operator is documented here. IsObject() could be more efficient though - don't know.
Have now discovered that official VB6 documentation is hosted by Microsoft here however, it seems to be accidentally missing documentation on VB6's VarType function.
Thank you for pointing out the side-effect of IIf. I never thought about this before but of course it makes perfect sense. I just wanted to abbreviate my code more by using the function.
Originally Posted by wqweto
... VarType is not unique and evaluating default property is a standard behaviour, even built-in Array(oMyInstance1, oMyInstance2, ...) does it and this is very welcome side-effect because when you have an instance of a Recordset for instance and you try to wrap Array(rs!ID, rs!Name, rs!Address) you don't want the ADODB.Fields in this array but only their default Value property. ...
I can understand the benefit in regard to code like rs!ID but to me, such behaviour in VarType() is counter-intuitive. It tripped-up myself and probably also @LaVolpe. However, I completely admit that perhaps I don't understand VB & COM well enough to understand the benefits of such functionality. Thank you so much for explaining more about this.
Thanks.
Last edited by MarkFern; Apr 22nd, 2019 at 06:28 AM.
Reason: Made reply more useful by reducing content & by adding some content regarding certain VB6 functions
I was recently made aware that a function I've used from time to time for calling virtual functions of COM objects ... So, I whipped up a 'generic' class that can call both standard DLL functions & COM VTable functions.
...
Variant parameter bug / issue
I have found a problem with using the/a version of @LaVolpe's code available on 23rd May 2019, to call COM object methods that have VARIANT parameters.
Within VB6 & VBA7 (close to what a VB7 would be), the VarType function never returns a value indicating that the passed argument is a non-array Variant value. However, for some COM method calls, the method parameters have to have this type. This appears to trip-up @LaVolpe's code.
You can test this with the Equals method of the Object class of the mscorlib COM library for the .NET framework. You can also test it with the Invoke_2 & Invoke_3 methods of the MethodInfo class of the same library.
If you manually intervene to choose the vbVariant type for the VARIANT parameters, @LaVolpe's code works.
Code to handle issue
I've drawn up VB code to handle such situations which I've attached at the bottom. The code was first written for VBA7 (close to what a VB7 would be) but should also hopefully work under VB6 when the environment is 32 bit. I did try to install VB6 to test the code under VB6, but couldn't figure out how to easily install VB6 under Windows 8.1.
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
Hi all.
When I'm using DispCallFunc with MSXML DLL (any version) I have noticed that typelib declaration of some methods in this library are not returning VT_HRESULT but VT_OBJECT.
For example
DOM.createElement("Root")
Analysing with typelib information the method createElement gives a vartype of 9 (VT_DISPATCH) for the return value.
So I suggest to to call DispCallFunc with return vartype 9 but this is not working. I always get the return error &H80020010 of DispCallFunc. Even if I use some modifications of return type, I do not get an error free call of DispCallFunc.
Obviously using the createElement function returns a IDispatch object reference in VB6.
So my question is, what are the correct settings for DispCallFunc to call createElement withou any errors?
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
Originally Posted by TAPCON
Analysing with typelib information the method createElement gives a vartype of 9 (VT_DISPATCH) for the return value.
Here is the relevant IDL from C:\Windows\SysWOW64\msxml6.dll
Code:
[
odl,
uuid(2933BF81-7B36-11D2-B20E-00C04F983E60),
hidden,
dual,
nonextensible,
oleautomation
]
interface IXMLDOMDocument : IXMLDOMNode {
[id(0x00000026), propget, helpstring("node corresponding to the DOCTYPE")]
HRESULT doctype([out, retval] IXMLDOMDocumentType** documentType);
[id(0x00000027), propget, helpstring("info on this DOM implementation")]
HRESULT implementation([out, retval] IXMLDOMImplementation** impl);
[id(0x00000028), propget, helpstring("the root of the tree")]
HRESULT documentElement([out, retval] IXMLDOMElement** DOMElement);
[id(0x00000028), propputref, helpstring("the root of the tree")]
HRESULT documentElement([in] IXMLDOMElement* DOMElement);
[id(0x00000029), helpstring("create an Element node")]
HRESULT createElement(
[in] BSTR tagName,
[out, retval] IXMLDOMElement** element);
. . .
and apparently createElement *is* returning an HRESULT.
Originally Posted by TAPCON
So I suggest to to call DispCallFunc with return vartype 9 but this is not working. I always get the return error &H80020010 of DispCallFunc. Even if I use some modifications of return type, I do not get an error free call of DispCallFunc.
Obviously using the createElement function returns a IDispatch object reference in VB6.
So my question is, what are the correct settings for DispCallFunc to call createElement withou any errors?
You cannot use DispCallFunc API directly as an errorless version of the built-in CallByName, a version that does not raise any errors as returned in the HRESULT. This would require manually calling IDispatch::Invoke as LaVolpe has shown in previous posts here.
For an errorless direct replacement of the built-in CallByName you can take a look at this DispInvoke function that uses DispCallFunc for the heavy lifting too.
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
HOW TO BIND A VB FUNCTION in a bas to cdecl dll api?
i want to use:
Code:
Public Declare Function sqlite3_exec Lib "sqlite3"(ByVal sqlite3 As Long, ByVal zSql As String) As Long
or
Function sqlite3_exec_vb Lib "sqlite3"(ByVal sqlite3 As Long, ByVal zSql As String) As Long
msgbox "ASM CODE"
end function
Build_CDecl_InVbStdcallFunction GetProcAddress("sqlite3_exec"),addressof sqlite3_exec_vb,args=2
sub Build_CDecl_InVbStdcallFunction(CdeclApiAddress as long ,VBFunctionAddress,Args as long)
dim AsmCode() as byte,ByteLen as long
****code here?
'ByteLen=ByteLen+***
Vblegend.WriteProcessMemory -1, ByVal VBFunctionAddress, AsmCode(0), ByteLen, 0
end sub
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
Hi all,
I've created an activex DLL and use this class to call functions from a standard DLL (one that I don't own, can't look at the source code). It all works fine however when I call my DLL from an ASP page, it is half a second slower than running it through VB6 (IDE or compiled).
I've added timing code to the Universal DLL class and it's this line that's slower:
I pass the DLL an XML string. It then runs several queries and returns an XML string. Has anyone got any ideas why it might be slower?
VB6 program > Calls my activex DLL > Calls standard DLL. Fast, 250ms
VB6 program compiled > Calls my activex DLL > Calls standard DLL. Fast, 250ms
ASP page > Calls my activex DLL > Calls standard DLL. Slow, 750ms
This happens every time. There is no difference in the calls, the same data is being passed from my DLL to the standard DLL in both cases. I can see the same SQL queries are being run and that they take the same amount of time.
I've tried disabling Windows Defender and that hasn't made a difference.
Dim specialsDLLWorker
Set specialsDLLWorker = Server.CreateObject("prjSpecialsDLL.clsWorker")
That seemed to fix the issue with slowness. Not ideal as it would make more sense to have this one in a global variable. I was also mucking around with the threading model and apartment is much faster.
I realise I'm off topic now, but if anyone can point out why global.asa is slower than page scope, please let me know!
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
Originally Posted by bahbahbah
That seemed to fix the issue with slowness. Not ideal as it would make more sense to have this one in a global variable. I was also mucking around with the threading model and apartment is much faster.
It's common knowledge with Classic ASP to keep global objects (in application state) only free-threading capable instances so that these can be used from MTA (i.e. methods can be called by multiple threads/requests simultaneously).
VB6 does not support MTA (cannot compile free-threaded components) so you end up with single-threaded component in MTA which *serializes* access to its properties i.e. each request has to wait in line until all other requests finish working with this global instance so that it get a time slot alotted and can call a method of this global instance -- not kosher!
The same global object problem exists with standard Scripting.Dictionary -- just don't keep instances of these STA components in IIS application state but use IISSample.LookupTable replacement which is a free-threaded component AFAIK.
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
Originally Posted by wqweto
The same global object problem exists with standard Scripting.Dictionary -- just don't keep instances of these STA components in IIS application state but use IISSample.LookupTable replacement which is a free-threaded component AFAIK.
cheers,
</wqw>
Brill, thanks mate. I'll look into the IISSample.LookupTable.
Re: [VB6] Call Functions By Pointer (Universall DLL Calls)
Originally Posted by jsvenu
Dear Lavolpe,
I tried to hide form 2 in standard exe project.It hides if I use tc.hide directly where tc is form2 object. But when I try to hide form2 using CallFunction_COM giving 516 as the address of Hide method
'ie, vtableoffset as per interface _Form which derives from IDispatch located in vb6.olb in vb98 folderwhen we view the typelib using oleview.Hide is the 122th method+3(for IUnknown)+4(for IDispatch)=129*4bytes=516.
the Hide method of the form2 is not called.
Can you clarify me where I am doing wrong.
I am attaching the sample standard exe project.I have used your cUniversalDLLCalls class from prjUniDLLcalls.zip sample.
regards,
jsvenu
Code:
sName = "Hide" ' << change to another known method in the class
does it can get more disp id?
Code:
Dim vv(2) As Long
Dim lDispIDList(2) As Long
Dim S(2) As String
S(0) = "Caption"
S(1) = "aa"
S(2) = "Hide"
vv(0) = StrPtr(S(0))
vv(1) = StrPtr(S(1))
vv(2) = StrPtr(S(2))
Call c.CallFunction_COM(IID_Dispatch, IDispatchIDsOfNames, CR_LONG, CC_STDCALL, VarPtr(aGUID(0)), VarPtr(vv(0)), 1&, 0&, VarPtr(lDispIDList(0)))
i only use this code can hide form2,but pIndex = DispCallFunc(*)<>0 why?
i do GetLastDllErr
do you have a full sample?
The second similar question, is it possible to write the event callback on the VB6 class object, implement the IUnknown interface, and delete all the redundant interfaces in the class?
This way I don't have to create a module with each callback and implement the virtual IUnknown interface.
Last edited by xiaoyao; Jun 6th, 2023 at 01:38 AM.
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.