Author Topic: Expose ARX Function to .NET  (Read 37293 times)

0 Members and 1 Guest are viewing this topic.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Expose ARX Function to .NET
« Reply #105 on: August 26, 2008, 01:36:26 AM »
There was a dll/sln a few posts back that pretty much did what you want, I'll dig it up and give it a going over. I downloaded sharpdev with IronPython addin, I'll knock up an example when I get time.
I'm waiting for some guidance on the swig forum for the other so I'll have a play with PyAcad.net for a bit.

As I was saying in the other thread, it may be a good solution for me in the short term, particularly if I can call straight python code from IPy later, I may just wrap my methods the hard way so I can use them all round(?)
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Expose ARX Function to .NET
« Reply #106 on: August 26, 2008, 11:58:27 PM »
Ok, try this for ac2008, I haven't had a chance today, maybe tonight. dll attached below.
ref it in a your .net file -

import/using PyAcadNET.PyAcadNET ;

create a delegate that will be the command method called when a command is called in acad, add the delegate to the Commands.load_command(cmd_group, cmd_name, cmd_globalname, cmd_flags, cmd_delegate);

load your new dll ito acad and see if it flys :)

Another quick note: if there are any problems it may be due to GC, you might have to 'pin' or make the delegate static so it doesn't get gc'd.
« Last Edit: August 27, 2008, 12:01:42 AM by MickD »
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Expose ARX Function to .NET
« Reply #107 on: August 27, 2008, 12:42:59 AM »
Actually, just thinking about it, if you want your commands to load automatically you will need to make it happen in the user defined IExtensionApplication derived class i.e.

public class App : IExtensionApplication

            Initialize()
         {
                               put your code here to load commands....

         }
      
         public void
            Terminate()
         {

         }
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Expose ARX Function to .NET
« Reply #108 on: August 27, 2008, 05:53:58 AM »
She's a goer! (for C# at least, I'm sure it would work for other .net languages though)

For some reason though it crashes the first time I load after a rebuild?? After that it loads and runs fine.

Here's a C# example of using the dll

Code: [Select]
// By MickD 27 Aug 2008. Use at your own risk!


using System;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using PyAcadNET.PyAcadNET; // our ref'd in dll to load commands

using acadApp = Autodesk.AutoCAD.ApplicationServices.Application;
namespace PytestCmds
{
    public delegate void callback(); // define a simple callback object, it can be used many times, see examples below.
    public class Commands
    {
        public Commands()
        {
            //
            // TODO: Add constructor logic here
            //
        }
        // Define a Command
        static public void test1()
        {
            // Put your command code here
            Editor ed = acadApp.DocumentManager.MdiActiveDocument.Editor;
            ed.WriteMessage("\nMessage from delegate method 1");
        }

        // Define another Command
        static public void test2()
        {
            // Put your command code here
            Editor ed = acadApp.DocumentManager.MdiActiveDocument.Editor;
            ed.WriteMessage("\nMessage from delegate method 2...w00t!!");
        }

    }
    public class App : IExtensionApplication
    {
        public void Initialize()
        {
            try
            {
                // Load our test callbacks:
                // 1
                callback py_clbk1 = new callback(Commands.test1);
                PyAcadNET.PyAcadNET.PyCommands.load_command("PYCMDS", "PYTEST1", "PYTEST1",
                                                            (int)CommandFlags.Modal, py_clbk1);
                Editor ed = acadApp.DocumentManager.MdiActiveDocument.Editor;
                ed.WriteMessage("\nLoaded command PYTEST1 successfully!");

                // and another one:
                callback py_clbk2 = new callback(Commands.test2);
                PyAcadNET.PyAcadNET.PyCommands.load_command("PYCMDS", "PYTEST2", "PYTEST2",
                                                            (int)CommandFlags.Modal, py_clbk2);
                ed = acadApp.DocumentManager.MdiActiveDocument.Editor;
                ed.WriteMessage("\nLoaded command PYTEST2 successfully!");
            }
            catch
            {
                Editor ed = acadApp.DocumentManager.MdiActiveDocument.Editor;
                ed.WriteMessage("\nFailed to load callback");
            }
        }
        public void Terminate()
        {
            //empty
        }
    }
    /// <summary>
    /// Summary description for Commands.
    /// </summary>

}


When Autocad loads a net dll it looks through the dll for attributes that define commands and probably does something similar to what we're doing here. As IronPython doesn't use attributes we need a work around, hopefully this is it.

How it works -
Another thing acad does when it loads the dll is it looks for an 'IExtensionApplication' interfaced class, you can only have one per dll but that's all you need to get acad and your dll talking together (as with any dll, there must be some type of 2 way interface code). The best part is that it can be used to load commands automatically at loading so all we have to do is define our commands, call the 'load_command' method in the Initialize() method of the IExtensionApplication class and you're ready to rock.

If someone wants to have a crack at writing an IronPython one for a test that would be great.

EDIT: I think the cause of the crash is coming from garbage collection, if I leave it for a while and come back 'boom' crash.
What's the best way to 'pin' the delegate/s from the garbage collector using 'safe' code?
« Last Edit: August 27, 2008, 07:11:06 AM by MickD »
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Expose ARX Function to .NET
« Reply #109 on: August 27, 2008, 07:16:04 AM »
This seems to behave better, note the static delegates -

Code: [Select]
    public class App : IExtensionApplication
    {
        static callback py_clbk1 = new callback(Commands.test1);
        static callback py_clbk2 = new callback(Commands.test2);
        public void Initialize()
        {
            try
            {
                // Load our test callbacks:
                // 1             
                PyAcadNET.PyAcadNET.PyCommands.load_command("PYCMDS", "PYTEST1", "PYTEST1",
                                                       (int)CommandFlags.Modal, py_clbk1);
                Editor ed = acadApp.DocumentManager.MdiActiveDocument.Editor;
                ed.WriteMessage("\nLoaded command PYTEST1 successfully!");

                // and another one:
                PyAcadNET.PyAcadNET.PyCommands.load_command("PYCMDS", "PYTEST2", "PYTEST2",
                                                            (int)CommandFlags.Modal, py_clbk2);
                ed = acadApp.DocumentManager.MdiActiveDocument.Editor;
                ed.WriteMessage("\nLoaded command PYTEST2 successfully!");
            }
            catch
            {
                Editor ed = acadApp.DocumentManager.MdiActiveDocument.Editor;
                ed.WriteMessage("\nFailed to load callback");
            }
        }
        public void Terminate()
        {
            //empty
        }
    }
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Expose ARX Function to .NET
« Reply #110 on: August 27, 2008, 10:22:30 PM »
Here's a rough outline for compilation in IronPython if someone can have a go at it (I don't have it with me here). It may need some tweaking of course, my Python is pretty rough :)

The main issue is the callback, at the moment I'm just assigning the function def (which should be 'static' as well). I need to do some more study in this area. I think the IExtensionApplication interface is created ok but we'll see. The command flags need to be cast to an int as well, I'll fix this in the C++ code so it won't be a problem in the future.

Anyway, if we can get this to work we have a fully functional PyAcad.Net ready for work and play :)

Code: [Select]
# By MickD 27 Aug 2008. Use at your own risk!

import System
import Autodesk.AutoCAD.Runtime
import Autodesk.AutoCAD.ApplicationServices
import Autodesk.AutoCAD.DatabaseServices
import Autodesk.AutoCAD.EditorInput
import PyAcadNET.PyAcadNET
import Autodesk.AutoCAD.ApplicationServices.Application as acadApp


# define a simple callback object, it can be used many times, see examples below.
class Commands:
def constructor():
pass
 
def test1():
ed = acadApp.DocumentManager.MdiActiveDocument.Editor
ed.WriteMessage('\nMessage from delegate method 1')


# Define another Command
def test2():
ed = acadApp.DocumentManager.MdiActiveDocument.Editor
ed.WriteMessage('\nMessage from delegate method 2...w00t!!')


class App(IExtensionApplication):

def Initialize():
# Load our test callbacks:
# 1
try:
py_clbk1 = callback(Commands.test1)
PyAcadNET.PyAcadNET.PyCommands.load_command('PYCMDS', 'PYTEST1', 'PYTEST1', CommandFlags.Modal, Commands.test1)
ed = acadApp.DocumentManager.MdiActiveDocument.Editor
ed.WriteMessage('\nLoaded command PYTEST1 successfully!')

# and another one:
py_clbk2 = callback(Commands.test2)
PyAcadNET.PyAcadNET.PyCommands.load_command('PYCMDS', 'PYTEST2', 'PYTEST2', CommandFlags.Modal, Commands.test2)
ed = acadApp.DocumentManager.MdiActiveDocument.Editor
ed.WriteMessage('\nLoaded command PYTEST2 successfully!')
except:
ed = acadApp.DocumentManager.MdiActiveDocument.Editor
ed.WriteMessage('\nFailed to load callback')

def Terminate():
pass
#empty

If you can compile it and post it that would be good, I can then go about debugging.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Expose ARX Function to .NET
« Reply #111 on: August 27, 2008, 10:45:52 PM »
Ahh, ok, I see a the bigger problem now - http://www.infoq.com/news/IronPython

So you can't build a .net dll with IronPython?
Ok, now I have some more work to do, I always thought you created the Ipy dll and netloaded it into acad but that's not the case. This is ok, I'll just have to come up with a workaround, something like I was doing with the straight python bindings, we can load an 'init.py' or similar that loads the commands and calls the script with the method.

"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

tjr

  • Guest
Re: Expose ARX Function to .NET
« Reply #112 on: August 28, 2008, 12:01:08 AM »
Ahh, ok, I see a the bigger problem now - http://www.infoq.com/news/IronPython

So you can't build a .net dll with IronPython?
Ok, now I have some more work to do, I always thought you created the Ipy dll and netloaded it into acad but that's not the case. This is ok, I'll just have to come up with a workaround, something like I was doing with the straight python bindings, we can load an 'init.py' or similar that loads the commands and calls the script with the method.
Whoa Mick, you've been going to town. Very nice work.

Sadly, however, you are correct in IronPython not creating "real" .NET dlls, meaning you can compile to a dll but not one that can be consumed by C# due to it's dynamic nature. This is why a while back I was asking about wrapping the C++ method for registering commands in C# to use for IronPython.

I haven't investigated moving to IronPython 2.0 which leverages the DLR which may solve this issue.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Expose ARX Function to .NET
« Reply #113 on: August 28, 2008, 12:04:04 AM »
We still may be able to do this, I have the cmd loader covered, what we may need to do though is have one dll (like an arx) to prep the environment for scripting and set up commands how I did with the other python work, I assumed being dotnet that it produced dll's, now I know it doesn't I just need to change tack a bit ;)
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: Expose ARX Function to .NET
« Reply #114 on: August 28, 2008, 12:22:23 AM »
Ahh, ok, I see a the bigger problem now - http://www.infoq.com/news/IronPython

So you can't build a .net dll with IronPython?
Ok, now I have some more work to do, I always thought you created the Ipy dll and netloaded it into acad but that's not the case. This is ok, I'll just have to come up with a workaround, something like I was doing with the straight python bindings, we can load an 'init.py' or similar that loads the commands and calls the script with the method.



Yes that was the problem I ran into. I think I had some success with storing the compiled code generated from engine.CompileFile(filename)
along with a command name in a hash table. The command would just call a worker function that would in turn call the execute method.
(ie cc.Execute()). But was still executing the File as a whole, in which case one would only need to store the path to the file and the command name.

The next step would be to look inside the compiled code, it should be MSIL

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Expose ARX Function to .NET
« Reply #115 on: August 28, 2008, 07:12:01 AM »
I've done some study of the IPy embedded engine which I notice you use in the Pyacad.net source.
I'd like to get into this a bit more but it's probably quicker (since Dan has already covered this ground a bit further) if I explain what I would try if coding this. I really don't have the time at the moment but I'm keen to see it happen, I know it can be done :)

In the dll that get's loaded into acad -

create the extension app class - done
create a static embedded engine - done
add a module that can be imported into scripts from the engine (CreateModule/Import??), this is the place where our scripts can 'import' the load command method from.

When I embedded an interpreter in arx I could create an extension module in the arx that can be imported into the script (only) at run time, I think this is what the engine.Import and engine.Globals are for, they create a module and add methods/variables to the module as I understand it.
We may be able to have a default 'initpyacad' script which the engine imports and executes an init method which adds the commands to the cmd_stack dictionary which then get loaded in the dll using the wrapped load command method in C#. From this script you should be able to import other scripts with much more detailed classes and code, we only need this file to create the 'glue' into IronPython and acad.

perhaps the code outline below will help explain -
Code: [Select]
    public class App : IExtensionApplication
    {
        private Dictionary<string, callback> cmdstack;
        static PythonEngine pe;
        public void Initialize()
        {
            pe = new PythonEngine();
            pe.Import("PyacadNET");
            pe.Globals["cmd_stack"] = cmdstack;
            try
            {
                // The Method:
                // import/compile a file we will use as our 'init' base file,
                // this file is the entry point into IronPython from acad which
                // loads commands into the cmdstack (key, callback delegate).
                // once the file has been called and the cmdstack filled, call
                // the load_commands method to add each of them to the arx command stack.
                // If we can't use callbacks directly, we can still use a dictionary to add
                // a command string which is added to arx along with the method def 'name' string
                // which can be called by the embedded engine.
                // eg. from the init script, this gets called from the engine -
                // def init_pyacadnet():
                //      PyacadNET.cmd_stack.Add("CMDNAME","method_def_name")#for each command we want to load
                //
                // The way this works is we have a default method (delegate) in this file
                // which can be added to the dictionary, this callback gets added to every
                // dictionary entry. This delegate gets registered with arx along with every command name.
                // When a command is called, in the default command delegate method we can get the command
                // string being called, get the script method name form the dict using the command
                // name as key and ask the engine to call the method by it's string name from the setup script.       
            }
            catch
            {

            }
        }
        public void Terminate()
        {
            //empty
        }
    }

hope that made sense. I will dig up the 'get command string' method tomorrow, if it's not available from .net I'll wrap it with the load commands method.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Krasyn

  • Guest
Re: Expose ARX Function to .NET
« Reply #116 on: September 01, 2008, 06:23:47 AM »
Callback.cs (I don't know how to define certain delegate in IronPython
Code: [Select]
namespace Callback
{
public delegate void  Callback();
}

script:
Code: [Select]
import clr
import System
clr.AddReference(System.Reflection.Assembly.LoadFile(ur'c:\Program Files\IronPython\PyAcadNET.dll'))
clr.AddReference(System.Reflection.Assembly.LoadFile(ur'c:\Program Files\IronPython\Callback.dll'))


from PyAcadNET.PyAcadNET.PyCommands import load_command
from Callback import Callback
from  Autodesk.AutoCAD.Runtime import CommandFlags

class Command:
@classmethod
def hello(cls):
print 'hello '

def good_bye():
print 'good_bye '

dlgt1 = Callback(Command.hello)
dlgt2 = Callback(good_bye)

load_command(u'hello_group',u'hello',u'hello',int(CommandFlags.Modal),dlgt1)
load_command(u'good_bye',u'good_bye',u'good_bye',int(CommandFlags.Modal),dlgt2)

And the result:
Code: [Select]
System.AccessViolationException: Attempted to read or write protected memory.
This is often an indication that other memory is corrupt.

As IronPython doesn't produce assemblies there is no possibilityto pass python functions pointers to ACAD?
No commands registration, no reactors?

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Expose ARX Function to .NET
« Reply #117 on: September 01, 2008, 06:39:53 AM »
I understand what you are saying, but it's not that black and white. When a python script is loaded into the embedded interpreter/clr the callback should be available as the code is compiled into the same 'runtime' code space if you like so the callback gets 'created' at that time.
I have this working with native Python, just need to work out the IronPython way. There is a way to create callbacks in IP but the trick is setting up the embedded IP interpreter into a C# dll which isn't that hard if a bit of time was spent studying the help doc's examples.

I'm still battling along with swig and arx at the moment and very slowly inching forward, I haven't had time to go any further with the outline I posted above which I think has a fair chance of working with a bit of study. I'm sure it can be done, though there are/will be a few hurdles that IP may hit as it's a dynamic language and .net is statically typed but we'll see I guess.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Krasyn

  • Guest
Re: Expose ARX Function to .NET
« Reply #118 on: September 01, 2008, 12:25:10 PM »
I understand what you are saying, but it's not that black and white. When a python script is loaded into the embedded interpreter/clr the callback should be available as the code is compiled into the same 'runtime' code space if you like so the callback gets 'created' at that time.
I have this working with native Python, just need to work out the IronPython way. There is a way to create callbacks in IP but the trick is setting up the embedded IP interpreter into a C# dll which isn't that hard if a bit of time was spent studying the help doc's examples.

I'm still battling along with swig and arx at the moment and very slowly inching forward, I haven't had time to go any further with the outline I posted above which I think has a fair chance of working with a bit of study. I'm sure it can be done, though there are/will be a few hurdles that IP may hit as it's a dynamic language and .net is statically typed but we'll see I guess.
I want to thank you for your explanation  and hope to see your embedded CPython in action soon. I think that Python is  the best scripting language to replace autolisp and vba which I don't like. I want Python to become primary scripting language for AutoCAD.
Before I found IronPython I had tried to use comtypes and win32com library shipped with ActivePython to script AutoCAD via COM but these libraries have some limitations and features 'not yet implemented'. PyAcadNet allows me to use all COM functionality and use autocad.exe as an in-process server like vba does. It would be fine if net.api worked too not only a subset. 

tjr

  • Guest
Re: Expose ARX Function to .NET
« Reply #119 on: September 01, 2008, 02:49:47 PM »
PyAcadNet allows me to use all COM functionality and use autocad.exe as an in-process server like vba does. It would be fine if net.api worked too not only a subset. 
Are you saying only a subset of the .NET API works in PyAcad.NET? If so please explain what you can't do? If it's registering native AutoCAD commands that's a known issue. I'd be interested in knowing of anything else.