[IronPython] Hosting: Delegates from Ironpython to C#

Tim Riley riltim at gmail.com
Thu Aug 30 12:49:30 PDT 2007


Dino:
I revised the code to what you suggested and still no luck.

Is there any way I can set the environment variable other than via the
command prompt? My application is hosted inside another application
and I can't set the variable from there.

Tim

On 8/30/07, Dino Viehland <dinov at exchange.microsoft.com> wrote:
> Setting the env var at runtime won't help, you'll want to set it at a command prompt and start your app from the command prompt.  One other tweak:
>
>         public static void PythonRegister(string CommandName,
> CmdDelegate FuncPointer, CommandFlags flags)
>         {
>         PythonDelegateWrapper pdw = new PythonDelegateWrapper(FuncPointer));
>             RegPyCmd("_pycmds", CommandName, flags, new
> CmdDelegate(pdw.Invoke);
>                 GC.KeepAlive(pdw);
>         }
>
> Which will ensure it's not a delegate getting collected issue.
>
> If that doesn't work then we'll need to go into unmanaged debugging territory w/ windbg/cdb/ntsd :).
>
> -----Original Message-----
> From: users-bounces at lists.ironpython.com [mailto:users-bounces at lists.ironpython.com] On Behalf Of Tim Riley
> Sent: Thursday, August 30, 2007 11:55 AM
> To: Discussion of IronPython
> Subject: Re: [IronPython] Hosting: Delegates from Ironpython to C#
>
> Dino:
>
> I tried using the PythonDelegateWrapper you posted below with the same
> results. To make things easier I tried moving all my testing into a
> single C# file and have it run the code from C# instead of a compiling
> a python file. Below is the code. When I execute "regtest" in AutoCAD
> it fatal errors on me. I also added
> System.Environment.SetEnvironmentVariable("COMPlus_MDA", "1"); to the
> initialize of my code but it doesn't do anything for me. Any other
> ideas?
>
> ********code*********
> using System ;
> using System.Runtime.InteropServices;
> using Autodesk.AutoCAD.Runtime ;
> using Autodesk.AutoCAD.EditorInput;
> using Autodesk.AutoCAD.ApplicationServices;
> using Autodesk.AutoCAD.DatabaseServices;
>
> using IronPython.Hosting;
>
> namespace PyAcadDotNet
> {
>         /// <summary>
>         /// PyAcadCmd Class:
>         /// Used to register commands on the AutoCAD command stack.
>         /// </summary>
>         public class PyAcadCmd
>         {
>                 public PyAcadCmd()
>                 {
>                 }
>                 public delegate void CmdDelegate();
>         internal delegate void AddReference(object assembly);
>
>                 /// <summary>
>                 /// RegPyAcadCmd:
>                 /// Registers a delegate (callback) with the AutoCAD command string
>                 /// on the command stack.
>                 /// </summary>
>         [DllImport("PyRegCmd.dll",
>                          CallingConvention=CallingConvention.Cdecl,CharSet = CharSet.Unicode,
>                          EntryPoint = "?RegPyCmd@@YAXPB_W0HP6AXXZ at Z")]
>                 public static extern void RegPyCmd(
>                         string cmd_group,
>                         string cmd_name,
>                         Autodesk.AutoCAD.Runtime.CommandFlags cmd_flags,
>                         [MarshalAs(UnmanagedType.FunctionPtr)] CmdDelegate cmd_delegate);
>
>
>         public static void PythonRegister(string CommandName,
> CmdDelegate FuncPointer, CommandFlags flags)
>         {
>             RegPyCmd("_pycmds", CommandName, flags, new
> CmdDelegate(new PythonDelegateWrapper(FuncPointer).Invoke));
>         }
>
>         //testing stuff
>         public static void testcommand()
>         {
>             Editor ed =
> Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor;
>             ed.WriteMessage("\ncb1 delegate seems to work!\n");
>         }
>         [CommandMethod("regcmds")]
>         static public void test() // This method can have any name
>         {
>             CmdDelegate cb1 = new CmdDelegate(PyAcadCmd.testcommand);
>             PythonRegister("testcommand", cb1, CommandFlags.Session);
>         }
>         [CommandMethod("regtest", CommandFlags.Session)]
>         static public void regtest()
>         {
>             PythonEngine engine = new PythonEngine();
>             engine.Import("clr");
>             AddReference adr =
> engine.CreateMethod<AddReference>("clr.AddReference(assembly)");
>             adr(typeof(BlockTableRecord).Assembly);
>             adr(typeof(Editor).Assembly);
>             CompiledCode cc = engine.Compile(
>                 @"
> import Autodesk.AutoCAD.Runtime
> import clr
> clr.AddReference('PyAcadDotNet')
> from PyAcadDotNet import PyAcadCmd
> def foo():
>     print 'hello world'
> PyAcadCmd.PythonRegister('pythontester', foo,
> Autodesk.AutoCAD.Runtime.CommandFlags.Session)");
>             cc.Execute();
>         }
>         }
>
>     class PythonDelegateWrapper
>     {
>         private PyAcadCmd.CmdDelegate cmdDelegate;
>         public PythonDelegateWrapper(PyAcadCmd.CmdDelegate dlg)
>         {
>             cmdDelegate = dlg;
>         }
>         public void Invoke()
>         {
>             cmdDelegate();
>         }
>     }
> }
> ********code**********
>
>
>
> On 8/30/07, Dino Viehland <dinov at exchange.microsoft.com> wrote:
> > Well at this point we've successfully created the delegate and passed it off to you so it's hard to tell what's going wrong here.  Couple of suggestions on things to try and troubleshoot the issue:
> >         Instead of handing the delegate you get off to the P/Invoke call can you just turn around and invoke the delegate from C# code and successfully call back into the Python code?
> >         If that works can you do something like:
> >
> > class PythonDelegateWrapper {
> >         private CmdDelegate cmdDelegate;
> >         public PythonDelegateWrapper(CmdDelegate dlg) {
> >                 cmdDelegate = dlg;
> >         }
> >
> >         public void Invoke() {
> >                 cmdDelegate();
> >         }
> > }
> >
> > And then pass "new CmdDelegate(new PythonDelegateWrapper(dlg).Invoke)" to the P/Invoke function?
> >
> > The reason why I'm proposing this is maybe there's a strange interaction between dynamic methods (which the Python delegate will be) and the P/Invoke call - this might help isolate the issue.
> >
> > And the final thing that might be interesting to try would be to make sure you keep the reference to the delegate alive during the lifetime of the call.  In theory this should be happening for you automatically -   but if the unmanaged side is going to hold onto this delegate for longer than the duration of the call you'll need to do this anyway.  Given that you're still in the call this shouldn't be an issue but you could set the environment variable COMPlus_MDA=1 to enable CLR Managed Debugging Assistants to see if you get any warnings about that firing.  I don't really believe this could be happening but it would be consistent w/ the exception you're getting.
> >
> > Those are my 1st two guesses as to what could be going wrong, hopefully one of them will be helpful :).
> >
> > -----Original Message-----
> > From: users-bounces at lists.ironpython.com [mailto:users-bounces at lists.ironpython.com] On Behalf Of Tim Riley
> > Sent: Thursday, August 30, 2007 8:59 AM
> > To: Discussion of IronPython
> > Subject: Re: [IronPython] Hosting: Delegates from Ironpython to C#
> >
> > Dino:
> >
> > I was trying something similar to what you had posted and was getting
> > an error. I also just tried the code you gave me with a minor
> > correction to fix the CommandFlags part and received the error below.
> > If it helps I have also added the C# code use to call the python file
> > below the error if that will help at all.
> >
> >
> > *********ERROR***********
> > Command: pyfile
> > System.AccessViolationException: Attempted to read or write protected memory.
> > This is often an indication that other memory is corrupt.
> >    at PyAcadDotNet.PyAcadCmd.RegPyCmd(String cmd_group, String cmd_name, CommandFlags cmd_flags, CmdDelegate cmd_delegate)
> >    at PyAcadDotNet.PyAcadCmd.PythonRegister(String CommandName, CmdDelegate FuncPointer, CommandFlags flags) in C:\Documents and Settings\TJRiley\My Documents\pyacaddotnet\registercommand.cs:line 65
> >    at PythonRegister##20(Object , Object , Object )
> >    at IronPython.Runtime.Calls.CallTarget3.Invoke(Object arg0, Object arg1, Object arg2)
> >    at IronPython.Runtime.Calls.FastCallable3.Call(ICallerContext context, Object arg0, Object arg1, Object arg2)
> >    at IronPython.Runtime.Calls.BuiltinFunction.Call(ICallerContext context, Object arg0, Object arg1, Object arg2)
> >    at IronPython.Runtime.Operations.Ops.CallWithContext(ICallerContext context, Object func, Object arg0, Object arg1, Object arg2)
> >    at C:\Documents and Settings\TJRiley\My Documents\pyacaddotnet\Samples\commandmethod_test.py##22(ModuleScope )
> >    at IronPython.Hosting.CompiledCodeDelegate.Invoke(ModuleScope moduleScope)
> >    at IronPython.Hosting.CompiledCode.Run(ModuleScope moduleScope)
> >    at IronPython.Hosting.CompiledCode.Execute(EngineModule engineModule, IDictionary`2 locals)
> >    at IronPython.Hosting.CompiledCode.Execute()
> >    at PyAcadDotNet.AcadInterface.pythonfile() in C:\Documents and
> > Settings\TJRiley\My Documents\pyacaddotnet\PyAcadDotNet.cs:line 98
> > *********ERROR***********
> >
> >
> > *********CODE**************
> > using System;
> > using System.Collections;
> > using System.Windows.Forms;
> > using System.IO;
> > using System.Text;
> > using System.Runtime.InteropServices;
> >
> > using Autodesk.AutoCAD.ApplicationServices;
> > using Autodesk.AutoCAD.DatabaseServices;
> > using Autodesk.AutoCAD.Runtime;
> > using Autodesk.AutoCAD.EditorInput;
> >
> > using AcEd = Autodesk.AutoCAD.EditorInput;
> > using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;
> >
> > using IronPython.Hosting;
> >
> > namespace PyAcadDotNet
> > {
> >   public class AcadInterface : IExtensionApplication
> >   {
> >     static internal AcEd.Editor ed =
> > AcadApp.DocumentManager.MdiActiveDocument.Editor;
> >
> >     public delegate void TestDelegate();
> >
> >
> >     public void Initialize()
> >     {
> >       ed.WriteMessage("\nPyAcad.NET Loaded Successfully....");
> >       ed.WriteMessage("\ntype 'pyhelp' for commands....");
> >     }
> >
> >     public void Terminate()
> >     {
> >       this.Terminate();
> >     }
> >
> >     internal delegate void AddReference(object assembly);
> >
> >     [CommandMethod("pyfile", CommandFlags.Session)]
> >     static public void pythonfile()
> >     {
> >       using (PythonEngine engine = new PythonEngine())
> >       {
> >         using (AcadCommandLine myCommandLine = new AcadCommandLine())
> >         {
> >           try
> >           {
> >             // Create a new instance of PythonEngine and set variables.
> >             engine.AddToPath(Environment.CurrentDirectory);
> >             // Send Stdout and Stderr to the AutoCAD command line.
> >             engine.SetStandardOutput(myCommandLine);
> >             engine.SetStandardError(myCommandLine);
> >             engine.Import("clr");
> >             PyAcadCmd regcmds = new PyAcadCmd();
> >             engine.Globals.Add("regcmds", regcmds);
> >             //lets load some AutoCAD assemblies.
> >             AddReference adr =
> > engine.CreateMethod<AddReference>("clr.AddReference(assembly)");
> >             adr(typeof(BlockTableRecord).Assembly);
> >             adr(typeof(Editor).Assembly);
> >
> >             // Display an OpenFileDialog and run the script.
> >             OpenFileDialog ofd = new OpenFileDialog();
> >             ofd.Filter = "Python files (*.py)|*.py|All files (*.*)|*.*";
> >             ofd.ShowDialog();
> >
> >             // Run the file selected by the open file dialog box.
> >             //engine.ExecuteFile(ofd.FileName);
> >             CompiledCode cc = engine.CompileFile(ofd.FileName);
> >             cc.Execute();
> >           }
> >           catch (System.Exception e)
> >           {
> >             ed.WriteMessage(e.ToString());
> >           }
> >         }
> >       }
> >     }
> >  }
> >
> >   //
> >   public class AcadCommandLine : Stream
> >   //Modified version of a class coded by Mike Stall.
> >   {
> >     public AcadCommandLine()
> >     {
> >       //constructor
> >     }
> >
> >     #region unsupported Read + Seek members
> >     public override bool CanRead
> >     {
> >       get { return false; }
> >     }
> >
> >     public override bool CanSeek
> >     {
> >       get { return false; }
> >     }
> >
> >     public override bool CanWrite
> >     {
> >       get { return true; }
> >     }
> >
> >     public override void Flush()
> >     {
> >       //
> >     }
> >
> >     public override long Length
> >     {
> >       get { throw new NotSupportedException("Seek not supported"); }
> > // can't seek
> >     }
> >
> >     public override long Position
> >     {
> >       get
> >       {
> >         throw new NotSupportedException("Seek not supported");  // can't seek
> >       }
> >       set
> >       {
> >         throw new NotSupportedException("Seek not supported");  // can't seek
> >       }
> >     }
> >
> >     public override int Read(byte[] buffer, int offset, int count)
> >     {
> >       throw new NotSupportedException("Reed not supported"); // can't read
> >     }
> >
> >     public override long Seek(long offset, SeekOrigin origin)
> >     {
> >       throw new NotSupportedException("Seek not supported"); // can't seek
> >     }
> >
> >     public override void SetLength(long value)
> >     {
> >       throw new NotSupportedException("Seek not supported"); // can't seek
> >     }
> >     #endregion
> >
> >     public override void Write(byte[] buffer, int offset, int count)
> >     {
> >       try
> >       {
> >         // Very bad hack: Ignore single newline char. This is because
> > we expect the newline is following
> >         // previous content and we already placed a newline on that.
> >         AcEd.Editor ed = AcadApp.DocumentManager.MdiActiveDocument.Editor;
> >
> >         if (count == 1 && buffer[offset] == '\n')
> >           return;
> >
> >         StringBuilder sb = new StringBuilder();
> >         while (count > 0)
> >         {
> >           char ch = (char)buffer[offset];
> >           if (ch == '\n')
> >           {
> >             ed.WriteMessage(sb.ToString() + "\n");
> >             sb.Length = 0; // reset.
> >           }
> >           else if (ch != '\r')
> >           {
> >             sb.Append(ch);
> >           }
> >
> >           offset++;
> >           count--;
> >         }
> >         if (sb.Length > 0)
> >           ed.WriteMessage(sb.ToString() + "\n");
> >       }
> >       catch (System.Exception e)
> >       {
> >         throw e;
> >       }
> >     }
> >   }
> > }
> > *********CODE**************
> > On 8/30/07, Dino Viehland <dinov at exchange.microsoft.com> wrote:
> > > I think you should be able to just pass a function object to PythonRegister and it should be converted into a delegate.  For example:
> > >
> > > import clr
> > > clr.AddReference('PyAcadDotNet')
> > > from PyAcadDotNet import PyAcadCmd
> > >
> > > def foo():
> > >         print 'hello world'
> > >
> > > PyAcadCmd.PythonRegister('some command', foo, CommandFlags.Whatever)
> > >
> > > Does that not work?
> > >
> > > -----Original Message-----
> > > From: users-bounces at lists.ironpython.com [mailto:users-bounces at lists.ironpython.com] On Behalf Of Tim Riley
> > > Sent: Wednesday, August 29, 2007 7:10 PM
> > > To: Discussion of IronPython
> > > Subject: [IronPython] Hosting: Delegates from Ironpython to C#
> > >
> > > I'm embedding IronPython in a C# dll that is hosted inside a program
> > > called AutoCAD. In order to register commands in AutoCAD from .NET I
> > > need to P/Invoke a C function inside a .dll. I can do this fairly easy
> > > from C# but I can't figure out the right way to call my C# wrapper
> > > from IronPython to have it register the command. I have perused the
> > > hosting docs for 1.1 and haven't been able to come up with a solution
> > > that works. Here is my C# code. I either want to call the PyRegCmds
> > > void or the PythonRegister void.  Both of which expect a delegate.for
> > > example if I had a python function like:
> > >
> > > def test1:
> > >     print "This is a test".
> > >
> > > I can't figure out how to map test to the delegate required in the code below.
> > > Note: I can call this from C# fine. See :static public void test().
> > >
> > > Can anyone give me any pointers? It would be greatly appreciated.
> > >
> > >
> > > code:
> > >
> > > using System ;
> > > using System.Runtime.InteropServices;
> > > using Autodesk.AutoCAD.Runtime ;
> > > using Autodesk.AutoCAD.EditorInput;
> > >
> > > namespace PyAcadDotNet
> > > {
> > >         /// <summary>
> > >         /// PyAcadCmd Class:
> > >         /// Used to register commands on the AutoCAD command stack.
> > >         /// </summary>
> > >         public class PyAcadCmd
> > >         {
> > >                 public PyAcadCmd()
> > >                 {
> > >                 }
> > >                 public delegate void CmdDelegate();
> > >
> > >                 /// <summary>
> > >                 /// RegPyAcadCmd:
> > >                 /// Registers a delegate (callback) with the AutoCAD command string
> > >                 /// on the command stack.
> > >                 /// </summary>
> > >         [DllImport("PyRegCmd.dll",
> > >                          CallingConvention=CallingConvention.Cdecl,CharSet = CharSet.Unicode,
> > >                          EntryPoint = "?RegPyCmd@@YAXPB_W0HP6AXXZ at Z")]
> > >                 public static extern void RegPyCmd(
> > >                         string cmd_group,
> > >                         string cmd_name,
> > >                         Autodesk.AutoCAD.Runtime.CommandFlags cmd_flags,
> > >                         [MarshalAs(UnmanagedType.FunctionPtr)] PyAcadCmd.CmdDelegate cmd_delegate);
> > >
> > >
> > >         public static void PythonRegister(string CommandName,
> > > CmdDelegate FuncPointer, CommandFlags flags)
> > >         {
> > >             RegPyCmd("_pycmds", CommandName, flags, FuncPointer);
> > >         }
> > >
> > >         //testing stuff
> > >         public static void testcommand()
> > >         {
> > >             Editor ed =
> > > Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor;
> > >             ed.WriteMessage("\ncb1 delegate seems to work!\n");
> > >         }
> > >         [CommandMethod("regcmds")]
> > >         static public void test() // This method can have any name
> > >         {
> > >             CmdDelegate cb1 = new CmdDelegate(PyAcadCmd.testcommand);
> > >             PythonRegister("testcommand", cb1, CommandFlags.Session);
> > >         }
> > >         }
> > >
> > >
> > > }
> > > _______________________________________________
> > > Users mailing list
> > > Users at lists.ironpython.com
> > > http://lists.ironpython.com/listinfo.cgi/users-ironpython.com
> > > _______________________________________________
> > > Users mailing list
> > > Users at lists.ironpython.com
> > > http://lists.ironpython.com/listinfo.cgi/users-ironpython.com
> > >
> > _______________________________________________
> > Users mailing list
> > Users at lists.ironpython.com
> > http://lists.ironpython.com/listinfo.cgi/users-ironpython.com
> > _______________________________________________
> > Users mailing list
> > Users at lists.ironpython.com
> > http://lists.ironpython.com/listinfo.cgi/users-ironpython.com
> >
> _______________________________________________
> Users mailing list
> Users at lists.ironpython.com
> http://lists.ironpython.com/listinfo.cgi/users-ironpython.com
> _______________________________________________
> Users mailing list
> Users at lists.ironpython.com
> http://lists.ironpython.com/listinfo.cgi/users-ironpython.com
>


More information about the Users mailing list