CallByName Function

A post in the REAL Software forums asked if REALbasic has a CallByName function like VB has. After reviewing my answer and the CallByName function in VB, I am presenting a more thorough solution here.

First, REALbasic does not have CallByName. However, it does have Introspection (aka reflection) which can be used to achieve this.

The official function definition in VB is:

CallByName(object As Object, procname As String, calltype As Integer, [args() As Variant])

This is a bit limited as it cannot handle return values. I’d also prefer a more object-oriented syntax. So our REALbasic definitions are:

Object.CallByName(name As String, nameType As CallType, ParamArray args As Variant)
Object.CallByName(name As String, nameType As CallType, ParamArray args As Variant) As Variant

In order for this to be usable on any object, we’ll implement these as extension methods. First we need to create a module to contain our methods. Create a module called CallByNameExtension. This self-contained module will eventually have everything we need to use CallByName in our projects.

Next, we need to create the CallType enumeration. This is used to specify the type of the we want to call. Our options are Method, PropertyGet and PropertySet. On our module, create a CallType enumeration with those three values (Method, PropertyGet, PropertySet).

Now create our two method signatures for CallByName:

Sub CallByName(Extends obj As Object, name As String, nameType As CallType, ParamArray args As Variant)
End Sub
Function CallByName(Extends obj As Object, name As String, nameType As CallType, ParamArray args As Variant) As Variant
End Function

Since both of these methods will actually use very similar code, we will create a private worker method to actually do the call. Create a new private method:

Private Function CallByNameWorker(obj As Object, name As String, nameType As CallType, args() As Variant) As Variant
End Function

Note that we’ve changed the ParamArray to just a simple array. This is because we cannot resend the parameters as a ParamArray.

Let’s add the simple code to the public methods:

Sub CallByName(Extends obj As Object, name As String, nameType As CallType, ParamArray args As Variant)
 
   Call CallByNameWorker(obj, name, nameType, args)
 
End Sub
Function CallByName(Extends obj As Object, name As String, nameType As CallType, ParamArray args As Variant) As Variant
 
  Return CallByNameWorker(obj, name, nameType, args)
 
End Function

As you can see, this code simply calls the worker, saving the return value if appropriate. Since ParamArrays are converted to real arrays, we can just pass that along to the worker as well.

In the worker method we’ll use Introspection to actually call the method or to get/set the value of the property. Here is the code:

Private Function CallByNameWorker(obj As Object, name As String, nameType As CallType, args() As Variant) As Variant
  If obj <> Nil Then
 
    Dim info As Introspection.TypeInfo
    info = Introspection.GetType(obj)
 
    Select Case nameType
    Case CallType.Method
      // Get the names of all the methods on the object
      Dim methods() As Introspection.MethodInfo
      methods = info.GetMethods
 
      // Search for the specified method
      For Each m As Introspection.MethodInfo In methods
        If m.Name = name Then
          // We found it, so call it
          Dim rv As Variant
          rv = m.Invoke(obj, args)
 
          Return rv
        End If
      Next
 
    Case CallType.PropertyGet
      // Get the names of all the properties on the object
      Dim props() as Introspection.PropertyInfo
      props = info.GetProperties
 
      // Search for the specified property
      For Each p As Introspection.PropertyInfo In props
        If p.Name = name Then
          // We found it, so return its value
          Dim value As Variant
          value = p.Value(obj)
 
          Return value
        End If
      Next
 
    Case CallType.PropertySet
      // Get the names of all the properties on the object
      Dim props() as Introspection.PropertyInfo
      props = info.GetProperties
 
      // Search for the specified property
      For Each p As Introspection.PropertyInfo In props
        If p.Name = name Then
          // We found it so set its value to the first argument
          p.Value(obj) = args(0)
        End If
      Next
    End Select
 
  End If
 
Exception e As RuntimeException
  Dim eInfo As Introspection.TypeInfo
  eInfo = Introspection.GetType(e)
 
  Dim eMessage As String
  eMessage = "A " + eInfo.FullName + " occurred."
  If e.Message <> "" Then
    eMessage = eMessage + EndOfLine + "Message: " + e.Message
  End If
 
  MsgBox(eMessage)
End Function

This code has one significant limitation when it comes to calling methods: it cannot handle overloaded methods. If you have multiple methods with the same name, but with different parameters, then this code will not always call the method you expect. In order to fix this we would have to compare the types of each of the supplied arguments with the types of the arguments for each matching method and then call the one where everything matches. This is all possible using Introspection, but I’ll leave it as an exercise for the reader.

Download the project with all the code: CallByName.rbp.zip



One Response to “CallByName Function”

  1. Blueapples says:

    This is really an excellent function. Thanks for the contribution!

Leave a Reply