One problem that comes up for us quite a bit when using R.NET from the RProvider is that we need to load it into multiple appdomains simultaneously. The native R.DLL is obviously AppDomain agnostic, but the R.NET instances are completely isolated.
The concrete problem is that in the second appdomain, the R.DLL fails during initialization because it is already initialized.
Any ideas on how we could make that work? Seems like R.DLL instances would have to be managed through some native code, or we would need some process-wide flag to record the fact that R.DLL has already been initialized.
Basically this is the same issue as the multiple-initialization problem.
Comments: Sorry it took a little longer. Here is a repro (assuming RDotNet.dll and RDotNet.NativeLibrary.dll were added to the list of references). You'll also need to set the RPath const to the path to load R. ``` using System; using System.IO; using System.Reflection; using RDotNet; using RDotNet.Devices; namespace RDotNetWithAppDomain { public class Job : MarshalByRefObject { private const string RPath = @"C:\dev\p4\bmc\3rdParty\R\R-2.14.2\bin\x64"; // uses R.NET here public void Execute() { SetupRPath(); var engine = InitREngine(); engine.Evaluate("x <- 5"); } // initializes REngine private REngine InitREngine() { var engine = REngine.CreateInstance(System.AppDomain.CurrentDomain.Id.ToString()); engine.Initialize(null, new NullCharacterDevice()); // real char device? AppDomain.CurrentDomain.DomainUnload += (EventHandler)((o, e) => engine.Dispose()); return engine; } // sets up R path private void SetupRPath() { var oldPath = Environment.GetEnvironmentVariable("PATH"); var newPath = string.Format("{0}{1}{2}", RPath, Path.PathSeparator, oldPath); Environment.SetEnvironmentVariable("PATH", newPath); } } class Program { private static void TestAppDomain(string jobName) { var domainSetup = new AppDomainSetup(); domainSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; AppDomain ad = AppDomain.CreateDomain(jobName, AppDomain.CurrentDomain.Evidence, domainSetup); var type = typeof(Job); var jd = (Job) ad.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName, true, BindingFlags.CreateInstance, null, new object[] {}, null, null); jd.Execute(); AppDomain.Unload(ad); } static void Main(string[] args) { TestAppDomain("test1"); // works TestAppDomain("test2"); // hangs at the last line in Job.Execute() } } } ```
The concrete problem is that in the second appdomain, the R.DLL fails during initialization because it is already initialized.
Any ideas on how we could make that work? Seems like R.DLL instances would have to be managed through some native code, or we would need some process-wide flag to record the fact that R.DLL has already been initialized.
Basically this is the same issue as the multiple-initialization problem.
Comments: Sorry it took a little longer. Here is a repro (assuming RDotNet.dll and RDotNet.NativeLibrary.dll were added to the list of references). You'll also need to set the RPath const to the path to load R. ``` using System; using System.IO; using System.Reflection; using RDotNet; using RDotNet.Devices; namespace RDotNetWithAppDomain { public class Job : MarshalByRefObject { private const string RPath = @"C:\dev\p4\bmc\3rdParty\R\R-2.14.2\bin\x64"; // uses R.NET here public void Execute() { SetupRPath(); var engine = InitREngine(); engine.Evaluate("x <- 5"); } // initializes REngine private REngine InitREngine() { var engine = REngine.CreateInstance(System.AppDomain.CurrentDomain.Id.ToString()); engine.Initialize(null, new NullCharacterDevice()); // real char device? AppDomain.CurrentDomain.DomainUnload += (EventHandler)((o, e) => engine.Dispose()); return engine; } // sets up R path private void SetupRPath() { var oldPath = Environment.GetEnvironmentVariable("PATH"); var newPath = string.Format("{0}{1}{2}", RPath, Path.PathSeparator, oldPath); Environment.SetEnvironmentVariable("PATH", newPath); } } class Program { private static void TestAppDomain(string jobName) { var domainSetup = new AppDomainSetup(); domainSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; AppDomain ad = AppDomain.CreateDomain(jobName, AppDomain.CurrentDomain.Evidence, domainSetup); var type = typeof(Job); var jd = (Job) ad.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName, true, BindingFlags.CreateInstance, null, new object[] {}, null, null); jd.Execute(); AppDomain.Unload(ad); } static void Main(string[] args) { TestAppDomain("test1"); // works TestAppDomain("test2"); // hangs at the last line in Job.Execute() } } } ```