5

Background

I am creating a VBA function (UDF) called MyUDF(), which wraps CallByName().

I wish to mimic precisely the signature and parametric behavior of CallByName(). Furthermore, MyUDF() must copy its Args() argument to a modular variable ArgsCopy — a Variant array — whose elements are then passed by MyUDF() as further arguments to CallByName().

Don't ask why — it's a long story.

Reference

CallByName() displays in the VBA editor like so

2

and it is described in the documentation like so:

Syntax

CallByName (object, procname, calltype, [args()]_)

The CallByName function syntax has these named arguments:

Part Description
object Required: Variant (Object). The name of the object on which the function will be executed.
procname Required: Variant (String). A string expression containing the name of a property or method of the object.
calltype Required: Constant. A constant of type vbCallType representing the type of procedure being called.
args() Optional: Variant (Array).

It appears that "args()" is actually a ParamArray, rather than a simple Variant array, but without further documentation, I can't be perfectly sure.

Format

My tentative design is of the following form:

' Modular variable.
Private ArgsCopy() As Variant


' Wrapper function.
Public Function MyUDF( _
    ByRef Object As Object, _
    ByRef ProcName As String, _
    CallType As VbCallType, _
    ParamArray Args() As Variant _
)
    ' ...
    
    ' Copy the argument list to the modular variable.
    ArgsCopy = Args
    
    ' ...
    
    ' Pass the arguments (and modular variable) to 'CallByName()'.
    MyUDF = VBA.CallByName( _
        Object := Object, _
        ProcName := ProcName, _
        CallType := CallType, _
        Args := ArgsCopy _
    )
End Function

Displayed Signature

In contrast to CallByName(), MyUDF() displays in the VBA editor like so, and concludes with ParamArray Args() As Variant:

5

Only by changing Args() from a ParamArray to a Variant array (ByRef Args() As Variant) can we make them display identically:

6

However, the latter would clash with the functional behavior described below for CallByName().

Parametric Behavior

Unfortunately, one cannot pass ArgsCopy to Args by name (Args := ArgsCopy), since Args is apparently a ParamArray and would thus accept only the unnamed arguments:

VBA.CallByName( _
    Object, ProcName, CallType, _
    ArgsCopy(0), ArgsCopy(1), ..., ArgsCopy(n) _
)

Note

Please disregard the fact that CallByName() returns a Variant, which may (or may not) be an Object that must be Set. I have already accounted for this in my actual code.

Question

How do I construct MyUDF(), and especially its Args() argument, such that

  1. its signature mimics that of CallByName(), in both the Type and Optionality of its parameters; and
  2. it accurately passes to CallByName() any arbitrary set of arguments listed in Args()?

Ideally, MyUDF() will also

  1. work properly on both Mac and Windows; and
  2. display like CallByName() in the VBA editor:

6

2

This 3rd and 4th criteria are a bonus, but I don't require them.

Suggestions

Visual Basic (VB) suggests that one may pass arguments to its ParamArray as in MyUDF() above: the arguments are elements in an array of the same type as the ParamArray, and this array is supplied as a single argument. However, I have found neither a documented nor an experimental equivalent in VBA.

I did find these three VBA questions on Stack Overflow, but I lack the experience to apply their lessons here.

  1. Passing an array of Arguments to CallByName VBA
  2. Pass array to ParamArray
  3. How to view interface spec from Framework files on Mac OS

Change Method Signature

That first question has a solution, which changes the method signature for CallByName(), such that Args() is a single argument: an Any array.

However, I am unfamiliar with the "Any" type, and the third question (unanswered) makes me doubt this preprocessor "magic" could work on a Mac:

#If VBA7 Or Win64 Then
  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
#Else
  Private Declare Function rtcCallByName Lib "VBE6.DLL" ( _
    ByVal Object As Object, _
    ByVal ProcName As Long, _
    ByVal CallType As VbCallType, _
    ByRef args() As Any, _
    Optional ByVal lcid As Long) As Variant
#End If
Public Function CallWithArgs( _
    ByRef Object As Object, _
    ByRef ProcName As String, _
    CallType As VbCallType, _
    ByRef Args() As Variant _
)
   CallWithArgs = rtcCallByName(Object, ProcName, CallType, Args)
End Function
Greg
  • 3,054
  • 6
  • 27
  • Hi @VBasic2008, thanks for commenting! I'm afraid I already tried the `ParamArray` approach. Unfortunately, it won't pass the value of `ArgsCopy` to the `Args()` argument (a `ParamArray`) of `CallByName()`. When I try to pass it — and `CallByName()` will only accept its `Args()` namelessly — then `CallByName()` simply treats `ArgsCopy` as a _single argument itself_ (with an array type) rather than as a list of multiple arguments. The last element of `ArgsCopy` is then passed as the first argument to the procedure, which is named by `ProcName`. – Greg Nov 27 '22 at 21:18
  • @VBasic2008 In short, I'm trying to pass a `Variant` array _to_ the `ParamArray` in `CallByName()`. I am _not_ trying to pass a `ParamArray` to a `Variant` array. – Greg Nov 27 '22 at 21:21
  • 2
    See [rtcCallByName](https://github.com/cristianbuse/Excel-VBA-ProgressBar/blob/f3323268d9e0a35ea674ab32259a3fcd01104792/src/ProgressBar.cls#L70) declaration from my repo. I pass the whole array in one go. I actually go a step further and make sure any ```ByRef``` flags are preserved (see ```RunObjMethod``` method). If I have time will write an answer here tomorrow. – Cristian Buse Nov 29 '22 at 18:17
  • @CristianBuse Thanks, I'll take a look! Quick question: does it work on **Mac** too? – Greg Nov 29 '22 at 18:28
  • 1
    @Greg Unfortunately, no. For Mac I've build a [piramid](https://github.com/cristianbuse/Excel-VBA-ProgressBar/blob/f3323268d9e0a35ea674ab32259a3fcd01104792/src/ProgressBar.cls#L565) :) – Cristian Buse Nov 29 '22 at 20:58
  • 1
    @CristianBuse Ah, so I noticed. Can we reliably say a `ParamArray` will always have a "reasonable" limit on size, namely `255` elements (for now)? If so, then I can just write a script (in R) to autogenerate the pyramid, for all `MAX_PARAMARRAY_SIZE <- 255` of the VBA `Case`s. We might even design a VBA module with pyramids for both `VBA.CallByName()` and `Application.Run()`, where the code is dynamically autogenerated. – Greg Nov 29 '22 at 21:05
  • 1
    The limit is not 255 parameters. It works with more than that, way more than 1000. The limit is the length of the code line i.e. how many arguments can you squeeze in a line of code even if that line of code is split into multiple text lines with the _ character. A piramid is simply not feasible - it gets ugly and big really quick. It would be nice if ```rtcCallByName``` would work on Mac. – Cristian Buse Nov 29 '22 at 21:24
  • @CristianBuse Ouch! _"It works with more than that, way more than 1000."_ I had misread this thread [here](https://www.mrexcel.com/board/threads/excel-2007-vba-udf-argument-limits.640842/post-3180508), which discussed a limit of 254 arguments on calls to UDFs _within Excel formulae_. – Greg Nov 29 '22 at 22:52
  • I did not realize you were referring to UDF limit. Maybe that's indeed 254. I was referring to the limit for passing arguments to a param array. Still a 'piramid' is too much for 254. – Cristian Buse Nov 29 '22 at 23:09
  • @CristianBuse I just tested this in VBA, for an object `TestObject` with a method `CountArgs(requiredArg As Boolean, ParamArray Args() As Variant)`. **I confirmed what you said**: that `CallByName()` can indeed pass an (essentially) **unlimited number of arguments** to `TestObject.CountArgs()`: `CallByName(TestObject, "CountArgs", VbMethod, 1, 2, 3, ..., 254, 255, 256, ...)`. – Greg Nov 30 '22 at 01:18
  • @CristianBuse For `Mac`, what about self-modifying code? Some function `Caller(..., Args() As Variant)` can dispatch to `CallManyArgs(..., a() As Variant)`; but not before invoking the Sub `Modify(n As Long)`, which [dynamically alters](http://www.cpearson.com/excel/vbe.aspx) the contents of `CallManyArgs()`. So `CallManyArgs()` would start out by hard coding `VBA.CallByName(obj, proc, type, a(0), a(1), a(2), ..., a(n), a(n + 1), ..., a(MAX))`. Then `Modify()` would dynamically inject `)'`, to give us `VBA.CallByName(obj, proc, type, a(0), a(1), a(2), ..., a(n))', a(n + 1), ..., a(MAX))`. – Greg Dec 03 '22 at 21:52
  • @CristianBuse Obviously this solution is suboptimal in performance and is limited. The character count for a call `Fun(0,0,0,...,0,0)`, which passes `n` arguments of `0` value to a Function `Fun(ParamArray Args() As Variant)`, is smaller than the character count to the hard-coded call `CallByName(a(0),a(1),a(2),..., a(n))`. So the `CallByName(...)` call would run afoul of the line limit in VBA, before the original `Fun(...)` would ever do so. And obviously an array passed to `CallManyArgs()` can hold as many as `2147483647` (?) elements, which are _far_ too many to hard code dynamically. – Greg Dec 03 '22 at 21:58
  • 1
    It would seem like there is no good solution for Mac. BTW, I've spent a couple of hours on Thursday to figure out if ```rtcCallByName``` is available on Mac but it's not unfortunately. – Cristian Buse Dec 03 '22 at 22:18
  • @CristianBuse I've just invited you to collaborate on my [**ArgMate**](https://github.com/GregYannes/ArgMate) repo. The **`ArgMate`** module (`ArgMate.bas`) aspires to create a platform-agnostic API that standardizes the passing of arguments via an array: `CallByName()` is standardized as `Exe_Obj()` ("execute on object") and `Application.Run()` is standardized as `Exe_Mcr()` ("execute a macro"). Obviously Mac is limited to pyramids, which are autogenerated in R and inserted into a VBA stub (`Stub.bas`) to yield `ArgMate.bas`; this stub should be the origin of all VBA edits. – Greg Mar 10 '23 at 11:39
  • Thanks! Will have a look in the next few weeks. – Cristian Buse Mar 10 '23 at 16:24
  • Hi @CristianBuse! Any thoughts on [**`ArgMate`**](https://github.com/GregYannes/ArgMate) so far? – Greg Apr 20 '23 at 23:15
  • 1
    Hi @Greg , unfortunately I was swamped in the last few weeks and I haven't properly looked into it yet but I haven't forgot about it. Something to note is that if an error is raised in the called procedure then ```Application.Run``` still raises the error even if you have a preceding ```On Error Resume Next```. I recently found out and was surprised as no doubt you will be if you didn't know already – Cristian Buse Apr 21 '23 at 07:59
  • Thanks @CristianBuse, good to know! I think this consideration, about `Application.Run()`, would apply less to **`ArgMate`** itself, and more to my intended **`Sudo`** repo (which will likely depend on **`ArgMate`**). – Greg Apr 21 '23 at 15:14

0 Answers0