I'm wondering if it is possible to call .NET functions from R, via a COM call.
The library rcom
allows calls to COM objects, so this should be possible, in theory, for any .NET assembly that is exposed as a COM object.
To keep it simple, I'll see if I can call the .Reverse()
function in System.Text
, which is exposed by default as a COM object from the .NET framework.
This is what I have tried so far:
I obtained a list of ProgID's in my system (see link to C# code). Here is a list of the relevant ProgIDs in my system:
---start list of COM ProgID entries---
<snip>
System.SystemException -> mscoree.dll
System.Text.ASCIIEncoding -> mscoree.dll
System.Text.StringBuilder -> mscoree.dll
System.Text.UnicodeEncoding -> mscoree.dll
System.Text.UTF7Encoding -> mscoree.dll
System.Text.UTF8Encoding -> mscoree.dll
<snip>
---end list---
This R code loads a .NET .dll exposed as a COM object:
library('rcom')
x <- comCreateObject("System.Text.ASCIIEncoding")
Its definitely finding the COM object:
x attr(,"class") 1 "COMObject"
My question is - how do I call the .Reverse()
function within this COM object?
Update
In .NET, the call would be:
string x = "hello".Reverse();
So, in R, the call would be?
Update
For an example of R calling C#, see R calls C# in Embedding R in Applications on Windows on slide 61.
Note that ProgId
is ProjectName.ClassName
from .NET class.
I have just successfully called .NET code from R via COM, based on instructions from slide 61 to 65 of Embedding R in Applications on Windows.
Here is the R code:
# This is a once-off call.
install.packages("rcom")
library(rcom)
# This is a once-off call. See rcom user manual at:
# http://cran.r-project.org/web/packages/rcom/rcom.pdf
installstatconnDCOM()
x <- comCreateObject("InteropSample.MyClass32")
comSetProperty(x,"Text","xxx")
comSetProperty(x,"Connector",comThis())
comInvoke(x,"DoCallback")
Here is the output within R:
> DoCallback: xxxNULL
Here is the C# code for the .NET class:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
// Before running this, get rid of errors with "RCOMServerLib" by adding the rcom type library:
//
// Make sure everything is 32-bit (32-bit build in .NET, 32-bit run of Revolution R).
//
// Tick "Register for COM interop" in .NET project settings.
//
// 1.Browse to "C:\Revolution\R-Enterprise-6.1\R-2.14.2\library\rcom\libs\i386>", then execute:
// C:\Revolution\R-Enterprise-6.1\R-2.14.2\library\rcom\libs\i386> C:\Windows\Microsoft.NET\Framework\v4.0.30319\regtlibv12.exe rcom_srv.tlb
// Registration of rcom_srv.tlb successful.
//
// 2. Add reference to "rcom_srv.tlb", this gets rid of errors for RComServerLib.
// (browse to "C:\Revolution\R-Enterprise-6.1\R-2.14.2\library\rcom\libs\i386")
//
// 3. If we are using VS2012, this .NET assembly class will be automatically registered as COM server on build if we are using VS2012. If using VS2012, you must do this manually on the command line.
//
// See:
// http://generally.wordpress.com/2006/07/28/exposing-your-net-assembly-through-com/
// http://www.inside-r.org/packages/cran/rcom/docs/comCreateObject
// In R:
// comCreateObject("InteropSample.MyClass32")
// comSetProperty(x,"Text","xxx")
// comSetProperty(x,"Connector",comThis())
// comInvoke(x,"DoCallback")
namespace COM___called_from_R
{
[Guid("3ddfe021-a0c6-4218-a254-4fc4328c99a7"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
internal interface IMyComponent
{
RCOMServerLib.IStatConnector Connector { set; }
string Text { set; }
void DoCallback();
}
[Guid("133fee0e-9b32-4429-8a43-6e2a706a9beb"), ComVisible(true)]
[ProgIdAttribute("InteropSample.MyClass32")]
public class MyComponent : IMyComponent
{
private string mText;
private RCOMServerLib.IStatConnector mConnector;
public RCOMServerLib.IStatConnector Connector
{
set { mConnector = value; }
}
public string Text
{
set { mText = value; }
}
public string MyProperty;
public void DoCallback()
{
if (mConnector != null)
{
mConnector.EvaluateNoReturn("cat(\"DoCallback: "
+ mText + "\")\n");
}
}
}
}
Notes
In order for this to work, everything must be consistently 32-bit, or consistently 64-bit. I got it working in 32-bit mode, by using the following settings:
If you use Visual Studio 2012 (VS2012), then if you tick "Register for COM interop" in .NET project settings, it will automatically run C:\Windows\Microsoft.NET\Framework\v4.0.30319\regtlibv12.exe
to register your custom .NET class as a system wide COM component, on compile. However, if you use Visual Studio 2010 (VS2010), it will not automatically run regtlibv12.exe
, all this setting will do is create the .tlb file (you will have to run regtlibv12.exe
manually, yourself).
Can unregister a COM component by calling "regtlibv12.exe -u MyComDLL.tlb".
If you build your project, and VS2012 complains that it cannot write the output .dll, this means that R is locking it due to the call x <- comCreateObject("InteropSample.MyClass32")
. To unlock the .dll so it can be compiled VS2012, close R, compile C#, then restart R.
Additional Information
rcom
package.rcom
.I know this question is old, I'm reporting my experience to help out future .Net/R developers.
No matter what I tried I could not Reference rcom_srv.tlb
A reference to C:\Program Files\R\R-2.15.3\library\rcom\libs\i386\rcom_srv.tlb
could not be added. Please make sure that the file is accessible, and that it is a valid assembly or COM component.
I found this article where they use both RCOMServerLib and STATCONNECTORSRVLib:
public STATCONNECTORSRVLib.StatConnectorClass rdcom = null;
//public RCOMServerLib.InternalConnectorClass rdcom = null; // Use 'rcom' for debugging
I wasn't able to make progress with either, so I eventually I did it without the RcomServerLib:
namespace XYZ.dotNetProject_R
{
[Guid("FA6F70DD-CDD0-4FF3-94BA-E2B94E68321D"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IDataHelper
{
string[,] GetdotNetProject2DArray(string code, DateTime fromDate, DateTime toDate);
}
[ComVisible(true)]
[ProgId("XYZ.dotNetProject_R")]
public class DataHelper : IDataHelper
{
public string[,] GetdotNetProject2DArray(string code, DateTime fromDate = default(DateTime), DateTime toDate = default(DateTime))
{
}
}
}
And I call this via R:
# On some PC's it wont download the Package until you set it to use your IE Proxy Settings:
setInternet2(TRUE)
# This is a once-off call.
install.packages("rcom")
# This is a once-off call.
installstatconnDCOM()
#Resusable calls
> library('rcom')
Loading required package: rscproxy
> dll = comCreateObject("XYZ.dotNetProject_R")
> dll
<pointer: 0x2079002c>
attr(,"class")
[1] "COMObject"
> series = comInvoke(dll,"GetdotNetProject2DArray","abc123","2000-01-01","2010-01-01")
> series
[,1] [,2]
[1,] "2000-01-01" "1236.1"
COM doesn't support generics, so I simply returned a string array. I found R only supports basic/primitive .Net types, eg string, datetime, int & etc. When I tried to return a object array, it failed and the .Net call returned NULL to R.
Well in general you use comInvoke
:
s <- comInvoke(x,"Reverse")
However, since neither System.Text.ASCIIEncoding
nor string
have a Reverse
method, you need to pick a different method to execute.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With