How to host an IronPython engine in a separate AppDomain? - c#

I am using the below code to execute the IronPython Script on separate "appDomain" from c#.
(I used this approach to resolve memory leakage issue)
The scripts which take a lesser time (less than 3 mins), executes fine.
But if the script which takes a longer time (more than 5mins) throws an exception saying
-> System.Runtime.Remoting.RemotingException: Object '/011b230e_2f28_4caa_8bbc_92fabb63b311/vhpajnwe48ogwedf6zwikqow_4.rem'
using System;
using Microsoft.Scripting;
namespace PythonHostSamle
{
class Program
{
static void Main(string[] args)
{
AppDomain sandbox = AppDomain.CreateDomain("sandbox");
var engine = IronPython.Hosting.Python.CreateEngine(sandbox);
var searchPaths = engine.GetSearchPaths();
searchPaths.Add(#"C:\Python25\Lib");
searchPaths.Add(#"C:\RevitPythonShell");
engine.SetSearchPaths(searchPaths);
ScriptScope scope = engine.ExecuteFile("C:\Python25\Test.py")
// Script takes morethan 5mins to execute(sleep in the script)
ObjectHandle oh = scope.GetVariableHandle("GlobalVariableName")
// System throws following exception
//System.Runtime.Remoting.RemotingException:
// Object '/011b230e_2f28_4caa_8bbc_92fabb63b311/vhpajnwe48ogwedf6zwikqow_4.rem'
Console.ReadKey();
}
}
}

Overriding InitializeLifetimeServices and returning null would be the normal approach. I doubt that's possible in your case. Including the <lifetime> element in the app.config file is another approach.

Related

How can I see what exceptions my IronPython script is generating?

I am experimenting with embedding IronPython in a Unity 3D project and I've run into some errors I can't seem to figure out.
I have the following script attached to a GameObject in Unity.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using IronPython;
using IronPython.Modules;
using System.Text;
public class IronPythonWrapper : MonoBehaviour {
void Start() {
ScriptTest();
}
public static void ScriptTest() {
// create the engine
var engine = IronPython.Hosting.Python.CreateEngine();
// and the scope (i.e. the Python namespace)
var scope = engine.CreateScope();
// execute the Python script
engine.ExecuteFile("Assets/Scripts/ipscript.py");
// grab the variable from the Python scope
string commands = scope.GetVariable<string>("commands");
Debug.Log(commands);
}
}
The goal is to have the IronPython engine instantiated above execute the following simple Python script, called ipscript.py.
commands = "Script executed."
When I enter play mode (in Unity) I get the following error.
MissingMemberException: 'ScopeStorage' object has no attribute 'commands'
Does anyone have any idea what I should investigate to resolve this problem? Perhaps I need to implement some error trapping so I can see any exceptions IronPython is generating when it executes the Python script?
Your python execution is not properly passing the scope. Your commands variable is therefore created on a transient scope that is discarded after executing the python file and the explicitly created scope has no idea what commands is.
The snippet should read like
engine.ExecuteFile("Assets/Scripts/ipscript.py", scope);

Dynamic assembly created using Reflection.Emit crashes with exit code -532462766

I have been following this article to generate a dynamic assembly as follows:
var directory = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Desktop));
var file = new FileInfo(Path.Combine(directory.FullName, #"MyDynamicAssembly.exe"));
var domain = AppDomain.CurrentDomain;
var name = new AssemblyName("Namespace.With.Dots");
var builderAssembly = domain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Save, directory.FullName);
var builderModule = builderAssembly.DefineDynamicModule("Namespace.With.Dots.Temp.exe");
var builderType = builderModule.DefineType("Program", TypeAttributes.Class | TypeAttributes.Public, typeof(object));
var builderMethod = builderType.DefineMethod("Main", MethodAttributes.Private | MethodAttributes.Static, typeof(int), new Type [] { typeof(string []) });
var generator = builderMethod.GetILGenerator();
generator.Emit(OpCodes.Ldstr, "Hello, World!");
generator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type [] { typeof(string) }));
generator.EmitWriteLine("Hello again.");
generator.Emit(OpCodes.Ldc_I4, 0);
generator.Emit(OpCodes.Ret);
builderAssembly.SetEntryPoint(builderMethod, PEFileKinds.ConsoleApplication);
builderAssembly.Save(file.Name, PortableExecutableKinds.ILOnly, ImageFileMachine.I386);
var process = Process.Start(file.FullName); // Crashes with image below.
var result = process.WaitForExit();
var exitCode = process.ExitCode; // -532462766.
Here is what I know about the code above:
It is creating a dynamic assembly as Save only.
The assembly name, module name and output PE name are all different (I'm assuming this is not a problem).
It creates a public static class called Program.
It creates a single method in this class with signature private static int Main (string []).
It sets this method as an entry point and configures the assembly to be a console app.
It writes the assembly as ILOnly which is processor architecture agnostic.
It configures the assembly image as i386 which is what I'm running on (Win7 32 bit with an Intel processor).
METHOD:
Pushes a string literal reference to the stack.
Calls Console.WriteLine with the arguments taken from the stack.
Calls Console.WriteLine again using EmitWriteLine.
Pushes 0 as an Int32 to the stack as a return value.
Returns.
CRASH:
Ignore the filename on the image. It would be MyDynamicAssembly.exe per the code above.
Any pointers on what's going wrong here would be appreciated.
You'll have a lot more tough debugging jobs ahead of you beyond this one. You basically only ever get two exit codes. -532462766 or 0xe0434352 is the infamous "CCR" exception. The CLR died trying to load the assembly and can't perform the normal exception handling logic. You of course want to make sure that your generated IL is correct by testing it in-process before you try to run it stand-alone in a separate process. You'll at least have a debugger available that way.
The other one is -532459699 or 0xe0434f4d, the normal "COM" exception. Produced when the code threw a plain .NET exception and it wasn't handled because of a lack of try/catch and no AppDomain.UnhandledException event handler. You'll have to make-do without a stack trace and can only reverse-engineer the location where the exception was thrown with the hints in this answer.
Very punishing trouble-shooting of course, you basically do not ever want to do this. At least consider loading the code in another AppDomain so you stand a chance to produce a diagnostic and recover. It can still be in another process by writing a small "host" program that creates the appdomain and loads the assembly and generates a diagnostic. Also provides you with a way to use the debugger.
Finally got it to work by changing the module builder cell to overload:
var builderModule = builderAssembly.DefineDynamicModule("MyDynamicAssembly", "MyDynamicAssembly.exe", false);

System.NullReferenceException When Trying To Load Functions From A DLL File - C#

I'm creating an add-on system for a shell I'm developing using C#. I've followed this and this. Here is my function to load an add-on:
public void loadAppFromDLL(string assemblyFile)
{
Assembly a = Assembly.Load(assemblyFile);
Type app = a.GetType("App");
MethodInfo loadMethod = app.GetMethod("load");
object appInstance = Activator.CreateInstance(app);
loadMethod.Invoke(appInstance, null);
}
Here is the add-on:
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace App
{
public class App
{
public void load()
{
MessageBox.Show("Application loaded successfully!");
}
}
}
When I build the add-on, I place it in the same directory as the shell executable and call:
LoadExternalApp lea = new LoadExternalApp();
lea.loadAppFromDLL("SampleApp");
(LoadExternalApp contains the DLL loading function)
When I was debugging my shell, I noticed that:
The app didn't start
There was a System.NullReferenceException
What am I not doing right?
This:
Type app = a.GetType("App");
is looking for a type with a namespace-qualified name of App.
Your type is called App in a namespace of App, so Assembly.GetType is returning null, and then you're dereferencing it. Instead, you should use:
Type app = a.GetType("App.App");
However, you shouldn't give a class the same name as its namespace in the first place. Fix that, so that you end up with something more like:
Type app = a.GetType("App.PlugIn");
You should still check whether GetType (or GetMethod) returns null, in order to fail rather more gracefully and with more information.
Additionally, you should start following .NET naming conventions - give methods names in PascalCase. Oh, and you might want to consider a common interface for your add-ins rather than relying on reflection to call methods.

Why do 'requires' statements fail when loading (iron)ruby script via a C# program?

IronRuby and VS2010 noob question:
I'm trying to do a spike to test the feasibility of interop between a C# project and an existing RubyGem rather than re-invent that particular wheel in .net. I've downloaded and installed IronRuby and the RubyGems package, as well as the gem I'd ultimately like to use.
Running .rb files or working in the iirb Ruby console is without problems. I can load the both the RubyGems package, and the gem itself and use it, so, at least for that use case, my environment is set up correctly.
However, when I try to do the same sort of thing from within a C# (4.0) console app, it complains about the very first line:
require 'RubyGems'
With the error:
no such file to load -- rubygems
My Console app looks like this:
using System;
using IronRuby;
namespace RubyInteropSpike
{
class Program
{
static void Main(string[] args)
{
var runtime = Ruby.CreateRuntime();
var scope = runtime.ExecuteFile("test.rb");
Console.ReadKey();
}
}
}
Removing the dependencies and just doing some basic self-contained Ruby stuff works fine, but including any kind of 'requires' statement seems to cause it to fail.
I'm hoping that I just need to pass some additional information (paths, etc) to the ruby runtime when I create it, and really hoping that this isn't some kind of limitation, because that would make me sad.
Short answer: Yes, this will work how you want it to.You need to use the engine's SetSearchPaths method to do what you wish.
A more complete example
(Assumes you loaded your IronRuby to C:\IronRubyRC2 as the root install dir)
var engine = IronRuby.Ruby.CreateEngine();
engine.SetSearchPaths(new[] {
#"C:\IronRubyRC2\Lib\ironruby",
#"C:\IronRubyRC2\Lib\ruby\1.8",
#"C:\IronRubyRC2\Lib\ruby\site_ruby\1.8"
});
engine.Execute("require 'rubygems'"); // without SetSearchPaths, you get a LoadError
/*
engine.Execute("require 'restclient'"); // install through igem, then check with igem list
engine.Execute("puts RestClient.get('http://localhost/').body");
*/
Console.ReadKey();

How to host an IronPython engine in a separate AppDomain?

I have tried the obvious:
var appDomain = AppDomain.CreateDomain("New Domain");
var engine = IronPython.Hosting.Python.CreateEngine(appDomain); // boom!
But I am getting the following error message: Type is not resolved for member 'Microsoft.Scripting.Hosting.ScriptRuntimeSetup,Microsoft.Scripting, Version=0.9.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
Googling for this error has not proved fruitful sofar...
EDIT #1:
I tried to create a minimal reproducing project by copying the relevant stuff to a new Console Application:
using System;
using Microsoft.Scripting;
namespace PythonHostSamle
{
class Program
{
static void Main(string[] args)
{
AppDomain sandbox = AppDomain.CreateDomain("sandbox");
var engine = IronPython.Hosting.Python.CreateEngine(sandbox);
var searchPaths = engine.GetSearchPaths();
searchPaths.Add(#"C:\Python25\Lib");
searchPaths.Add(#"C:\RevitPythonShell");
engine.SetSearchPaths(searchPaths);
var scope = engine.CreateScope();
//scope.SetVariable("revit", _application);
//engine.Runtime.IO.SetOutput(new ScriptOutputStream(_instance), Encoding.UTF8);
//engine.Runtime.IO.SetErrorOutput(new ScriptOutputStream(_instance), Encoding.UTF8);
var script = engine.CreateScriptSourceFromString("print 'hello, world!'", SourceCodeKind.Statements);
script.Execute(scope);
Console.ReadKey();
}
}
}
This works as expected!
I am thus left to conclude that the error I am getting is related to one of the lines I commented out: The scope added to the engine contains an object I have little control over - a reference to a plugin host this software is intended to run in (Autodesk Revit Architecture 2010).
Maybe trying to pass that is what is creating the error?
Is there a way to pass a proxy instead? (will have to look up .NET remoting...)
EDIT #2:
I have whittled the problem down to passing an object via the scope that does cannot be proxied to the other AppDomain: All objects added to the scope of an IronPython interpreter running in a different AppDomain will have to be marshaled somehow and must thus either extend MarshalByRefObject or be Serializable.
Just create your own bootstrapping class that will run in a new AppDomain and will do the initialization of IronPyton there, will it solve the prob?

Categories