I am currently using CallByName to dynamically call methods. There are several methods which I pick up daily from a table in server along with the arguments. For this reason, I send an array of the arguments to CallByName rather than a param array as I don't know the number of arguments until runtime. Given CallByName expects a paramarray I use a private declare function to bypass the VBA Type definition.
Private Declare PtrSafe Function rtcCallByName Lib "VBE7.DLL" ( _
ByVal Object As Object, _
ByVal ProcName As LongPtr, _
ByVal CallType As VbCallType, _
ByRef Args() As Any, _
Optional ByVal lcid As Long) As Variant
Public Function CallByNameMethod(Object As Object, ProcName As String, ByRef Args () As Variant)
AssignResult CallByNameMethod, rtcCallByName(Object, StrPtr(ProcName), VbMethod, Args)
End Function
Private Sub AssignResult(target, Result)
If VBA.IsObject(Result) Then Set target = Result Else target = Result
End Sub
This works when I pass an object where the method changes its underlying properties. However, there are some methods where I pass an object and a method which changes the values of the passed arguments. For example, I pass an array with the following arguments
Dim Name as String, Value1 as double, Value2 as double, Value3 as double
Dim Array(3) as Variant
String = "Name"
Value1 = 0
Value2 = 0
Value3 = 0
Array(0) = Name
Array(1) = Value1
Array(2) = Value2
Array(3) = Value3
When I Pass that array, the method just returns the array back with the same values, but I am expecting double type values for Array(1), Array(2), Array(3). Any ideas?
The the first clue to the answer lies in the function declaration for rtcCallByName (pulled from the exports table of vbe7.dll):
function CallByName(Object: IDispatch; ProcName: BSTR; CallType: VbCallType; Args: ^SafeArray; out lcid: I4): Variant; stdcall;
Note that Args
is declared as a pointer to a SafeArray
, but is not declared as an out
parameter. This means that the COM contract for the function is basically saying that if you pass a ParamArray
the only guarantee that it makes is that it won't change pointer to the ParamArray
itself. The ByRef
in the Declare Function
only indicates that you are passing a pointer.
As for the values inside the ParamArray
, there really isn't much Microsoft documentation I can dig up specific to VBA, but the VB.NET version of the documentation gives the second clue:
A ParamArray parameter is always declared using ByVal (Visual Basic).
In the context of CallByName
, this make perfect sense. The rtcCallByName
function itself doesn't (and can't) know which of the parameters for the called method are themselves declared ByRef
or ByVal
, so it has to assume that it can't change them.
As far as implementations to work around this limitation, I'd suggest either refactoring to eliminate return values that are passed ByRef
in code called by CallByName
or wrapping the needed functionality in a class.
Turns out this is actually possible, see the RunMacro
method in this post:
https://codereview.stackexchange.com/q/273741/146810
Which copies the paramarray into a variant array to pass to rtcCallByName
whilst preserving the ByRef
flag of the variants, using this CloneParamArray
method:
https://github.com/cristianbuse/VBA-MemoryTools/blob/f01b0818930fb1708caaf5fc99812abdeaa9f1df/src/LibMemory.bas#L890
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With