The rSharp package is a low-level interoperability bridge between R and the .NET runtime. It offers access to .NET libraries from R, allowing to create .NET objects, access their fields, and call their methods.
rSharp uses R’s native interface to C++ which then calls the .NET runtime. To make working with .NET objects intuitive, rSharp utilizes the R6 object-oriented approach. This article will guide you through the basic usage of the package, starting from loading an assembly to calling methods and accessing fields.
Data type conversion
Basic data types
rSharp allows to call public and static methods implemented in .NET assemblies and to pass data to these methods. In return, calls will often result in some data being returned.
Where there is an obvious and natural conversion between R and .NET
data types, R values can be easily passed as is, and the return value
can be interpreted as a native R type. Most of the basic modes in R
(character
,numeric
,integer
,
logical
, and their vectors) have equivalents in .NET.
Object pointers and the NetObject
class
It is common that .NET method returns another object that can not be
directly converted to a native R type. In such cases, the method returns
an external pointer to the .NET object. The NetObject
class
is used to represent such pointers in R. The NetObject
class is an R6 object that wraps the external pointer and provides
methods to interact with the .NET object with semantics familiar for the
object-oriented programming. In most cases, the user would prefer to
interact with these objects through the NetObject
. If, for
any reason, the user needs to access the raw pointer, it can be done by
accessing the field $pointer
of the R6 object.
NetObject
instances can be passed to .NET methods as
arguments.
Loading an assembly
To start working with a .NET assembly, you need to load it into the R
session. This is done using the rSharp::loadAssembly
function. In this example, we will use the library
rSharp.Examples.dll
provided with this package.
library(rSharp)
assembly <- loadAssembly(system.file("extdata", "rSharp.Examples.dll", package = "rSharp"))
We can get the list of all loaded assemblies using the
getLoadedAssemblies
function:
getLoadedAssemblies()
#> [1] "System.Private.CoreLib"
#> [2] "ClrFacade"
#> [3] "System.Runtime"
#> [4] "System.Runtime.InteropServices"
#> [5] "System.Console"
#> [6] "System.Linq"
#> [7] "System.Collections"
#> [8] "RDotNet"
#> [9] "netstandard"
#> [10] "System.Linq.Expressions"
#> [11] "DynamicInterop"
#> [12] "Microsoft.Win32.Primitives"
#> [13] "System.Diagnostics.Process"
#> [14] "System.ComponentModel.Primitives"
#> [15] "System.Threading"
#> [16] "System.IO.Pipes"
#> [17] "System.Net.Primitives"
#> [18] "System.Net.Sockets"
#> [19] "System.Threading.ThreadPool"
#> [20] "System.Threading.Thread"
#> [21] "System.Collections.Concurrent"
#> [22] "System.Diagnostics.Tracing"
#> [23] "System.Memory"
#> [24] "System.Runtime.Numerics"
#> [25] "rSharp.Examples"
#> [26] "Microsoft.CSharp"
#> [27] "System.Reflection.Emit.ILGeneration"
#> [28] "System.Reflection.Emit.Lightweight"
#> [29] "System.Reflection.Primitives"
#> [30] "Anonymously Hosted DynamicMethods Assembly"
# Check if a specific assembly is loaded
isAssemblyLoaded("rSharp.Examples")
#> [1] TRUE
Working with static members
Static members of a .NET class can be accessed without creating an instance of the class.
The rSharp.Examples
library implements a class
SampleStaticClass
with different static members. To get an
overview of the available static members of a class, you can use the
getStaticMembers
function:
getStaticMembers("rSharp.Examples.SampleStaticClass")
#> $Methods
#> [1] "Add" "GetAString" "GetInstanceObject"
#>
#> $Fields
#> [1] "StaticString"
#>
#> $Properties
#> character(0)
To explore the methods, fields, and properties of a class separately,
you can use the getStaticMethods
,
getStaticFields
, and getStaticProperties
functions, respectively.
To call static methods of a .NET class, you can use the
rSharp::callStatic
function. If you are not sure which
arguments are expected by a certain method, you can use the
getStaticMemberSignature
function to get the method’s
signature:
# Get the signature of the GetAString method
getStaticMemberSignature("rSharp.Examples.SampleStaticClass", "GetAString")
#> [1] "Static, Method: String GetAString"
# Get the signature of the Add method
getStaticMemberSignature("rSharp.Examples.SampleStaticClass", "Add")
#> [1] "Static, Method: Int32 Add, Int32, Int32"
The method GetAString
returns a string and does not have
any arguments, so we can call it using the callStatic
function:
rSharp::callStatic("rSharp.Examples.SampleStaticClass", "GetAString")
#> [1] "A string from static class"
If the method has arguments, they can be passed as additional
arguments to the callStatic
function, as in this case of
calling the static function Add()
that requires two
integers as arguments:
rSharp::callStatic("rSharp.Examples.SampleStaticClass", "Add", as.integer(1), as.integer(2))
#> [1] 3
Mind that we had to explicitly cast the arguments to integer, as all numeric values in R are double by default.
We can also access static fields of a class using the
getStatic
and setStatic
functions:
# Get the value of the static field
getStatic("rSharp.Examples.SampleStaticClass", "StaticString")
#> [1] "A string from static class"
This static string is returned by the method GetAString
we called earlier. We can set the value of the static field and would
expect it to be returned by the GetAString
method:
# Set the value of the static field
setStatic("rSharp.Examples.SampleStaticClass", "StaticString", "New value")
# Get the value of the static field
getStatic("rSharp.Examples.SampleStaticClass", "StaticString")
#> [1] "New value"
# Call the GetAString method
callStatic("rSharp.Examples.SampleStaticClass", "GetAString")
#> [1] "New value"
Working with objects
So far, we were passing basic data types as arguments and receiving
basic data types as return values. However, in many cases, we would need
to work with .NET objects. For example, the method
GetInstanceObject
returns an instance of the
SampleInstanceClass
class. rSharp
wraps such
objects in the NetObject
class:
# Call the SampleInstanceClass method
instance <- callStatic("rSharp.Examples.SampleStaticClass", "GetInstanceObject")
# Check the class of the returned object
class(instance)
#> [1] "NetObject" "R6"
# Get the type of the object
instance$type
#> [1] "rSharp.Examples.SampleInstanceClass"
The R6 class NetObject
holds the pointer to the .NET
object and provides methods to interact with it. Thus, the constructor
of NetObject
requires an external pointer to a .NET object.
The pointer can be created by calling the
newPointerFromName
function. Is the constructor of the .NET
class requires arguments, they can be passed as additional arguments to
the newPointerFromName
function. We can examine the
constructors of a class with the getConstructors
function:
getConstructors("rSharp.Examples.SampleInstanceClass")
#> [1] "Constructor: .ctor"
#> [2] "Constructor: .ctor, Double"
#> [3] "Constructor: .ctor, Double, Double"
#> [4] "Constructor: .ctor, Int32"
#> [5] "Constructor: .ctor, Int32, Int32"
#> [6] "Constructor: .ctor, Int32, Int32, Double, Double"
The SampleInstanceClass
has six different constructor
signature. We can create a new pointer to an instance of the class by
calling the constructor with two double arguments and create a
NetObject
using the pointer:
# Create a pointer to a .NET object
pointer <- newPointerFromName("rSharp.Examples.SampleInstanceClass", as.double(1), as.double(2))
# Wrap the pointer in a new NetObject
newInstance <- NetObject$new(pointer)
The more conventient way of creating a NetObject
for a
new object is by calling the newObjectFromName
function:
# Create a new instance of the SampleInstanceClass
newInstance2 <- newObjectFromName("rSharp.Examples.SampleInstanceClass", as.integer(1))
As with static classes, we can examine the methods, fields, and
properties of an object using the getMethods
,
getFields
, and getProperties
methods called on
the object, respectively.
# Get the methods of the object
newInstance$getMethods()
#> [1] "Equals" "GetAString" "GetHashCode" "GetType" "ToString"
# Get the fields of the object
newInstance$getFields()
#> [1] "FieldDoubleOne" "FieldDoubleTwo" "FieldIntegerOne" "FieldIntegerTwo"
# Get the properties of the object
newInstance$getProperties()
#> character(0)
A non-static class can also have static members, which can be listed
using the getStatic...
functions:
newInstance$getStaticFields()
#> [1] "StaticString"
Setting and getting non-static fields and properties of an object is
done using the set
and get
methods,
respectively:
# Get the value of the static field:
newInstance$get("FieldDoubleOne")
#> [1] 1
# Change the value of the static field:
newInstance$set("FieldDoubleOne", 23)
# Get the value of the static field:
newInstance$get("FieldDoubleOne")
#> [1] 23
To get or set static fields, use the functions getStatic
and setStatic
.
Finally, we can call the methods of the object using the
call
method:
# Call the Add method
newInstance$call("GetAString")
#> [1] "A string from instance class"