R(D)COM .NET Wrapper

by Igor Velikorossov of Insurance Australia Group, ©2004-2007.

An interaction between R and .NET is made possible by R(D)COM and the following article will discuss the implementation of managed wrapper for R(D)COM and IStatConnectorCharacterDevice.

  1. Adding references
  2. Character Device
  3. Manager wrapper
    • Basic wrapper
    • Extending the wrapper with async calls
  4. Using the wrapper



[ Downaload the demo project ]


Step 1: Adding references

First of all you need to include references to

  • StatConnectorCommon 1.1 Type Library (StatConnectorSrv.tlb),
  • StatConnectorSrv 1.1 Type Library (StatConnLib.tlb)

both of these you should find at the COM tab when adding project's references. The tlb's are normally located at C:\Program Files\R\(D)COM Server\tlb

Step 2: Character Device

You need to implement IStatConnectorCharacterDevice interface to be able to capture output from R.

The interface is pretty simple and defines only 3 methods.

/// <summary>
/// Managed wrapper for <see cref="IStatConnectorCharacterDevice"/> interface.
/// </summary>
public class CharacterDevice : IStatConnectorCharacterDevice, IDisposable
{
	void IStatConnectorCharacterDevice.Clear()
	{
	}
 
	void IStatConnectorCharacterDevice.WriteString(string bstrLine)
	{
	}
 
	void IStatConnectorCharacterDevice.WriteStringLevel(string bstrLine, int lLevel)
	{
	}
}

The current implementation of the character device is based on RichTextBox control, and provides ability to color-code and indent output. For more details on implmentation please refer to CharacterDevice.cs.

Step 3: Manager wrapper

Now we are ready to implement the actual R(D)COM wrapper.

Basic wrapper

The proposed skeleton for the wrapper will look similar to the following:

/// <summary>
/// Managed R(D)Com wrapper.
/// </summary>
public class Rcom : IDisposable 
{
	private		StatConnector		rsrv		= null;
	private		CharacterDevice	chardev		= new CharacterDevice();
 
	#region public Rcom()
	/// <summary>
	/// Initializes a new instance of the <see cref="Rcom"/> class.
	/// </summary>
	public Rcom()
	{
		this.chardev.DefaultColor	= Color.DarkGreen;
		this.chardev.DefaultIndent	= 30;
 
		this.rsrv = new StatConnectorClass();
		this.rsrv.Init("R");
		this.chardev.WriteString("R initialised\r\n\r\n", Color.Navy);
 
		this.rsrv.SetCharacterOutputDevice(this.chardev);
	}
	#endregion // public Rcom()
 
}

It is important to remember that we need to implement IDisposable interface to ensure that all unmanaged resources are released when the wrapper is no longer used and/or exists:

	protected virtual void Dispose(bool disposing) 
	{
		// Check to see if Dispose has already been called.
		if (!this.disposed) 
		{
			this.disposed = true;
 
			// If disposing equals true, dispose all managed 
			// and unmanaged resources.
			if (disposing) 
			{
				// Dispose managed resources.
				if (this.chardev != null) 
				{
					this.chardev.Dispose();
					this.chardev = null;
				}
			}
 
			// Release unmanaged resources. If disposing is false, 
			// only the following code is executed.
			if (this.rsrv != null)
			{
				this.rsrv.Close();
				Marshal.ReleaseComObject(this.rsrv);
				this.rsrv = null;
			}
		}
	}

Extending the wrapper with async calls

It is quite likely that some of operations may take a while to compute and it may be desirable not to lock the application until all of the computations are finished.

To do this let's allow calls to R be made in either sync or async modes:

	#region public JobStatus Submit(bool async)
	/// <summary>
	/// Submits the job for execution.
	/// </summary>
	/// <param name="async"><see langword="true"/> to run job in asynchronous mode; 
	/// <see langword="false"/> otherwise.</param>
	/// <returns>Job state.</returns>
	public JobStatus Submit(bool async)
	{
		if (this.rsrv == null)
			return JobStatus.Unknown;
 
		this.executionResult	= JobStatus.Unknown;
		this.running		= true;
 
		if (async)
		{
			// you can implement this call if desired
			//this.OnSubmitting();
 
			ThreadStart start = new ThreadStart(this.Execute);
			Thread thread = new Thread(start);
			thread.IsBackground = true;
			thread.Start();
		}
		else
		{
			this.Execute();
		}
 
		return this.executionResult;
	}
	#endregion // public JobStatus Submit(bool async)
 
 
	#region private void Execute()
	private void Execute()
	{
		lock (this.codeLines) 
		{
			string line = string.Empty;
			try
			{
				this.chardev.StartCapture();
 
				// processing the code lines 
 
				this.executionResult = JobStatus.Success;
			}
			catch (Exception ex)
			{
				this.chardev.WriteString("\r\n> " + line + "\r\n", Color.Red);
				this.chardev.WriteString(ex.ToString(), Color.Red);
 
				this.executionResult = JobStatus.Error;
			}
			finally
			{
				this.chardev.EndCapture();
				this.OnFinished(new FinishedEventArgs(this.executionResult, this.chardev.GetCapture(DataFormat.Rtf)));
			}		
		}
	}
	#endregion // private void Execute()

For more details on implmentation please refer to Rcom.cs.

Step 4: Using the wrapper

Using the wrapper is pretty much straight forward:

try
{
	// create teh R server
	this.rcom = new Rcom();
	// wire events
	this.rcom.LineEvaluated 	+= new LineEvaluatedHandler(rcom_LineEvaluated);
	this.rcom.Finished		+= new FinishedEventHandler(rcom_Finished);
 
 
	#region R code
	// R code
	this.rcom.CodeLines.AddRange(false, new string[] {
	                                                     "some_r_code_no_return;",
	                                                     "more_of_r_code_no_return;",
	                                                 });
	this.rcom.CodeLines.Add("some_r_code_returning_value();", true);
	this.rcom.CodeLines.AddRange(false, new string[] {
	                                                     // open bmp device
	                                                     "bmp(file=\"c:/pic.bmp\", width=100, height=100);",
	                                                     // plot
	                                                     "par(col=3);",
	                                                     "plot(some_value, some_function(value));",
	                                                     // save
	                                                     "dev.off();",
	                                                });
	}
	#endregion // R code
 
	// execute the code
	this.rcom.Submit(true);
}
catch (Exception ex)
{
	MessageBox.Show(this, 
			ex.ToString(),
			"R call failed", 
			MessageBoxButtons.OK, 
			MessageBoxIcon.Error);
}

Personal Tools