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 =
list(
get=get,
set = function(name, value, pos = globalenv(), ...) {
assign(name, value, pos = pos);
TRUE
},
exists = exists,
evaluate = function(cmd) {
e = parse(text = cmd)
eval(e)
},
library = library,
detach = detach,
search = search,
print = function(x, ...) { if(is.character(x)) x = get(x)
print(get(x), ...)
NULL},
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.
R.library("mva")
print R.search()
print R.objects("package:base")
print R.objects(2)
|
---|
$R->library("mva");
@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)
|
---|
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