A COM interface to the S evaluator

Exposing the R evaluator as a COM interface

While we generally want to avoid clients in different languages having to know the S language, some operations are most easily expressed in S. For example, we may want to initialize the interpreter or establish some initial state. So it can be convenient to provide methods for manipulating the interpreter and evaluating S commands. We provide methods for
  • loading and unloading a package (i.e. the library and detach functions),
  • accessing variables (get and assign),
  • getting the search path and the contents of elements in the search path,
  • calling functions and evaluating expressions (in the form of strings)

To provide these methods, we collect S functions that implement them into a named list. The names will be used as the methods that a client can call. Since all of the functionality is readily available in R directly, we merely have to provide the functions directly or create a simple wrapper to them.
els = 
     set = function(name, value, pos = globalenv(), ...) { 
             assign(name, value, pos = pos); 
     exists = exists,
     evaluate = function(cmd) {
        e = parse(text = cmd)
     library = library,
     detach = detach,
     search = search,
     print = function(x, ...) { if(is.character(x)) x = get(x)
                                print(get(x), ...)
     call = function(name, ...) {do.call(name, list(...))},
     objects = function(name = 1, ...) {objects(name = name, ...)})

Now that we have the definitions of the methods, we need to tell R how to use them. We do this by creating a COM class definition which stores the function list as a prototype with which it can create a new instance of the COM object[1]. The function SCOMFunctionClass is used to create the appropriate COM definition. We also give this the name by which clients can refer to this COM class.
def = SCOMFunctionClass(els, name = "R.Evaluator")
This function generates a UUID for this COM class and stores it in the definition. In man cases, we will want to ensure that this does not change, so we can explicitly create a UUID ourselves and store it in the definition. We do this using the getuuid function in the Ruuid.
def@classId = getuuid("d09c2736-593e-42c2-f899-c3f91d4e19d2")
Now that we have the definition, we need to
  • store this somewhere that R can find it in a different session when the COM object is being reated.
  • add entries to the windows registry to associate the name of the COM object with the class UUID and to associate the UUID with the DLL that is used to create and implement the COM object in R (RDCOMServer.dll).
The function registerCOMClassDef performs these two actions.
At this point, the COM class is available to clients and we are finished developing and publishing it.

A client application can use it quite easily. For example, suppose we are writing code in Python and want to use the R evaluator. We create an instance of the R evaluator in Python using the following code:
 from win32com.client import Dispatch
 R = Dispatch("R.Evaluator")
 use Win32::OLE;
 $R = Win32::OLE->new("R.Evaluator");
Given this object, we can invoke some of the methods it provides to manipulate the state of the R session. For example, we can attach a library, query the search path and get a list of the variables in different elements of the search path.
 print R.search()
 print R.objects("package:base")
 print R.objects(2)

 @s = @{$R->search()};
 print "@s\n";
 @o = @{$R->objects("package:base")};

We can send an S command to the R engine and have it evaluate it and return the result. This can be convenient when it is easy to construct the string.
 m = R.evaluate("matrix(rnorm(400), 40, 10)")
In the case that we have values in Python variables, creating the S command as a string can be cumbersome. Instead, it is easier to use the values directly in R and call a function.
 n = 100
 m = R.call("rnorm", n)

Functions in R

It is also quite easy for us to implement the R evaluator COM class so that it makes all the S functions available as methods. In this case, we would be able to call the function above more naturally as
 m = R.rnorm(n)

[1] In this case, we will only need one COM object representing the R evaluator since there is only one R evaluator!