TheSwamp

Code Red => .NET => Topic started by: TR on August 23, 2007, 01:27:09 PM

Title: Expose ARX Function to .NET
Post by: TR on August 23, 2007, 01:27:09 PM
Could anyone give me a pointer on how to to write an ARX wrapper around AcEdCommandStack::addCommand() and expose it .NET?

Is this even possible?
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 23, 2007, 02:17:29 PM
Sure, it’s possible and it might already be done for you (at least by example)
In reflector->acmgd do a search for AddCommand and you should find AcMgCommandClass.AddCommand. 
To make the code more readable, you might want to get the C++/CLI plug-in for reflector

Dan
Title: Re: Expose ARX Function to .NET
Post by: TR on August 23, 2007, 04:33:09 PM
Cant' find it in the 2008 version of acmgd, I can find "AcMgCommandClass" but not the AddCommand method.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 23, 2007, 04:42:44 PM
what you might need to do is write a C++/cli dll class that wraps the native 'add command' function, I have vs2002 loaded on my lap top, I'll see if I can knock something up later or over the weekend if you like, you should then be able to ref it into your project and call it passing the params required for the command...hopefully, the callbacks may be a bit tricky!
Title: Re: Expose ARX Function to .NET
Post by: TR on August 23, 2007, 04:49:38 PM
I'll take any help I can get. I'm not going to pretend I know C++ well enough to do any ARX stuff.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 23, 2007, 04:55:37 PM
No prob's, if it works you won't have to ;)

On another note (assuming you want to plug python into acad) I have been doing quite a bit with embedding the python interpreter into an application and have it down pretty good. The downside is it would probably be easier to just write wrappers for the functions you need rather than swig the whole lib (same as com has done basically), a lot of work!
If you're interested I could get something going open source and maybe some members or others can contribute where they can in writing the wrappers. It works quite good but it wouldn't be as rich as the .net lib's you're working with now though.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 23, 2007, 05:09:47 PM
I have been exchanging emails with Kean Walmsley trying to get some kind of functionality going that would allow me to register AutoCAD command line callable commands from IronPython. Basically he told me that I'd have to write a wrapper to the ARX function and expose it to .NET. If I can get that going then I have reached the last step required before I can proceed with my rewrite of PyAcad.NET. Registering commands was the last hurdle, and if I can do that than I should be able to provide a pretty solid development platform for AutoCAD via IronPython.

I like your idea, and it seems like it would be similiar to PyAcad (http://pyacad.sourceforge.net/) but I'd prefer to keep going with IronPython instead of using the CPython engine. Using IronPython it is fairly simple to manipulate AutoCAD via the .NET API. I'm also a huge fan of using .NET winforms in IronPython.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 23, 2007, 05:22:48 PM
No prob's and it would be easier. I'll load vs onto this machine shortly, I have a bit of spare time later and see what we can do.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 23, 2007, 05:30:42 PM
Sounds great. What version of AutoCAD are you running these days? Also what version of MS C++ are you going to be using? The C++ 2005 Express Edition or VS.NET 2002?
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 23, 2007, 05:39:54 PM
What I'll do is set up a vs2002 project for AC2006, once the framework is up it wouldn't be hard to convert to a later solution.
I have both VS on my other machine but it's 'in the shop' at the moment with mb prob's, should have it going next week I hope.

WRT pyacad, yes it is very similar only he is using ads functions for entity creation. I would start form scratch though if I was to do it and wrap the arx api. I still might yet, Bricscad is pushing really hard to make the new DRX sdk compatible with existing ARX so it may be worth it. The new BricsCAD also uses wxWidgets as its gui and there should be a native Linux version around the corner!
But that's another story, let's see if we can get this going :)
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 23, 2007, 10:29:02 PM
Cant' find it in the 2008 version of acmgd, I can find "AcMgCommandClass" but not the AddCommand method.

It’s is in 2008 as well,

One of the arguments that AcEdCommandStack::addCommand() needs,
is the memory address of the method that gets invoked(your function).This is the signature from the docs

Code: [Select]
addCommand(
const ACHAR * cmdGroupName,
const ACHAR * cmdGlobalName,
const ACHAR * cmdLocalName,
Adesk::Int32 commandFlags,
AcRxFunctionPtr FunctionAddr, /* <<<<<------------ this one */
AcEdUIContext * UIContext = NULL,
int fcode = -1,
HINSTANCE hResourceHandle = NULL,
AcEdCommand** cmdPtrRet = NULL) ;

The wrapped version uses a reference(MethodInfo) to the method that gets invoked

Code: [Select]
void AcMgCommandClass.AddCommand(const const AcMgCommandClass* local1, ICommandLineCallable^ ca, MethodInfo^ mi)
The methods get invoked through one of the helper functions AcMgCommandClass.Invoke or AcMgCommandClass.InvokeWorker, both expect a Reflection.MethodInfo argument.

So my question is, which one of the two arguments would you like to use, Reflection.MethodInfo or AcRxFunctionPtr?

If it’s Reflection.MethodInfo, I already have code that we can use to cook up your command loader.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 23, 2007, 10:52:53 PM
Maybe it's the couple of beers I have in me but I'm pretty sure you're talking way over my head. If you already have the code using MethodInfo ready then I'll give that a shot. I'm not sure if you can reflect on IronPython compiled code though.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 23, 2007, 11:11:46 PM
this is what I have so far just wrapping the acedRegCmds() that does the hard work for you -

Code: [Select]
void IronPy::CommandReg::IronPyCommandReg::RegisterCommand(String* cmd_group,
   String* cmd_global_name,
   String* cmd_local_name,
   System::Int32 cmd_flags,
   System::IntPtr pyFunction)
{
acedRegCmds->addCommand(StringToCIF(cmd_group),
StringToCIF(cmd_global_name), StringToCIF(cmd_local_name),
(unsigned int)cmd_flags,
/**** This is the bit that needs work maybe!, we have to marshal a delegate/method
for acad to call when the registered command is invoked ------------------*/
(AcRxFunctionPtr)(void*)Marshal::GetUnmanagedThunkForManagedMethodPtr(pyFunction,0,0));
}

this compiles and the dll is attached, ref in the dll into your project and see how it goes. You may need to use the command flags, these have been made visible to the managed wrapper for convenience.
Let me know how it goes...if it does at all, it's just a quick hack to get something to compile and work with.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 23, 2007, 11:18:07 PM
Just thinking about it, maybe the functionptr needs 'pinning' so it doesn't get GC'd?? Oh well, we'll find out I guess :)

BTW, I tried to add the solution but it was over 9.5 meg zipped!! the project file is over 32meg in release build and cleaned!  :-o

That's pretty big for a one function wrapper  :?
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 24, 2007, 12:03:18 AM
Here's the source of the only two files and code I added to a standard mixed arx/net application wizard solution in VS2002. If you have any trouble creating the solution just holler.

The header file
Code: [Select]
// IronPyCommandReg.h
// Created: 24th Aug. 2007
// By: Mick Duprez DCS Pty Ltd.
// Usage:
// Used to register commands from IronPython scripts using
// the .net wrapper api for AutoCAD.

#pragma once

#include "stdafx.h"
using namespace System ;
using namespace Autodesk::AutoCAD::Geometry ;
using namespace Autodesk::AutoCAD::DatabaseServices ;

namespace IronPy {
namespace CommandReg {
public __gc class IronPyCommandReg
{
public:
IronPyCommandReg(void);
~IronPyCommandReg(void);
static void RegisterCommand(String* cmd_group,
String* cmd_global_name,
String* cmd_local_name,
System::Int32 cmd_flags,
System::IntPtr pyFunction);
// command flag def's exposed to .net, may not be required???
static int CMD_MODAL = ACRX_CMD_MODAL;
static int CMD_TRANSPARENT = ACRX_CMD_TRANSPARENT;
static int CMD_USEPICKSET = ACRX_CMD_USEPICKSET ;
static int CMD_REDRAW = ACRX_CMD_REDRAW;
static int CMD_NOPERSPECTIVE = ACRX_CMD_NOPERSPECTIVE ; 
static int CMD_NOMULTIPLE = ACRX_CMD_NOMULTIPLE;   
static int CMD_NOTILEMODE = ACRX_CMD_NOTILEMODE ;
static int CMD_NOPAPERSPACE = ACRX_CMD_NOPAPERSPACE ;
static int CMD_PLOTONLY = ACRX_CMD_PLOTONLY ;
static int CMD_NOOEM = ACRX_CMD_NOOEM;
static int CMD_UNDEFINED = ACRX_CMD_UNDEFINED;
static int CMD_INPROGRESS = ACRX_CMD_INPROGRESS;
static int CMD_DEFUN = ACRX_CMD_DEFUN ;
static int CMD_NONEWSTACK = ACRX_CMD_NONEWSTACK ;
static int CMD_NOINTERNALLOCK = ACRX_CMD_NOINTERNALLOCK;
static int CMD_DOCREADLOCK = ACRX_CMD_DOCREADLOCK;
static int CMD_DOCEXCLUSIVELOCK = ACRX_CMD_DOCEXCLUSIVELOCK;
static int CMD_INTERRUPTIBLE = ACRX_CMD_INTERRUPTIBLE;
static int CMD_NOHISTORY = ACRX_CMD_NOHISTORY ;
static int CMD_NO_UNDO_MARKER = ACRX_CMD_NO_UNDO_MARKER;
static int CMD_NOBEDIT = ACRX_CMD_NOBEDIT;
};
}
}

The cpp file
Code: [Select]
// IronPyCommandReg.cpp
// Created: 24th Aug. 2007
// By: Mick Duprez DCS Pty Ltd.
// Usage:
// Used to register commands from IronPython scripts using
// the .net wrapper api for AutoCAD.

#include "StdAfx.h"
#include "IronPyCommandReg.h"

//-----------------------------------------------------------------------------
//- Template class that wraps GCHandle from mscorlib.dll
#include <gcroot.h>

//-----------------------------------------------------------------------------
//- autodesk interop header, to aid converting between unmanaged ObjectARX and managed ObjectARX.NET
#include "mgdinterop.h"
using namespace System::Runtime::InteropServices;

IronPy::CommandReg::IronPyCommandReg::IronPyCommandReg(void)
{
}

IronPy::CommandReg::IronPyCommandReg::~IronPyCommandReg(void)
{
}

void IronPy::CommandReg::IronPyCommandReg::RegisterCommand(String* cmd_group,
String* cmd_global_name,
String* cmd_local_name,
System::Int32 cmd_flags,
System::IntPtr pyFunction)
{
acedRegCmds->addCommand(StringToCIF(cmd_group),
StringToCIF(cmd_global_name), StringToCIF(cmd_local_name),
(unsigned int)cmd_flags,
/**** This is the bit that needs work maybe!, we have to marshal a delegate/method
for acad to call when the registered command is invoked ------------------*/
(AcRxFunctionPtr)(void*)Marshal::GetUnmanagedThunkForManagedMethodPtr(pyFunction,0,0));
}

<edit> fixed formatting
Title: Re: Expose ARX Function to .NET
Post by: TR on August 24, 2007, 10:28:21 AM
Thanks, I'll give it a shot. I don't know if I can use the ARX templates from Autodesk so I might have to manually recreate the template. I don't think I was able to get the templates to install correctly with Visual C++ Express 2005.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 24, 2007, 12:17:34 PM
Got the templates to work correctly with C++ Express. However now I'm missing tons of header files and I guess I have to download a 1+GB SDK to get windows.h. Am I wrong? I searched my system for the file and it was nowhere to be found.
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 24, 2007, 01:03:40 PM
I have put Mick's wrapper in a VS2005 solution and compiled it for Acad2008
you can test it by running the pyver command.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 24, 2007, 01:28:04 PM
Great, thanks a lot. Now I referenced it in C# and I can call the command but I don't really understand all the argument it's looking for. Any way I could get a sample of how to use it in C#?
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 24, 2007, 02:50:08 PM
Here is another one that includes wrappers for acedDefun as PyDefun and acedUndef as PyUnDef.

Honestly, I don’t know enough about IronPython to be able to give an example.
But now we have a couple of ways to register a command name,
now we just need to figure out how to invoke PyAcad.NET functions when the command is called.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 24, 2007, 03:10:38 PM
I don't need an example in IronPython. If you can give me an example of how to use these to wrap a simple C# function like the follow it would be great. I will be able to translate it to IronPython.

Code: [Select]
static public void testcommand()
{
ed.WriteMessage("Command Test Worked!");
}
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 24, 2007, 08:50:21 PM
an example -
IronPyCommandReg.RegisterCommand("timsapp","TEST","TESTINSPANISH",CMD_MODAL,testcommanddelegate)


to use the dll, all you need to do is add a ref into your project and call IronPyCommandReg.RegisterCommand("mygroup","myglobalcommandname","mylocalcomname",CMD_MODAL,mydotnetfuncname)
It's a static function so you don't need to create an instance, just use the class name then the '.' operator to call the function
where the 'group' is your application group, you add any commands you register to this group typically, global and local names are the command names that you issue at the command promt such as MYLINE, the command flags are just that, you can 'or' them together using the '|' operator, the last param is the function name that acad calls (the callback) when someone enters the command, you may need to create a delegate to pass to this function to work (most probably, a delegate is basically a function pointer used for callbacks).

You need to use that function for every command you want to register before you can use them, what it does is like I just explained, it registers the command name in a stack along with the function (address of) to call. You would typically do this when you load your dll into acad using the IExtensionblah blah (sorry, I forget) interface.

Like I said before, I have no idea if the callback will work but lets see, I also have no idea what Daniel has done with the code with all the '^''s :( It seems MS have done another good job of bastardizing a good language  :roll: but if it works .....
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 24, 2007, 09:25:43 PM
Actually, if anyone has some spare time this can be used from C# also, should be a good exercise in creating and using delegates ;)
Title: Re: Expose ARX Function to .NET
Post by: TR on August 25, 2007, 01:07:00 PM
Ok I was doing some testing just now and here is what I have. From a C# project I refereneced the mixed dll that Daniel has so kindly compiled for me. In my class I declared a public delegate like so:
Code: [Select]
public delegate void TestDelegate(); I then added the following code to the project:
Code: [Select]
      [CommandMethod("testreg", CommandFlags.Session)]
      static public void testreg()
      {
          TestDelegate testmycommand = new TestDelegate(testcommand);

          IronPy.CommandReg.IronPyCommandReg.RegisterCommand("tester", "testcommand", "testcommand", IronPy.CommandReg.IronPyCommandReg.CMD_MODAL, testmycommand());
      }

      static public void testcommand()
      {
          ed.WriteMessage("Command Test Worked!");
      }

When I got to build I get the following two errors:
Quote
Error   1   The best overloaded method match for 'IronPy.CommandReg.IronPyCommandReg.RegisterCommand(string, string, string, int, System.IntPtr)' has some invalid arguments   C:\Documents and Settings\TJRiley\My Documents\pyacaddotnet\PyAcadDotNet.cs   110   11   PyAcadDotNet
Error   2   Argument '5': cannot convert from 'void' to 'System.IntPtr'   C:\Documents and Settings\TJRiley\My Documents\pyacaddotnet\PyAcadDotNet.cs   110   148   PyAcadDotNet
So from there I see that a delegate isn't going to work and I'll have to use pointers in my C# code and compile it with the unsafe flag. Am I correct?
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 25, 2007, 06:48:35 PM
What we need to find out is how to pass a delegate to native code and it won't be trivial but it can be done. There should be no need for unsafe code as that part is handled with the dll.

Here's a link to something that should work (http://www.heege.net/blog/default.aspx), Daniel may have time over the weekend to have a look, hope it helps.

BTW, because you are registering the command using the new reg command function you no longer need the CommandMethod attributes, you may want to change the flag from CMD_MODAL to CMD_SESSION if that's what you need ;)
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 25, 2007, 07:03:52 PM
Just thinking about it I can't see why you can't just use P/Invoke ?!?

The issue is the same but saves a lot of messing around with a dll and 'hoping' we're doing it right!

I'll have a look around and see what turns up.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 25, 2007, 07:13:31 PM
Have a look through this, it gives you a simple a example of what you need to do, in your case it's easier as you don't need to marshal parameters, the other hard part may be finding the 'scrambled' function name as it calls a C++ class method.

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconcallbacksample.asp
Title: Re: Expose ARX Function to .NET
Post by: LE on August 25, 2007, 07:33:52 PM
I just tried to follow what it is in here:

http://through-the-interface.typepad.com/through_the_interface/2006/07/calling_objecta.html

With the latest arx code I have.... but I have to place the my arx where the acad.exe is located and appears that I need to copy those dll's in the same spot.... will see if works...
Title: Re: Expose ARX Function to .NET
Post by: TR on August 25, 2007, 11:08:17 PM
When I discussed this with Kean Walmsley he said it wasn't possible to access this through P/Invoke. Below are the emails exchanged between us. Hopefully they will help out.
Quote
Tim Riley <riltim@gmail.com>    Wed, Aug 22, 2007 at 9:45 PM
To: kean.walmsley@autodesk.com
Kean:

I am working on an open source project[1] to allow to the use of
Python as as a programming language for AutoCAD 2008 via
IronPython[2]. The code I have now compiles and I am able to
manipulate AutoCAD via the .NET API through code run from a python
file, which I never thought I could do. However the next task I need
to tackle before I'm 100% confident this is a worthwhile project to
devote a great deal of my time to is register AutoCAD commands via
code. IronPython doesn't support .NET attributes and probably never
will. This leaves me with the daunting task of somehow figure out a
way to register command line commands through code instead of the
CommandMethod attribute.

I have asked around on the newsgroups with little success. The best I
was able to receive was advice about System.Reflection. But since my
code is compiled in memory within a hosted application I'm thinking
this isn't the way I need to be going. I know the CommandMethod
attribute is just a wrapper for some function, I just can't figure out
what that function is and how to make my python stuff command line
callable.

Any pointers you could give me on this subject would be greatly appreciated.

Thanks,
Tim Riley

[1] http://code.google.com/p/pyacaddotnet/
[2] http://www.codeplex.com/IronPython
Kean Walmsley <kean.walmsley@autodesk.com>    Thu, Aug 23, 2007 at 4:21 AM
To: Tim Riley <riltim@gmail.com>
Tim,

The underlying ObjectARX (C++) function used to register a command is
AcEdCommandStack::addCommand(). There's typically only one
AcEdCommandStack in an AutoCAD session, which is generally accessed from
C++ via the acedRegCmds macro:

acedRegCmds->addCommand("CMD_GROUP", "GLOBAL_CMD_NAME",
"LOCAL_CMD_NAME", ACRX_CMD_MODAL | ACRX_CMD_USEPICKSET, func1);

I don't know whether it's possible to access this from IronPython - I
suspect you might need to include a small ObjectARX module that exposes
a C function or implements a managed wrapper that IronPython can call.

Cheers,

Kean
[Quoted text hidden]
Tim Riley <riltim@gmail.com>    Thu, Aug 23, 2007 at 9:35 AM
To: Kean Walmsley <kean.walmsley@autodesk.com>
Kean:

Thanks for the reply.

So if I understand what you said correctly AutoCAD's .NET
CommandMethod attribute uses the ARX AcEdCommandStack::addCommand()
function to register command line callable commands? If so I'm
assuming that I could also wrap the ARX command in C#, correct? As
long as I can get a function in C# to register a command I can use it
in IronPython as I can pass .NET objects to my IronPython.Engine and
then use them in my Python code.

Thanks,
Tim
[Quoted text hidden]
Kean Walmsley <kean.walmsley@autodesk.com>    Thu, Aug 23, 2007 at 10:00 AM
To: Tim Riley <riltim@gmail.com>
Tim,

Yes - this is ultimately how .NET-defined commands are registered. Our
managed wrapper (a mixed mode module) calls through to the C++ API to
register commands.

But calling this from C# is not that easy: the class is not exposed
directly - the way to register commands is via attributes - and as this
is a class method there's no way to P/Invoke it directly.

Cheers,

Kean

-----Original Message-----
From: Tim Riley [mailto:riltim@gmail.com]
[Quoted text hidden]
Tim Riley <riltim@gmail.com>    Thu, Aug 23, 2007 at 11:21 AM
To: Kean Walmsley <kean.walmsley@autodesk.com>
Kean:

So just to be clear I would have to write an ARX wrapper around
AcEdCommandStack::addCommand() and expose it .NET? Then I would be
able to access it from C# and ultimately IronPython?

I've never attempted to create a C++ ARX file, is it safe to assume I
can do this with the C++ express edition or do I need to get full
blown visual studio?

Thanks again for your help.
[Quoted text hidden]
Kean Walmsley <kean.walmsley@autodesk.com>    Fri, Aug 24, 2007 at 2:31 AM
To: Tim Riley <riltim@gmail.com>
Tim,

You could either expose it via .NET (by implementing a mixed-mode
module), or simply export a standard C-style function which you can then
P/Invoke from .NET (which would be much easier).

You should be able to use C++ Express, although I haven't done so
myself.

Regards,

Kean
Title: Re: Expose ARX Function to .NET
Post by: LE on August 25, 2007, 11:40:09 PM
Tim;

I have no idea about IronPython at all but, found this "using IronPython from C#"

http://www.mail-archive.com/users@lists.ironpython.com/msg01983.html

Might help....

Original code it is available at the url link above.

Make a C# wrapper for a  Python class ...
Code: [Select]
class LogViewerService
{
         public delegate int GetNumberOfEntriesDelegate();
         public GetNumberOfEntriesDelegate GetNumberOfEntries;

         public delegate object GetEntryDelegate(int entryNumber);
         public GetEntryDelegate GetEntry;

         public delegate void closeDelegate();
         public closeDelegate close;

         public LogViewerService(object pythonObject)
         {
             object method;
             IronPython.Runtime.Operations.Ops.TryGetAttr (pythonObject, IronPython.Runtime.SymbolTable.StringToId ("GetNumberOfEntries"), out method);
             GetNumberOfEntries = (GetNumberOfEntriesDelegate) IronPython.Runtime.Operations.Ops.GetDelegate(method, typeof (GetNumberOfEntriesDelegate));
             IronPython.Runtime.Operations.Ops.TryGetAttr (pythonObject, IronPython.Runtime.SymbolTable.StringToId("GetEntry"),  out method);
             GetEntry = (GetEntryDelegate) IronPython.Runtime.Operations.Ops.GetDelegate(method, typeof (GetEntryDelegate));
             IronPython.Runtime.Operations.Ops.TryGetAttr (pythonObject, IronPython.Runtime.SymbolTable.StringToId("close"),  out method);
             close = (closeDelegate) IronPython.Runtime.Operations.Ops.GetDelegate(method, typeof (closeDelegate));
         }
 }
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 26, 2007, 03:57:12 AM
Well, from that discussion Tim, the link I gave above in post 24 would be the way to go.

The guts of what you are trying to do is pass an address (pointer in C terms) of a function for autocad to call when someone enters the command string, basically like a Python dictionary, ie. 'call the function (go to the address) that is stored at this key (the command string)'.
The biggest problem is converting a clr address to a native address for native code to call, not only that is has to be there for the whole acad session, making your managed delegate static 'should' ensure that to an extent but I don't know for sure.

I would still give P/Invoke a go, you still need to make a delegate regardless and it's an easy exercise, if that doesn't work then the link I mentioned is probably your best chance.
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 26, 2007, 04:16:26 AM
I found this link as well
http://www.codeproject.com/managedcpp/delegate_to_callback.asp
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 26, 2007, 09:39:34 AM
Your going to be executing the script itself, and not individual functions within the script correct? cc.execute  runs the whole script right?
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 26, 2007, 09:34:35 PM
Ok, I took the most simple way out (for me anyway) and it seems to work, what you may need to do is have a C# command that asks for and loads you IronPy commands or just add them to a file similar to below, ie. create a delegate for each functions and call RegPyCmd() for each one.
hope it works at your end.

here's the C# code
Code: [Select]
// Created By: Mick Duprez, 27th August 2007
// Test application to test use of acedRegCmds wrapper function in C

using System ;
using System.Runtime.InteropServices;
using Autodesk.AutoCAD.Runtime ;
using Autodesk.AutoCAD.EditorInput;

[assembly: CommandClass(typeof(ClassLibrary.DCSClass))]

namespace ClassLibrary
{
/// <summary>
/// Summary description for DCSClass.
/// </summary>
public class DCSClass
{
public DCSClass()
{
//
// TODO: Add constructor logic here
//
}
public delegate void TestCb();
public static void testcommand()
{
Editor ed = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("\ntest delegate seems to work!\n");
}
// import the dll:
[DllImport("IronPyCmdReg.dll",
CallingConvention=CallingConvention.Cdecl,
EntryPoint = "?RegPyCmd@@YAXPBD0HP6AXXZ@Z")]
public static extern void RegPyCmd(string cmdGroup,
string cmdName,
CommandFlags cmdFlags,
[MarshalAs(UnmanagedType.FunctionPtr)] TestCb functionptr);

// Define Command to register commands without using attributes:
[CommandMethod("regcmds")]
static public void test() // This method can have any name
{
// create the callback
DCSClass.TestCb cb = new DCSClass.TestCb(DCSClass.testcommand);
// PInvoke the RegPyCmd() function:
DCSClass.RegPyCmd("pycmds","testpy",CommandFlags.Session,cb);
}
}
}

The C code -
Code: [Select]
// Created By: Mick Duprez, 27th August 2007
//************ Iron Python Command Register Function ************************//
__declspec(dllexport) void __cdecl RegPyCmd(const char * cmdGroup,
const char * cmdName,
int cmdFlags,
void(*functionptr)())
{
// Note: cast functionptr as AcRxFuctionPtr.
acedRegCmds->addCommand(cmdGroup, cmdName, cmdName, (int)cmdFlags,(AcRxFunctionPtr)functionptr);
}

I've also attached the test C# dll and native dll that is PInvoked, I'll post the 2 solutions when I get a chance (I'm still getting a huge arx sln???). Both dlls' were tested on ac2006.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 26, 2007, 09:42:38 PM
The 2 solutions are attached.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 26, 2007, 09:45:47 PM
And a piccy showing before regcmds was called and after
Title: Re: Expose ARX Function to .NET
Post by: TR on August 26, 2007, 10:18:57 PM
Mick, you are great. That looks like exactly what I was looking for. I'll mess with it tomorrow as I'm off to bed now.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 26, 2007, 10:22:39 PM
Glad to help, now you need to work out a way of calling it when you netload your IronPython dll without using C#!
Title: Re: Expose ARX Function to .NET
Post by: TR on August 26, 2007, 10:50:16 PM
Mick:

What exactly am I supposed to be doing with the IronPyCmdReg.dll file? Do I have to register it somehow or put it somewhere on my path? I am getting a Dll Not Found Exception. Also should this dll work with 2008 or do I need to recompile it?
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 26, 2007, 10:58:44 PM
just place the dll where you put the CsMgg---.dll, PInvoke will find it then.
There is no mfc or anything so it 'may' work in 2008, if not it should be trivial to recompile it to ac2008 - unicode may present a problem though??

This was knocked together this morning pretty roughly to see if it was going to work and can now be tidied up and made more suitable for your needs. The reason you can't just PInvoke the acedRegCmds is because it is a macro (it gets expanded during preprocessing into the real code which is another method all together) so it wasn't exported so I just wrapped it in another C function for export.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 26, 2007, 11:09:21 PM
A test using a few callbacks

Code: [Select]
// Created By: Mick Duprez, 27th August 2007
// Test application to test use of acedRegCmds wrapper function in C

using System ;
using System.Runtime.InteropServices;
using Autodesk.AutoCAD.Runtime ;
using Autodesk.AutoCAD.EditorInput;

[assembly: CommandClass(typeof(ClassLibrary.DCSClass))]

namespace ClassLibrary
{
/// <summary>
/// Summary description for DCSClass.
/// </summary>
public class DCSClass
{
public DCSClass()
{
//
// TODO: Add constructor logic here
//
}
public delegate void TestCb();
public static void testcommand1()
{
Editor ed = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("\ncb1 delegate seems to work!\n");
}

public static void testcommand2()
{
Editor ed = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("\ncb2 delegate seems to work!\n");
}

public static void testcommand3()
{
Editor ed = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("\ncb3 delegate seems to work!\n");
}
// import the dll:
[DllImport("IronPyCmdReg.dll",
CallingConvention=CallingConvention.Cdecl,
EntryPoint = "?RegPyCmd@@YAXPBD0HP6AXXZ@Z")]
public static extern void RegPyCmd(string cmdGroup,
string cmdName,
CommandFlags cmdFlags,
[MarshalAs(UnmanagedType.FunctionPtr)] TestCb functionptr);

// Define Command to register commands without using attributes:
[CommandMethod("regcmds")]
static public void test() // This method can have any name
{
// create some callbacks
DCSClass.TestCb cb1 = new DCSClass.TestCb(DCSClass.testcommand1);
DCSClass.TestCb cb2 = new DCSClass.TestCb(DCSClass.testcommand2);
DCSClass.TestCb cb3 = new DCSClass.TestCb(DCSClass.testcommand3);

// PInvoke the RegPyCmd() function:
DCSClass.RegPyCmd("pycmds","testpy1",CommandFlags.Session,cb1);
DCSClass.RegPyCmd("pycmds","testpy2",CommandFlags.Session,cb2);
DCSClass.RegPyCmd("pycmds","testpy3",CommandFlags.Session,cb3);

}
}
}

Title: Re: Expose ARX Function to .NET
Post by: TR on August 26, 2007, 11:13:41 PM
I'll have to figure out what I need to compile your C++ project using Visual C++ Express. I tried copying your C++ dll into the same dir as my dll and I get an exception. When I try to recompile your DLL I get can't find windows.h. I'll take a look tomorrow. Thanks again.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 26, 2007, 11:28:29 PM
Sorry, I don't have anything later than vs2002 on this machine.
What you will need is the arx sdk for ac2008, there should be an ARX wizard in the utils folder, install it and that will make it a snap although you may still need to add the paths to the include and lib directories of the project (under the C++ and linker dir's - Add additional lib/inc directories), I didn't have to add any lib's for arx as the wizard took care of them I think.

Just copy and paste the function in the top (under the 'includes') of the acrxentrypnt.cpp or similar, as long as it's not in the scope of a class.
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 27, 2007, 02:20:09 AM
See if this works

Netload the PyAcad dll file for AC2008 in the zip file
run the PyLoad command at the prompt
load one of the .py files from the zip file
at the top of the py file is the command I.e #@$winform
a new command called winform should be registered

run the winform form command.

Deleted the zip file if your machine crashes  :-D


Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 27, 2007, 06:21:17 AM
Here is the source/.dll/solution,

Basically what it does is,
Reads the first line of the .py file the registers the command.
Stores a copy of the command and the path to the py file in a dictionary.
A reactor fires when you type a command that’s in the dictionary, invoking a copy of your pyfile command.

At first I was storing the command and a copy of the compiled code in a hashtable, which is faster than compiling the .py file each time, but it was unstable (I don’t know the internal workings of IronPython)

I ported all of your C# Code to C++/CLI and stuck it in the assembly

This should be a great foundation for you to build on, as you can use mix native/managed code. You can stick Mick’s code in there as well.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 27, 2007, 07:02:19 AM
Wow, you have been busy Daniel, there's alot of work there!
I'm afraid though that although I can follow it, this CLI stuff is still a bit greek to me   :-(  :laugh:
Title: Re: Expose ARX Function to .NET
Post by: TR on August 27, 2007, 10:30:16 AM
Dan:

Wow! That was a lot of work you did, thanks. However when I load your assembly It raises an exception. :(

I see that you ported all the C# code to C++/CLI, but can I still access the function for registering commands from C#? I don't know C++/CLI and there is a lot of stuff I want to do to this project in the future, like stuff with standard paths and eventually hook this code up to IronPython V2 which uses the DLR. If I can plug the DLR into AutoCAD then it might be possible to make Ruby.NET and IronLisp available with not too much trouble as they use the DLR too.

If possible I would also would want to use Python decorators as the method to define a command, instead of placing it at the first line of the file. example:
Code: [Select]
@CommandMethod('testcommand')
def testcommand():
    print "The test command worked"
With the CommandMethod decorator calling back to the C# code to register the command. I'm thinking this shouldn't be hard as C# and IronPython can pass objects back and forth fairly easily.
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 27, 2007, 12:31:51 PM
Quote
..However when I load your assembly It raises an exception
Try this one attached for 2007 & 2008, If you still get the exception, maybe you can attach it so I can debug it. It works well with my machine. Also this one is linked with IronPython 1.1

Quote
..but can I still access the function for registering commands from C#?

You should be able to add commands from C# using

Code: [Select]
PyAcad.CcStorage.StoreCmd(String strCmd ,String path)
Then
PyAcad.CcStorage.RegCmd(String strCmd)

But this .DLL will needed to be netloaded for the reactors to fire. Of course you could also build your own reactors in C#.

Quote
.If possible I would also would want to use Python decorators as the method to define a command 

Sure, All you need to do is search the file for decorators then register the commands, Though did not see any method in IronPython for executing/accessing individual functions within a .py file. I am sure it’s possible, I just didn’t see it.

This is just a start though
Title: Re: Expose ARX Function to .NET
Post by: TR on August 27, 2007, 09:47:15 PM
Dan:

Perhaps I was doing something wrong but I tested the latest version you posted and it seemed to work. Is there any chance I can get you to post a stripped down version of that code that does nothing but handle the registration of AutoCAD commands? If possible I'd like the code to register commands not have any reference to IronPython at all, just provide access to dealing with command registration. This way I can swap out IronPython versions without worrying about updating your C++ code.

I have been trying for the past couple hours to get either your code or Mick's code to compile on my system without any luck. If I could do that then I think I could remove the parts myself. I think it's an issue with the express edition.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 27, 2007, 10:00:08 PM
What sort of errors are you getting Tim?
Title: Re: Expose ARX Function to .NET
Post by: TR on August 27, 2007, 10:03:20 PM
When I try to compile your code I get the following output:

Command Line:
Quote
IronPyCmdReg : warning PRJ0041 : Cannot find missing dependency 'winres.h' for file 'IronPyCmdReg.rc'.  Your project may still build, but may continue to appear out of date until this file is found.

Creating temporary file "c:\Documents and Settings\TJRiley\Desktop\IronPyRegCmds\IronPyCmdReg\Debug\RSP0000011152908.rsp" with contents
[
/Od /I "C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include" /I "C:\ObjectARX_2008\inc" /AI "C:\Program Files\AutoCAD 2006" /D "WIN32" /D "_WINDOWS" /D "_DEBUG" /D "IRONPYCMDREG_MODULE" /D "_ACRXAPP" /D "_VC80_UPGRADE=0x0700" /D "_WINDLL" /D "_UNICODE" /D "UNICODE" /Gm /EHsc /RTC1 /MD /Fo"Debug\\" /Fd"Debug\vc80.pdb" /W1 /c /Wp64 /ZI /TP ".\StdAfx.cpp"

".\IronPyCmdReg.cpp"

".\DocData.cpp"

".\acrxEntryPoint.cpp"
]
Creating command line "cl.exe @"c:\Documents and Settings\TJRiley\Desktop\IronPyRegCmds\IronPyCmdReg\Debug\RSP0000011152908.rsp" /nologo /errorReport:prompt"

Output Window
Quote
Compiling...
acrxEntryPoint.cpp
     Compiling MFC / STL / ATL header files in release mode.
c:\program files\microsoft visual studio 8\vc\include\use_ansi.h(49) : fatal error C1189: #error :  You have included some C++/C library header files with _DEBUG defined and some with _DEBUG not defined. This will not work correctly. Please have _DEBUG set or clear consistently.
DocData.cpp
     Compiling MFC / STL / ATL header files in release mode.
c:\program files\microsoft visual studio 8\vc\include\use_ansi.h(49) : fatal error C1189: #error :  You have included some C++/C library header files with _DEBUG defined and some with _DEBUG not defined. This will not work correctly. Please have _DEBUG set or clear consistently.
IronPyCmdReg.cpp
     Compiling MFC / STL / ATL header files in release mode.
c:\program files\microsoft visual studio 8\vc\include\use_ansi.h(49) : fatal error C1189: #error :  You have included some C++/C library header files with _DEBUG defined and some with _DEBUG not defined. This will not work correctly. Please have _DEBUG set or clear consistently.
StdAfx.cpp
     Compiling MFC / STL / ATL header files in release mode.
c:\program files\microsoft visual studio 8\vc\include\use_ansi.h(49) : fatal error C1189: #error :  You have included some C++/C library header files with _DEBUG defined and some with _DEBUG not defined. This will not work correctly. Please have _DEBUG set or clear consistently.
Generating Code...
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 27, 2007, 10:05:54 PM
Did you create a new solution from scratch or copy in my files from the vs2002 sln?
Title: Re: Expose ARX Function to .NET
Post by: TR on August 27, 2007, 10:12:57 PM
I loaded your solution and changed the paths to the headers. When I try to create a new solution in C++ Express Edition I can't even build an empty project. I keep getting the error
Code: [Select]
LINK : fatal error LNK1104: cannot open file 'mfcs80u.lib' Which I assume is the unicode mfc lib. I  downloaded the platform sdk but it doesn't contain that lib in it.
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 27, 2007, 10:26:28 PM
You bet, I will also remove MFC support, so you should be able to compile with the express version
Title: Re: Expose ARX Function to .NET
Post by: TR on August 27, 2007, 10:33:52 PM
Thanks Daniel.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 27, 2007, 10:38:59 PM
Sorry I can't help Tim, if you can get Daniel's to compile, copy and paste that one function (RegPyCmd) into the acrxentrypoint.cpp as noted before, it 'should' compile. The only difference may be if they changed the function signature to take unicode strings(?). You will have two options to register commands then ;)
Title: Re: Expose ARX Function to .NET
Post by: TR on August 27, 2007, 10:51:18 PM
I'll give it a shot Mick. I appreciate all the effort.
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 27, 2007, 11:30:26 PM
try this  :-o
Title: Re: Expose ARX Function to .NET
Post by: Kerry on August 27, 2007, 11:36:48 PM

I haven't had a chance to follow this conversation ...

putting IronPython aside for a sec' ..
Have you guys posted a solution for MSc# with an arx function ??
Title: Re: Expose ARX Function to .NET
Post by: TR on August 27, 2007, 11:52:41 PM
Dan:

So I would pass this a delegate from C#? It says it only accept a string.
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 27, 2007, 11:56:12 PM
ues a reactor to fire your method

here is en example in C#
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 12:02:19 AM

I haven't had a chance to follow this conversation ...

putting IronPython aside for a sec' ..
Have you guys posted a solution for MSc# with an arx function ??

Is this what you're after Kerry?
Title: Re: Expose ARX Function to .NET
Post by: TR on August 28, 2007, 12:03:45 AM
Thanks a lot Dan. I will look over this tomorrow. I think this is exactly what I needed.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 12:24:36 AM
Just for completeness I have cleaned up the code to be used in a C# class lib that could be imported into another class lib or Iron Python script. The function to use in arx will remain the same, it would be worth checking the signature if compiled into another dll for different versions though.

Code: [Select]
// Created By: Mick Duprez, 27th August 2007
// Used to register commands using a C wrapper for the acedRegCmds() macro

using System ;
using System.Runtime.InteropServices;
using Autodesk.AutoCAD.Runtime ;
using Autodesk.AutoCAD.EditorInput;

namespace ClassLibrary
{
/// <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("IronPyCmdReg.dll",
CallingConvention=CallingConvention.Cdecl,
EntryPoint = "?RegPyCmd@@YAXPBD0HP6AXXZ@Z")]
public static extern void RegPyAcadCmd(
string cmd_group,
string cmd_name,
Autodesk.AutoCAD.Runtime.CommandFlags cmd_flags,
[MarshalAs(UnmanagedType.FunctionPtr)] PyAcadCmd.CmdDelegate cmd_delegate);
}
}
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 28, 2007, 12:36:23 AM
Nice work Mick!
Title: Re: Expose ARX Function to .NET
Post by: Kerry on August 28, 2007, 02:19:19 AM

I haven't had a chance to follow this conversation ...

putting IronPython aside for a sec' ..
Have you guys posted a solution for MSc# with an arx function ??

Is >?>>> this <<<?< what you're after Kerry?

You trying to trick me Mick ?  :D



Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 02:31:38 AM
Sorry Mate! copied but didn't paste -> http://www.theswamp.org/index.php?topic=18355.msg224696#msg224696
 :roll:
Title: Re: Expose ARX Function to .NET
Post by: Kerry on August 28, 2007, 03:04:39 AM

Mick, Isn't that link using Python stuff ??
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 28, 2007, 05:39:06 AM
I might have to get more involved with these types of projects
http://www.codeplex.com/IronLisp
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 08:06:26 AM

Mick, Isn't that link using Python stuff ??


No, not at all Kerry, there is 2 vs2002 sln's, 1 arx which is an arx/dll which wraps the acedRegCmds and one C# class lib that PInvokes the plain C wrapper expoted from that dll/arx.
This will work for any case at all where you don't want to use attributes like the .net arx wrappers do as I did in the examples that followed that post.
It basically does what Tim wanted, makes a C function (in this case a  macro which needs a little more work) available to .net. What Daniel has done from what I can tell from a brief look is make the reg'ing of the command a bit easier by using reactors instead of having to register them at 'netload' (I think :roll:, I haven't looked that hard).
At the heart of it there is no 'real' Python code involved, while IronPython's syntax is Python, it is definitely not python 'binary' say, to put it another way, it's like comparing vb.net to vb, same lingo, different runtime environment.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 28, 2007, 09:31:58 AM
Dan:

Since you are the man for compiling the 2008 C++ files (I still can't compile) could you compile Mick's code in with yours under a single dll file? This way I could test both ways of registering commands side by side and figure out which way will end up working best.
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 28, 2007, 11:21:27 AM
Try this one, you may need to verify the new entry point
?RegPyCmd@@YAXPB_W0HP6AXXZ@Z

One more thing that you might look into
http://msdn2.microsoft.com/en-us/library/7esfatk4(VS.80).aspx

Quote
A reference to the function pointer to a managed delegate held by unmanaged code does not prevent the common language runtime from performing garbage collection on the managed object. 
Title: Re: Expose ARX Function to .NET
Post by: TR on August 28, 2007, 12:05:06 PM
Thanks. I'll try to digest this all later on tonight after work.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 28, 2007, 03:59:11 PM
Dan:
I'm trying to call the code from C# in the same fashion Mick does in in reply 33, and I am unable to. After looking at the source code of the latest file you posted I can't find to find code that looks like Mick's and takes the same arguments? Am I mistaken?
Title: Re: Expose ARX Function to .NET
Post by: TR on August 28, 2007, 04:10:18 PM
Nevermind, I'm an idiot and the code was there. I am still getting an overflow and my command isn't registered. I'll keep digging.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 28, 2007, 05:09:07 PM
Ok here is the code I'm using to test with Dan's latest C++ DLL.
Code: [Select]
// Created By: Mick Duprez, 27th August 2007
// Used to register commands using a C wrapper for the acedRegCmds() macro

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,
EntryPoint = "?RegPyCmd@@YAXPB_W0HP6AXXZ@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);

        //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);
            RegPyCmd("_pycmds", "TESTER", CommandFlags.Session, cb1);
            Editor ed = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor;
            ed.WriteMessage("\nRegister command was called, but does it work?\n");
        }
}
}
I call "regcmds" from the command line and the message is written to the command line. However my "TESTER" command is not available from the command line. I and doing this from debug mode in VC# Express. When I close AutoCAD it gives me a System.StackOverflowException.

Now if I passed bad values to the underlying C function should I expect an error? I ask because the C code askes for an int on cmd_flags and I'm passing a AutoDesk.AutoCAD.Runtime.CommandFlag, which I don't think is an int.

The C code in the dll I'm using:
Code: [Select]
#pragma unmanaged
__declspec(dllexport) void __cdecl RegPyCmd(const ACHAR * cmdGroup,
const ACHAR * cmdName,
int cmdFlags,
void(*functionptr)())
{
// Note: cast functionptr as AcRxFuctionPtr.
acedRegCmds->addCommand(cmdGroup, cmdName, cmdName, (int)cmdFlags,(AcRxFunctionPtr)functionptr);
}
Note: I'm almost certain I have the entry point correct as I ran "dumpbin /EXPORTS PyRegCmd.dll" and it gave me the one I use in the code above.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 05:57:43 PM
Tim, add the bold code below into the arx function, this should give us an idea of what's not working. (I should have had it in there anyways  :roll: )

Quote
__declspec(dllexport) void __cdecl RegPyCmd(const char * cmdGroup,
                                 const char * cmdName,
                                 int cmdFlags,
                                 void(*functionptr)())
{
   // Note: cast functionptr as AcRxFuctionPtr.
   Acad::ErrorStatus es =
   acedRegCmds->addCommand(cmdGroup, cmdName, cmdName, (int)cmdFlags,(AcRxFunctionPtr)functionptr);
   if(es != Acad::ErrorStatus::eOk)
   {
      acutPrintf("Error loading command %s: error status %d", cmdName, acadErrorStatusText(es));
   }

}

The command flags should be fine as they translate to an int (Adesk::Int32), they are defined in accmd.h if you want to have a look, the .net cmd flags just wrap those.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 06:03:29 PM
Another thing to try is change the ACHAR's to plain char's, it may not compile due to unicode in arx now, I don't know. Perhaps the command string is to big for its slot in the command stack??
Title: Re: Expose ARX Function to .NET
Post by: LE on August 28, 2007, 06:19:04 PM
a positive attitude.... and to encourage you :)

by now, we might end up having all what it is in python - ported to C# or having an alternative from scratch.... he he

Sorry, could not resist.... (and I'm joking eh!  :evil: )

Now the 64,000 dollars question, why not doing this project using C# ? -----  :roll:
Title: Re: Expose ARX Function to .NET
Post by: LE on August 28, 2007, 06:21:52 PM
I think I have python22 installed in my home PC (it came with that pre-installed).... never had tried before.... maybe is something in there that I am missing ?

Time will tell (after reading about it)
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 06:25:38 PM
Luis, Python isn't required at this stage, we have just created a brute force way of registering commands without using attributes in C#/VB.net. IronPython from what I understand does not use attributes but handles everything else.

Jump in anytime, the water's fine :)
Title: Re: Expose ARX Function to .NET
Post by: LE on August 28, 2007, 06:32:05 PM
Luis, Python isn't required at this stage, we have just created a brute force way of registering commands without using attributes in C#/VB.net. IronPython from what I understand does not use attributes but handles everything else.

Jump in anytime, the water's fine :)

I see - and it will be for general purposes not just for python no?..... still I will put python in my reading list....

Gracias!, Mick.

ps... I am afraid of water.....   :-P
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 06:42:31 PM
>> and it will be for general purposes not just for python no?

That is correct.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 28, 2007, 09:55:09 PM
I've been trying for the past hour to get the C++ dll with the changes Mick noted above without any luck. I keep getting missing "mfc80u.lib" errors in visual C++ 2005 express.

Dan: can you bail me out again?
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 10:06:33 PM
That's no good Tim, did you ever get it to compile or are you using Dan's dll?
Have a look at your project settings/properties under configuration->general and make sure that 'use of MFC' is set to 'use std windows lib's' or similar, that may help.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 28, 2007, 10:13:03 PM
I have never gotten it to compile. Every time I get past one error another rears it's ugly head. I have all my paths straight now and I have "Use Standard Windows Libraries" under "Use of MFC" under the general tab. I have searched my system and I don't have that lib anywhere. Even though I have the platform SDK installed and referenced in my paths.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 10:44:11 PM
Tim, this may not work with the express edition but it might be worth a shot (the add/remove features part) -> http://msdn2.microsoft.com/en-us/library/wsdfs47e(VS.80).aspx
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 28, 2007, 10:58:27 PM
Here you go, You may need to neload it as well,
Title: Re: Expose ARX Function to .NET
Post by: TR on August 28, 2007, 11:03:00 PM
Thanks Dan. I'll take a look.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 28, 2007, 11:33:03 PM
Ok. I tried Dan's new dll and still no luck with the example code I recently posted. I loaded the pyacaddotnet.dll file and ran the arx command with the option C to list all arx commands, everything looked fine. I then ran the regcmds command and listed the arx commands again. The following screenshot is what I found. Note the squares. This is what was added after I ran the regcmds command. This leads me to believe something is getting lost in the translation somewhere.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 11:43:59 PM
I take it that you are not getting an error when running regcmds then Tim?

Dan, could you compile it using std 'char's instead of ACHAR's, there's something screwy going on with the strings I think.

The reason I think ACHAR may not work is that the command stack may not be updated for unicode, while arx has made many changes to the way strings are handled as input etc, to change every instance of a char* would be some feat!
Another thing to consider is what is happening to the strings when passed from .net which is unicode by default if I remember correctly, just a thought.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 28, 2007, 11:52:17 PM
Maybe a cast the param's to char* in the acedRegCmd function would do the trick even??
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 28, 2007, 11:55:52 PM
I tried and got

Error   1   error C2664: 'AcEdCommandStack::addCommand' : cannot convert parameter 1 from 'const char *' to 'const ACHAR *'
                   c:\PyRegCmd\PyRegCmd\acrxEntryPoint.cpp   39   
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 28, 2007, 11:58:37 PM
Well, I am off to Disneyland. we need another volunteer for the night shift  :-D
be back later
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 29, 2007, 12:08:05 AM
Thanks Daniel, I'm downloading vc2005xpress now, then the platform sdk, then the arx sdk, you will probably having breakfast by the time I'm done :D.
I'll see if I can nut something out at this end then although I can't test as I don't have ac2008.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 29, 2007, 12:15:23 AM
Mick:
Yes, I'm not getting any errors at the command line. I appreciate any help I can get getting this going too.

As far as not having 2008 goes if you're really interested you can snag the 30 day trial (http://usa.autodesk.com/adsk/servlet/mform?id=9106363&siteID=123112).
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 29, 2007, 12:21:25 AM
Tim, try adding the bold text below to the DllImport -

Quote
[DllImport("PyRegCmd.dll",
          CallingConvention=CallingConvention.Cdecl,CharSet = CharSet.Unicode,
          EntryPoint = "?RegPyCmd@@YAXPB_W0HP6AXXZ@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);

If that works you can thank Luis, I just noticed it in his latest thread ;)
Title: Re: Expose ARX Function to .NET
Post by: TR on August 29, 2007, 12:27:45 AM
Oh heck yes!!!!!!!


Here's the command line output:
Quote
AutoCAD menu utilities loaded.
Command: netload
Assembly file name: "C:/Documents and Settings/TJRiley/My
Documents/pyacaddotnet/bin/Debug/PyAcadDotNet.dll"

PyAcad.NET Loaded Successfully....
type 'pyhelp' for commands....Enter BACKSPACE to interrupt script.

Command: DASHBOARD

Command: regcmds

Register command was called, but does it work?

Command: tester

cb1 delegate seems to work!

You guys are the greatest.

Mick, Dan and Luis please PM me your info that you'd like posted in the credits section of the project.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 29, 2007, 12:39:18 AM
Woot! Great news!

'nothing like learning on the run :D
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 29, 2007, 04:13:08 AM
Quote
…CharSet.Unicode…

thought of this while I was standing in line for Space Mountain, Awesome  8-)
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! on August 29, 2007, 04:59:26 AM
And here is the alternative method.
Title: Re: Expose ARX Function to .NET
Post by: Kerry on August 29, 2007, 10:02:17 PM
I've been trying for the past hour to get the C++ dll with the changes Mick noted above without any luck. I keep getting missing "mfc80u.lib" errors in visual C++ 2005 express.

Dan: can you bail me out again?

This may be unrelated.

We have had to update most work machines with vcredist_x86.exe
from http://www.microsoft.com/downloads/details.aspx?familyid=32BC1BEE-A3F9-4C13-9C99-220B62A191EE&displaylang=en
to ensure that the runtime support dll's for our Cpp stuff are up to date.

Quote
The Microsoft Visual C++ 2005 Redistributable Package (x86) installs runtime components of Visual C++ Libraries required to run applications developed with Visual C++ on a computer that does not have Visual C++ 2005 installed.
Title: Re: Expose ARX Function to .NET
Post by: TR on August 29, 2007, 10:10:01 PM
Thanks Kerry. I'll give it a shot.
Title: Re: Expose ARX Function to .NET
Post by: MickD on August 25, 2008, 11:14:00 PM
Quote from: From the pyacad.net download readme
Currently the only way to bind a python file to an AutoCAD command is to do so via lisp. For this we have added a lisp command called "runpyfile". The runpyfile returns (T) on success (nil) on error.

didn't we have a workable solution to this?

I can see why you may want to run a whole script at times but this also could be done via a command.

I have been thinking on this since I wrote the other command stack for pyarx stuff, thought I'd re-visit it and clean it up so it automatically loads your commands from an init file say
Title: Re: Expose ARX Function to .NET
Post by: tjr on August 26, 2008, 12:43:42 AM
Quote from: From the pyacad.net download readme
Currently the only way to bind a python file to an AutoCAD command is to do so via lisp. For this we have added a lisp command called "runpyfile". The runpyfile returns (T) on success (nil) on error.

didn't we have a workable solution to this?

I can see why you may want to run a whole script at times but this also could be done via a command.

I have been thinking on this since I wrote the other command stack for pyarx stuff, thought I'd re-visit it and clean it up so it automatically loads your commands from an init file say
Not as of yet. I'm hoping you can help me out though. :)
Title: Re: Expose ARX Function to .NET
Post by: MickD 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(?)
Title: Re: Expose ARX Function to .NET
Post by: MickD 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.
Title: Re: Expose ARX Function to .NET
Post by: MickD 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()
         {

         }
Title: Re: Expose ARX Function to .NET
Post by: MickD 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?
Title: Re: Expose ARX Function to .NET
Post by: MickD 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
        }
    }
Title: Re: Expose ARX Function to .NET
Post by: MickD 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.
Title: Re: Expose ARX Function to .NET
Post by: MickD 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.

Title: Re: Expose ARX Function to .NET
Post by: tjr 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.
Title: Re: Expose ARX Function to .NET
Post by: MickD 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 ;)
Title: Re: Expose ARX Function to .NET
Post by: It's Alive! 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
Title: Re: Expose ARX Function to .NET
Post by: MickD 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.
Title: Re: Expose ARX Function to .NET
Post by: Krasyn 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?
Title: Re: Expose ARX Function to .NET
Post by: MickD 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.
Title: Re: Expose ARX Function to .NET
Post by: Krasyn 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. 
Title: Re: Expose ARX Function to .NET
Post by: tjr 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.
Title: Re: Expose ARX Function to .NET
Post by: Krasyn on September 02, 2008, 01:27:44 AM
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.
I translated a couple of programs from C# to Python and it was OK. The third program used reactors and it failed to work.
Title: Re: Expose ARX Function to .NET
Post by: MickD on September 03, 2008, 11:12:53 PM
A little bit further down the track, almost there, here's where I will need someone to code up a simple script to load a command, we can then fill in the missing function and do some testing.

Can we call a script that has 'main' that can execute the loading or can we nominate a method name that we know we can hard code into this dll that calls the main script's load method??

Delegates between IP and C# aren't a problem though (so I've read), it will be interesting to see if this flys :)

Code: [Select]
using System;
using System.Collections.Generic;
using System.Text;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

using IronPython.Hosting;
using IronPython.Compiler;
using PyAcadNET.PyAcadNET;

using acadapp = Autodesk.AutoCAD.ApplicationServices.Application;

// the one and only callback to be called by acad to
// execute our ironpython methods in our scripts.
public delegate void callback();
public delegate void maincallback();

namespace IpyEmbed1
{
    class CmdStack
    {
        private maincallback mcb;
        public CmdStack()
        {
            // init our stack object:
            mcb = new maincallback(RunPyMethod);
        }
        // our dictionary object to hold the command name
        // as key with script method delegate to execute.
        // these are used to call the method in the IPy script
        private static Dictionary<string, callback> cmd_dict;
       
        // Adds a command name and method def name to the cmd_stack
        public void Push(string cmd_string, string cmd_def, PythonEngine pyeng, EngineModule mod)
        {
            // create the callback:
            callback cb = pyeng.CreateMethod<callback>(cmd_def + "()", mod);
            // add the command string (used in acad) and the
            // callback to the script method to the cmd_stack dictionary:
            cmd_dict.Add(cmd_string, cb);

            // now add the command to the acad cmd stack:
            PyCommands.load_command("PYCOMMANDS", cmd_string, cmd_string, (int)CommandFlags.Modal, mcb);
        }

        // This function is the same function that gets called from every
        // acad command (maincallback), in it we grab the current command
        // string and use it to get the function name from the dictionary
        // and call into the python script to execute the dict's callback value.
        public static void RunPyMethod()
        {
            // grab the string for the current command in action: (the return needs checking!)
            string cmdstr = acadapp.DocumentManager.MdiActiveDocument.CommandInProgress;
            // get the function name from the dictionary using the cmd name as the key:
            callback cb = cmd_dict[cmdstr];         
            // call it:
            cb();
        }
    }
    class App : IExtensionApplication
    {
        private CmdStack cmd_stack;
        private PythonEngine engine;
        EngineModule pymod;
        public void Initialize()
        {
            cmd_stack = new CmdStack();
            engine = new PythonEngine();
            engine.AddToPath(Environment.CurrentDirectory);
            pymod = engine.CreateModule("pyacadnet", true);
            pymod.Globals["cmd_stack"] = cmd_stack; 

            // call our function to initialise our commands:
            InitPyCmds();
        }

        // a method to call into our main IP script that calls a method
        // to load all our commands from IP
        void InitPyCmds()
        {
            // use the engine and execute/evaluate the main script, in this script
            // there must be a method for loading commands, this is th scripts
            // 'entry point' just like you have with a dll. To make this work
            // we need to have a 'main' or setup script that's always available
            // and in it is the one and only 'load_command_methods' method
            // or similar.
            // This will need some thorough error checking for files etc.!!
        }
        public void Terminate()
        {
            // pass
        }

    }
}

Solution attached for those that want to join in.
Title: Re: Expose ARX Function to .NET
Post by: MickD on November 13, 2008, 08:48:29 PM
I stumbled on to a bit more info, the help doc's are a bit vague on how things work (unless you thoroughly understand the inner workings of IP) but we can give this a try, it compiled ok anyway :).

Code: [Select]
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

using IronPython.Hosting;
using IronPython.Compiler;
using PyAcadNET.PyAcadNET;

using acadapp = Autodesk.AutoCAD.ApplicationServices.Application;

// the one and only callback to be called by acad to
// execute our ironpython methods in our scripts.
public delegate void callback();
public delegate void maincallback();

namespace IpyEmbed1
{
    class CmdStack
    {
        private maincallback mcb;
        public CmdStack()
        {
            // init our stack object:
            mcb = new maincallback(RunPyMethod);
        }
        // our dictionary object to hold the command name
        // as key with script method delegate to execute.
        // these are used to call the method in the IPy script
        private static Dictionary<string, callback> cmd_dict;

        // Adds a command name and method def name to the cmd_stack
        public void Push(string cmd_string, PythonEngine pyeng, EngineModule mod)
        {
            // create the callback:
            callback cb = pyeng.CreateMethod<callback>(cmd_string + "()", mod);
            // add the command string (used in acad) and the
            // callback to the script method to the cmd_stack dictionary:
            cmd_dict.Add(cmd_string, cb);

            // now add the command to the acad cmd stack:
            PyCommands.load_command("PYCOMMANDS", cmd_string, cmd_string, (int)CommandFlags.Modal, mcb);
        }

        // This function is the same function that gets called from every
        // acad command (maincallback), in it we grab the current command
        // string and use it to get the function name from the dictionary
        // and call into the python script to execute the dict's callback value.
        public static void RunPyMethod()
        {
            // grab the string for the current command in action: (the return needs checking!)
            string cmdstr = acadapp.DocumentManager.MdiActiveDocument.CommandInProgress;
            // get the function name from the dictionary using the cmd name as the key:
            callback cb = cmd_dict[cmdstr];
            // call it:
            cb();
        }
    }

    public delegate void callback();
    class App : IExtensionApplication
    {
        private CmdStack cmds;
        private PythonEngine engine;
        private EngineModule mod;
        public void Initialize()
        {
            // create the engine and pre-compile our script for better first run perf.
            engine = new PythonEngine();
            mod = engine.CreateModule();
            engine.AddToPath(Environment.CurrentDirectory);
            string stmts = LoadScriptFile("pyacad.py");
            engine.Compile(stmts);

            // here we just hard code some test commands, later we can ask the script
            // to call back here and do this dynamically.
            cmds = new CmdStack();
            cmds.Push("py1",engine, mod);
            cmds.Push("py2", engine, mod);
            cmds.Push("py3", engine, mod);
        }

        public static string LoadScriptFile(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentException("Argument cannot be a null or empty string.", "fileName");

            string fullScriptPath = null;
            string result;

            fullScriptPath = string.Format(@"{0}\{1}", Environment.CurrentDirectory, fileName);

            using (StreamReader reader = File.OpenText(fullScriptPath))
            {
                result = reader.ReadToEnd();
                reader.Close();
            }

            return result;
        }
        public void Terminate()
        {
            // pass
        }
    }
}

I don't have time now as lunch is over but if someone can write a simple test script with method def's of "py1", "py2" and "py3", that should be enough to see if it will work. The commands only need to do something simple like print to the command line.
Title: Re: Expose ARX Function to .NET
Post by: tjr on November 13, 2008, 10:52:15 PM
Mick:

Very cool if it works. Sadly can't test it out tonight. It's 10:48pm here and I am busily fixing bugs in an a C# app that is going live Monday morning. If my app doesn't perform as planed it kind of fails our entire ERP system. :(

I'll try to test it out this weekend.
Title: Re: Expose ARX Function to .NET
Post by: MickD on November 18, 2008, 01:43:54 AM
I did a quick test but didn't have any luck and I couldn't get debugging to work properly and ran out of time.

Here's a breif explanation of what I 'think' is happening.

When you compile/execute (compile I understand, execute could have had a better name though), the script is passed and compiled/loaded into the engine, this means all you need to do is call the method in the engine and it all should work.
To make it dynamic, the trick is to know what the method names are in advance and that way you can call the engine directly with the method name.
If I get some more time I'll check it out, I may be able to loop through the engine (I think you can as it stores them in a dictionary) and retrieve all the method names and use those as the registered command names and call straight into the engine to execute them, this means we can ditch the callback system alltogether.

That's the plan anyway :)
Title: Re: Expose ARX Function to .NET
Post by: TonyT on November 26, 2008, 11:53:16 PM
I did a quick test but didn't have any luck and I couldn't get debugging to work properly and ran out of time.

Here's a breif explanation of what I 'think' is happening.

When you compile/execute (compile I understand, execute could have had a better name though), the script is passed and compiled/loaded into the engine, this means all you need to do is call the method in the engine and it all should work.
To make it dynamic, the trick is to know what the method names are in advance and that way you can call the engine directly with the method name.
If I get some more time I'll check it out, I may be able to loop through the engine (I think you can as it stores them in a dictionary) and retrieve all the method names and use those as the registered command names and call straight into the engine to execute them, this means we can ditch the callback system alltogether.

That's the plan anyway :)

If you can target AutoCAD 2009 or later, there is now a way to add and
remove registered commands dynamically (the Action Recorder uses it to
register recorded actions as commands) without the need for C++/CLI.

Here's a wrapper:

Code: [Select]

// AutoCADCommand.cs  Copyright (c)2008 Tony Tanzillo / www.caddzone.com

using System;
using System.Collections.Generic;
using System.Text;
using Autodesk.AutoCAD.Internal; // <------ acmgdinternal.dll, A2K9 or later only
using Autodesk.AutoCAD.Runtime;
using System.Reflection;

/// This code requires AutoCAD 2009 or later.

namespace CaddZone.AutoCAD.ApplicationServices
{
   /// <summary>
   ///
   /// AutoCADCommand class
   ///
   /// A class that implements a registered AutoCAD
   /// command.
   ///
   /// When an instance of this type is created, the
   /// command is registered with the specified name,
   /// and becomes available to the user.
   ///
   /// When the instance is Disposed, the command is
   /// unregistered and no longer available to the user.
   ///
   /// When the user issues the command, the OnInvoke()
   /// method is invoked and the Invoke event is fired.
   ///
   /// You can handle the command invocation in one of
   /// two ways:
   ///
   ///    - Create an instance of AutoCADCommand, and
   ///      handle the Invoke event.
   ///
   ///    - Derive a type from AutoCADCommand, and override
   ///      the OnInvoke() method.
   ///
   /// This code depends on undocumented/unsupported APIs
   /// from acmdginternal.dll, and requires AutoCAD 2009
   /// or later.
   ///
   /// </summary>

   public class AutoCADCommand : IDisposable
   {
      private string groupName = Assembly.GetCallingAssembly().GetName().Name + "_Commands";
      private string name = string.Empty;
      private CommandFlags flags = CommandFlags.Modal;

      public AutoCADCommand( string Name, string GroupName, CommandFlags flags )
      {
         if( string.IsNullOrEmpty( Name ) )
            throw new ArgumentNullException( "Name" );
         if( !string.IsNullOrEmpty( GroupName ) )
            this.groupName = GroupName;
         this.name = Name;
         this.flags = flags;
         Utils.AddCommand( this.groupName, this.name, this.name, this.flags, this.invoke );
      }

      public AutoCADCommand( string Name, CommandFlags flags )
         : this( Name, null, flags )
      {
      }

      public AutoCADCommand( string Name )
         : this( Name, null, CommandFlags.Modal )
      {
      }

      ~AutoCADCommand()
      {
         if( !disposed )
            Dispose( false );
      }

      public string GroupName
      {
         get
         {
            return groupName;
         }
      }

      public string Name
      {
         get
         {
            return name;
         }
      }
     
      private void invoke()
      {
         OnInvoke();
      }

      protected virtual void OnInvoke()
      {
         if( this.Invoke != null )
            this.Invoke( this, EventArgs.Empty );
      }

      public event EventHandler Invoke = null;

      private bool disposed = false;

      protected virtual void Dispose( bool disposing )
      {
         disposed = true;
         if( disposing )
         {
            Utils.RemoveCommand( this.GroupName, this.Name );
            GC.SuppressFinalize( this );
         }
      }

      public void Dispose()
      {
         if( !disposed )
            Dispose( true );
      }
   }
}

// AutoCADCommand.cs