[R] Using R.dll in .NET IPC
adschai at optonline.net
adschai at optonline.net
Thu Oct 18 01:57:31 CEST 2007
Hi - I wrapped R.dll up using somebody code online (see below). I am comparing R on .NET IPC with R(D)COM.
I got it all working. Except, I realize that in my client code of the IPC remoting, if I call 'plot' function in R via EvaluateNoReturn, I don't seem to see any results on the R plot window (although the window pops up). Is there anything to do with the code of R.dll wrapper below? I don't understand much of the code. But I have the feeling that graphic output was not specified to output correctly. Any help would be really really appreciated. Thank you.
- adschai
#define SUPERCONSOLE
using System;
using System.Collections;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32;
namespace SharpR
{
/// <summary>
/// Class for interp with the R.DLL. All is static as R is mono-threaded.
/// </summary>
class RWrapper
{
#region <R.DLL interop signatures>
//- DLL Management/Information
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPStr)]
static extern string getDLLVersion();
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPStr)]
static extern string get_R_HOME();
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPStr)]
static extern string getRUser();
//- R Start Up
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern void R_setStartTime();
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern void R_DefParams(ref RStartStruct @params);
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern void R_SetParams(ref RStartStruct @params);
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern void R_set_command_line_arguments(int argc, string[] args);
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern int GA_initapp(int argc, string[] args);
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern void readconsolecfg();
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern void setup_Rmainloop();
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern void R_ReplDLLinit();
//- R SEXP management
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern IntPtr Rf_mkString(string toConvert);
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern IntPtr Rf_protect(IntPtr ptr);
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern void Rf_unprotect(int l);
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern void Rf_unprotect_ptr(IntPtr ptr);
//- R Parser/Eval
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern IntPtr R_ParseVector(IntPtr str, int x, out RParseStatus result);
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern IntPtr R_tryEval(IntPtr exp, IntPtr env, out int evalError);
//- R Symbols
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern IntPtr Rf_install(string name);
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern IntPtr Rf_findVar(IntPtr symbol, IntPtr env);
[DllImport("R.DLL", /*CallingConvention = CallingConvention.Cdecl,*/ CharSet = CharSet.Ansi)]
static extern void Rf_setVar(IntPtr symbol, IntPtr value, IntPtr env);
#endregion
#region <R.DLL interop types>
enum RParseStatus
{
PARSE_NULL,
PARSE_OK,
PARSE_INCOMPLETE,
PARSE_ERROR,
PARSE_EOF
};
enum SaType
{
SA_NORESTORE = 0,/* = 0 */
SA_RESTORE,
SA_DEFAULT,/* was === SA_RESTORE */
SA_NOSAVE,
SA_SAVE,
SA_SAVEASK,
SA_SUICIDE
};
enum RBool
{
RFalse = 0,
RTrue
};
enum RYesNoCancel
{
Yes = 1,
No = -1,
Cancel = 0
};
enum RUIMode
{
RGui = 0, RTerm, LinkDLL
};
[StructLayout(LayoutKind.Sequential)]
struct RStartStruct
{
public RBool R_Quiet;
public RBool R_Slave;
public RBool R_Interactive;
public RBool R_Verbose;
public RBool LoadSiteFile;
public RBool LoadInitFile;
public RBool DebugInitFile;
public SaType RestoreAction;
public SaType SaveAction;
public uint vsize;
public uint nsize;
public uint max_vsize;
public uint max_nsize;
public uint ppsize;
public int NoRenviron;
//!! Warning - R will keep theses pointers. See gnuwin32\system.c (line 638)
public IntPtr home;
public IntPtr rhome;
//!!
[MarshalAs(UnmanagedType.FunctionPtr)]
public dgReadConsole readConsole;
[MarshalAs(UnmanagedType.FunctionPtr)]
public dgWriteConsole writeConsole;
[MarshalAs(UnmanagedType.FunctionPtr)]
public dgCallback callback;
[MarshalAs(UnmanagedType.FunctionPtr)]
public dgShowMessage showMessage;
[MarshalAs(UnmanagedType.FunctionPtr)]
public dgYesNoCancel yesNoCancel;
[MarshalAs(UnmanagedType.FunctionPtr)]
public dgBusy busy;
public RUIMode characterMode;
};
//[UnmanagedFunctionPointer(CallingConvention.Cdecl)] < .Net 2.0
delegate int dgReadConsole(
[MarshalAs(UnmanagedType.LPStr)]string prompt,
IntPtr buf, int len,
int addtohistory
);
//[UnmanagedFunctionPointer(CallingConvention.Cdecl)] < .Net 2.0
delegate void dgWriteConsole(
[MarshalAs(UnmanagedType.LPStr,SizeParamIndex = 1)]
string buf, int len);
//[UnmanagedFunctionPointer(CallingConvention.Cdecl)] < .Net 2.0
delegate void dgCallback();
//[UnmanagedFunctionPointer(CallingConvention.Cdecl)] < .Net 2.0
delegate void dgShowMessage(string msg);
//[UnmanagedFunctionPointer(CallingConvention.Cdecl)] < .Net 2.0
[return: MarshalAs(UnmanagedType.I4)]
delegate RYesNoCancel dgYesNoCancel(string msg);
//[UnmanagedFunctionPointer(CallingConvention.Cdecl)] < .Net 2.0
delegate void dgBusy(int which);
#endregion
#region <BDX Interops frm RPROXY.DLL>
/* const string strBdxGetObject = "BDX_get_vtbl at 8";
/// <summary>
/// PInvoke with automatic marshal on GetProcAddress to return dgScProxyGetObject function
/// </summary>
/// <param name="hModule">HMODULE of the RPROXY.DLL</param>
/// <param name="procName">*MUST* be strScProxyGetObject</param>
/// <returns></returns>
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, EntryPoint = "GetProcAddress")]
[return: MarshalAs(UnmanagedType.FunctionPtr)]
static extern dgBdxGetObject GetPABdxGetObject(IntPtr hModule, string procName);
delegate int dgBdxGetObject(out IntPtr vtable, uint value);
delegate void BdxFree(IntPtr bdx);
delegate void BdxTrace(IntPtr bdx);
delegate int BdxVariant2BDX([MarshalAs(UnmanagedType.Struct)]object var, out IntPtr bdx);
delegate int BdxBDX2Variant(IntPtr bdx, [MarshalAs(UnmanagedType.Struct)]out object var);
[StructLayout(LayoutKind.Sequential)]
struct RBdxVtable
{
public BdxFree free;
public BdxTrace trace;
public BdxVariant2BDX v2bdx;
public BdxBDX2Variant bdx2v;
} */
[DllImport("Rproxy.DLL", CallingConvention = CallingConvention.Cdecl)]
static extern int BDX2SEXP(IntPtr pBDXData, out IntPtr pSEXPData);
[DllImport("Rproxy.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int SEXP2BDX(IntPtr pSexp, out IntPtr ppBDXData);
[DllImport("Rproxy.dll", EntryPoint = "Variant2BDX at 20")]
static extern int Variant2BDX([MarshalAs(UnmanagedType.Struct)]object var, out IntPtr bdx);
[DllImport("Rproxy.dll", EntryPoint = "BDX2Variant at 8")]
static extern int BDX2Variant(IntPtr bdx,[MarshalAs(UnmanagedType.Struct)]out object var);
[DllImport("Rproxy.dll", EntryPoint = "bdx_free at 4")]
static extern void bdx_free(IntPtr bdx);
#endregion
#region <Win32 interop signatures>
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
#endregion
static string sg_dllVersion,sg_RHome,sg_RUsersHome;
static IntPtr sg_hModR;
static IntPtr sg_hModRProxy;
static StringBuilder sg_ConsoleOutput;
static IntPtr sg_rDll_R_GlobalEnvPtr, sg_rDll_R_UserBreakPtr;
static IntPtr sg_rDll_R_UnboundValue;
static GCHandle[] sg_lockDelegates;
static RWrapper()
{
try {
//- Get the active DLL path from the registry
string dllPath = Convert.ToString(
Registry.LocalMachine.OpenSubKey("Software\\R-core\\R", false).GetValue("InstallPath")
);
//- Fix the process PATH
Environment.SetEnvironmentVariable("PATH",
dllPath + "\\bin;" + Environment.GetEnvironmentVariable("PATH"),
EnvironmentVariableTarget.Process
);
//- Load the R.DLL module into the process
sg_hModR = LoadLibrary(dllPath + "\\bin\\R.dll");
if (sg_hModR == IntPtr.Zero) throw new Exception("Unable to load R.DLL");
//- Load the Rproxy.DLL module into the process
sg_hModRProxy = LoadLibrary(dllPath + "\\bin\\Rproxy.dll");
if (sg_hModRProxy == IntPtr.Zero) throw new Exception("Unable to load R.DLL");
//- Read the DLL version by Interop
sg_dllVersion = getDLLVersion();
//- Get important R global variable pointers from GetProcAddress
sg_rDll_R_GlobalEnvPtr = GetProcAddress(sg_hModR, "R_GlobalEnv");
sg_rDll_R_UserBreakPtr = GetProcAddress(sg_hModR, "UserBreak");
sg_rDll_R_UnboundValue = GetProcAddress(sg_hModR, "R_UnboundValue");
//- Output DLL
sg_ConsoleOutput = new StringBuilder();
//- Let's start R
RStartStruct start = new RStartStruct();
//- Get Defaults
R_setStartTime();
R_DefParams(ref start);
sg_RHome = get_R_HOME();
sg_RUsersHome = getRUser();
//- Inject R Home
start.home = Marshal.StringToHGlobalAnsi(sg_RHome);
start.rhome = Marshal.StringToHGlobalAnsi(sg_RUsersHome);
//- Setup R in embedded/batch mode
start.characterMode = RUIMode.LinkDLL;
start.R_Quiet = RBool.RTrue;
start.R_Interactive = RBool.RTrue;
start.RestoreAction = SaType.SA_RESTORE;
start.SaveAction = SaType.SA_NOSAVE;
//- Setup the callbacks
start.readConsole = new dgReadConsole(cbReadConsole);
start.writeConsole = new dgWriteConsole(cbWriteConsole);
start.busy = new dgBusy(cbBusy);
start.callback = new dgCallback(cbCallback);
start.showMessage = new dgShowMessage(cbShowMessage);
start.yesNoCancel = new dgYesNoCancel(cbYesNoCancel);
sg_lockDelegates = new GCHandle[7];
sg_lockDelegates[0] = GCHandle.Alloc(start.readConsole);
sg_lockDelegates[1] = GCHandle.Alloc(start.writeConsole);
sg_lockDelegates[2] = GCHandle.Alloc(start.busy);
sg_lockDelegates[3] = GCHandle.Alloc(start.callback);
sg_lockDelegates[4] = GCHandle.Alloc(start.showMessage);
sg_lockDelegates[5] = GCHandle.Alloc(start.yesNoCancel);
sg_lockDelegates[6] = GCHandle.Alloc(start);
//- Gentleman start your engines !
R_SetParams(ref start);
R_set_command_line_arguments(0, new string[] { });
GA_initapp(0, new string[] { });
readconsolecfg();
setup_Rmainloop();
R_ReplDLLinit();
} catch(Exception e)
{
throw;
}
}
private RWrapper() {}
static int UserBreak
{
get
{
return Marshal.ReadInt32(sg_rDll_R_UserBreakPtr);
}
set
{
Marshal.WriteInt32(sg_rDll_R_UserBreakPtr,value);
}
}
static public string RDllVersion { get { return sg_dllVersion; } }
static public string RHome { get { return sg_RHome; } }
static public string RUsersHome { get { return sg_RUsersHome; } }
#region <R Callbacks>
static int cbReadConsole(string prompt, IntPtr buf, int len, int addtohistory)
{
//- We don't use the console to interact with R. The function returns 0
// to force R exiting any event loop.
return 0;
}
static void cbWriteConsole(string buf, int len)
{
sg_ConsoleOutput.Append(buf);
#if SUPERCONSOLE
ConsoleColor c = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Green;
Console.Write(buf);
Console.ForegroundColor = c;
#endif
}
static void cbCallback() { /*NoOp*/ }
static void cbBusy(int which)
{
#if SUPERCONSOLE
int top = Console.CursorTop, left = Console.CursorLeft;
Console.CursorTop = Console.CursorLeft = 0;
Console.Write("Busy : {0}", which);
Console.CursorTop = top; Console.CursorLeft = left;
#endif
}
static void cbShowMessage(string msg)
{
Console.WriteLine("Message : " + msg);
}
static RYesNoCancel cbYesNoCancel(string msg)
{
Console.WriteLine("YesNoCancel : " + msg);
return RYesNoCancel.Cancel;
}
#endregion
static IntPtr GetCurrentEnv()
{
IntPtr ret = Marshal.ReadIntPtr(sg_rDll_R_GlobalEnvPtr);
return ret;
}
static bool IsUnbound(IntPtr Sexp)
{
IntPtr unbound = Marshal.ReadIntPtr(sg_rDll_R_UnboundValue);
return Sexp == unbound;
}
static public void EvaluateNoReturn(string statement)
{
//- Parse the expresion
RParseStatus status;
IntPtr lSexpVect = R_ParseVector(Rf_mkString(statement), 1, out status);
if(status!=RParseStatus.PARSE_OK)
{
throw new Exception("R Parse Error : " + status.ToString());
}
Rf_protect(lSexpVect);
// lSexpVect is a vector of lSexp. We need to read the memory directly to get
// the lSexp
int evalError;
IntPtr lSexp = Marshal.ReadIntPtr(lSexpVect, 24);
R_tryEval(lSexp, IntPtr.Zero, out evalError);
Rf_unprotect(1);
if (evalError != 0) throw new Exception("R Eval Error : " + evalError.ToString());
}
static public object Evaluate(string statement)
{
//- Parse the expresion
RParseStatus status;
IntPtr lSexpVect = R_ParseVector(Rf_mkString(statement), 1, out status);
if (status != RParseStatus.PARSE_OK)
{
throw new Exception("R Parse Error : " + status.ToString());
}
Rf_protect(lSexpVect);
// lSexpVect is a vector of lSexp. We need to read the memory directly to get
// the lSexp
int evalError;
IntPtr lSexp = Marshal.ReadIntPtr(lSexpVect, 24);
Rf_protect(lSexp);
IntPtr lresult = R_tryEval(lSexp, GetCurrentEnv(), out evalError);
Rf_unprotect(1);
if (evalError != 0) throw new Exception("R Eval Error : " + evalError.ToString());
IntPtr bdxResult;
object result;
evalError = SEXP2BDX(lresult, out bdxResult);
evalError = BDX2Variant(bdxResult, out result);
bdx_free(bdxResult);
return result;
}
static public object GetSymbol(string name)
{
IntPtr lsValue = Rf_findVar(Rf_install(name), GetCurrentEnv());
if (IsUnbound(lsValue))
{
throw new Exception(name + " is an unbound value");
}
IntPtr bdxResult;
object result;
int evalError = SEXP2BDX(lsValue, out bdxResult);
evalError = BDX2Variant(bdxResult, out result);
bdx_free(bdxResult);
return result;
}
static public void SetSymbol(string name,object value)
{
IntPtr bdxData, sexpData;
int evalError = Variant2BDX(value, out bdxData);
evalError = BDX2SEXP(bdxData, out sexpData);
bdx_free(bdxData);
IntPtr lsSymbol = Rf_install(name);
Rf_setVar(lsSymbol, sexpData, GetCurrentEnv());
}
static public string CollectConsole()
{
string ret = sg_ConsoleOutput.ToString();
sg_ConsoleOutput = new StringBuilder();
return ret;
}
}
}
More information about the R-help
mailing list