Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible call a COM object from within R, if the COM object is exposed from a .NET assembly?

Tags:

.net

r

com

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:

  1. 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---
    
  2. This R code loads a .NET .dll exposed as a COM object:

    library('rcom')
    x <- comCreateObject("System.Text.ASCIIEncoding")
    
  3. Its definitely finding the COM object:

    x attr(,"class") 1 "COMObject"

  4. 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.

like image 391
Contango Avatar asked Feb 13 '13 20:02

Contango


3 Answers

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

  1. 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:

    • The C# assembly (set to 32-bit).
    • The version of R (I used Revolution R in 32-bit mode).
  2. 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).

  3. Can unregister a COM component by calling "regtlibv12.exe -u MyComDLL.tlb".

  4. 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

  1. See R help on rcom package.
  2. See User manual for rcom.
  3. See the statconn Wiki page.
  4. If it doesn't work with R v2.15, try it with R v2.14.
like image 110
Contango Avatar answered Sep 18 '22 21:09

Contango


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.

enter image description here

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.

like image 25
Jeremy Thompson Avatar answered Sep 20 '22 21:09

Jeremy Thompson


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.

like image 39
D Stanley Avatar answered Sep 18 '22 21:09

D Stanley