Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I pass an array of arguments ByRef with CallByName?

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?

like image 525
maracuja Avatar asked May 25 '16 08:05

maracuja


2 Answers

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.

like image 54
Comintern Avatar answered Nov 19 '22 08:11

Comintern


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

like image 1
Greedo Avatar answered Nov 19 '22 08:11

Greedo