Author Topic: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?  (Read 4202 times)

0 Members and 1 Guest are viewing this topic.

csharpbird

  • Newt
  • Posts: 64
How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« on: December 03, 2011, 10:13:02 AM »
It seems that the Region class in .NET does not have the getAreaProp method.So I decide to P/Invoke it:
Code: [Select]
    public class Class1
    {
        [SuppressUnmanagedCodeSecurity]
        [DllImport(
          "acdb17.dll",
          CallingConvention = CallingConvention.ThisCall,
          EntryPoint = "?getAreaProp@AcDbRegion@@UBE?AW4ErrorStatus@Acad@@ABVAcGePoint3d@@ABVAcGeVector3d@@1AAN2AAVAcGePoint2d@@QAN24QAVAcGeVector2d@@433@Z"
         )]
        private static extern void GetAreaProp(IntPtr region,
                                                out Point3d origin,
                                                out Vector3d xAxis,
                                                out Vector3d yAxis,
                                                out double perimeter,
                                                out double area,
                                                out Point2d centroid,
                                                double[] momInertia,
                                                out double prodInertia,
                                                double[] prinMoments,
                                                Vector2d[] prinAxes,
                                                double[] radiiGyration,
                                                out Point2d extentsLow,
                                                out Point2d extentsHigh);
        [CommandMethod("Test")]
        public void Test()
        {
            Database db=HostApplicationServices.WorkingDatabase;
            Document doc=Application.DocumentManager.MdiActiveDocument;
            Editor ed=doc.Editor;
            ObjectId id=ed.GetEntity("ent").ObjectId;
            Point3d origin;
            Vector3d xAxis;
            Vector3d yAxis;
            double perimeter;
            double area;
            Point2d centroid;
            double[] momInertia=new double[2];
            double prodInertia;
            double[] prinMoments=new double[2];
            Vector2d[] prinAxes=new Vector2d[2];
            double[] radiiGyration=new double[2];
            Point2d extentsLow;
            Point2d extentsHigh;
            using (Transaction trans=db.TransactionManager.StartTransaction())
            {
                DBObject region=trans.GetObject(id, OpenMode.ForRead);
                GetAreaProp(region.UnmanagedObject, out origin,
                                            out xAxis,
                                            out yAxis,
                                            out perimeter,
                                            out area,
                                            out centroid,
                                            momInertia,
                                            out prodInertia,
                                            prinMoments,
                                            prinAxes,
                                            radiiGyration,
                                            out extentsLow,
                                            out extentsHigh);

                trans.Commit();
            }
            ed.WriteMessage(area.ToString());
        }
    }
But the return values are always 0.

gile

  • Gator
  • Posts: 2520
  • Marseille, France
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #1 on: December 03, 2011, 11:49:26 AM »
Hi,

The .NET Region class has a Area property.
Speaking English as a French Frog

kaefer

  • Guest
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #2 on: December 05, 2011, 07:45:36 AM »
But the return values are always 0.

Hi, there are two issues with your code:
1) The return value is Autodesk.AutoCAD.Runtime.ErrorStatus.
2) The second to fourth arguments aren't out parameters. I take them as origin and X- and Y-axis of a reference coordinate system, so that the function can ensure they lie in the same plane as your region - see 1).

Code: [Select]
            CoordinateSystem3d cs3d = ed.CurrentUserCoordinateSystem.CoordinateSystem3d;
            ...
            ErrorStatus es =
                GetAreaProp(
                    region.UnmanagedObject,
                    cs3d.Origin,
                    cs3d.Xaxis,
                    cs3d.Yaxis,
                    ...
            if (es == ErrorStatus.OK)
                ...

csharpbird

  • Newt
  • Posts: 64
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #3 on: April 08, 2012, 02:17:43 PM »
It seems that AutoCAD will crash if I run your codes.

kaefer

  • Guest
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #4 on: April 08, 2012, 04:51:03 PM »
It seems that AutoCAD will crash if I run your codes.

That is entirely possible. I'm just lucky when running this under AutoCAD 2012.
Code - C#: [Select]
  1. [DllImport(
  2.   "acdb18.dll",
  3.   CallingConvention = CallingConvention.ThisCall,
  4.   EntryPoint = "?getAreaProp@AcDbRegion@@UEBA?AW4ErrorStatus@Acad@@AEBVAcGePoint3d@@AEBVAcGeVector3d@@1AEAN2AEAVAcGePoint2d@@QEAN24QEAVAcGeVector2d@@433@Z"
  5.                // "?getAreaProp@AcDbRegion@@UBE?AW4ErrorStatus@Acad@@ABVAcGePoint3d@@ABVAcGeVector3d@@1AAN2AAVAcGePoint2d@@QAN24QAVAcGeVector2d@@433@Z"
  6.  )]
  7. private static extern ErrorStatus GetAreaProp(IntPtr region,
  8.                                         Point3d origin,
  9.                                         Vector3d xAxis,
  10.                                         Vector3d yAxis,
  11.                                         out double perimeter,
  12.                                         out double area,
  13.                                         out Point2d centroid,
  14.                                         double[] momInertia,
  15.                                         out double prodInertia,
  16.                                         double[] prinMoments,
  17.                                         Vector2d[] prinAxes,
  18.                                         double[] radiiGyration,
  19.                                         out Point2d extentsLow,
  20.                                         out Point2d extentsHigh);
  21. [CommandMethod("Test")]
  22. public void Test()
  23. {
  24.     Database db = HostApplicationServices.WorkingDatabase;
  25.     Document doc = Application.DocumentManager.MdiActiveDocument;
  26.     Editor ed = doc.Editor;
  27.     CoordinateSystem3d cs3d = ed.CurrentUserCoordinateSystem.CoordinateSystem3d;
  28.     PromptEntityOptions peo = new PromptEntityOptions("ent");
  29.     peo.SetRejectMessage("Not region.");
  30.     peo.AddAllowedClass(typeof(Region), false);
  31.     PromptEntityResult per = ed.GetEntity(peo);
  32.     if (per.Status != PromptStatus.OK)
  33.         return;
  34.     double perimeter;
  35.     double area;
  36.     Point2d centroid;
  37.     double[] momInertia = new double[2];
  38.     double prodInertia;
  39.     double[] prinMoments = new double[2];
  40.     Vector2d[] prinAxes = new Vector2d[2];
  41.     double[] radiiGyration = new double[2];
  42.     Point2d extentsLow;
  43.     Point2d extentsHigh;
  44.     ErrorStatus es;
  45.     using (Transaction trans = db.TransactionManager.StartTransaction())
  46.     {
  47.         Region region = (Region)trans.GetObject(per.ObjectId, OpenMode.ForRead);
  48.         es =
  49.             GetAreaProp(region.UnmanagedObject,
  50.                 cs3d.Origin,
  51.                 cs3d.Xaxis,
  52.                 cs3d.Yaxis,
  53.                 out perimeter,
  54.                 out area,
  55.                 out centroid,
  56.                 momInertia,
  57.                 out prodInertia,
  58.                 prinMoments,
  59.                 prinAxes,
  60.                 radiiGyration,
  61.                 out extentsLow,
  62.                 out extentsHigh);
  63.         if(es != ErrorStatus.OK)
  64.         {
  65.             ed.WriteMessage("\nErrorStatus: {0} ", es);
  66.         }
  67.         else
  68.         {
  69.             ed.WriteMessage("\nPerimeter: {0} ", perimeter);
  70.             ed.WriteMessage("\nArea: {0} ", area);
  71.             ed.WriteMessage("\nCentroid: {0} ", centroid);
  72.             ed.WriteMessage("\nMomInertia: {0}; {1} ", momInertia[0], momInertia[1]);
  73.             ed.WriteMessage("\nProdInertia: {0} ", prodInertia);
  74.             ed.WriteMessage("\nPrinMoments: {0}; {1} ", prinMoments[0], prinMoments[1]);
  75.             ed.WriteMessage("\nPrinAxes: {0}; {1} ", prinAxes[0], prinAxes[1]);
  76.             ed.WriteMessage("\nRadiiGyration: {0}; {1} ", radiiGyration[0], radiiGyration[1]);
  77.             ed.WriteMessage("\nExtentsLow: {0} ", extentsLow);
  78.             ed.WriteMessage("\nExtentsHigh: {0} ", extentsHigh);
  79.         }
  80.         trans.Commit();
  81.     }
  82. }

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #5 on: April 08, 2012, 09:37:32 PM »
Hi,

The .NET Region class has a Area property.

Yes, it does in
17.2.0.0 ; ac2009
to
19.0.0.0 ; ac2013

I don't have the 2008ARX  SDK here to check
... but if it's in ac2009 I'd hazard a guess that it's in ac2008 as well.
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #6 on: April 08, 2012, 09:41:16 PM »
It seems that AutoCAD will crash if I run your codes.

That is entirely possible. I'm just lucky when running this under AutoCAD 2012.
Code - C#: [Select]
  1. < .. >

great example kaefer
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

csharpbird

  • Newt
  • Posts: 64
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #7 on: April 09, 2012, 10:56:39 AM »
AutoCAD 2008 still crashes with kaefer's codes.

csharpbird

  • Newt
  • Posts: 64
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #8 on: April 09, 2012, 11:15:59 AM »
I finnally find the solution:
Code: [Select]
private static extern ErrorStatus GetAreaProp(IntPtr region,
                                                ref Point3d origin,
                                                ref Vector3d xAxis,
                                                ref Vector3d yAxis,
                                                out double perimeter,
                                                out double area,
                                                out Point2d centroid,
                                                double[] momInertia,
                                                out double prodInertia,
                                                double[] prinMoments,
                                                Vector2d[] prinAxes,
                                                double[] radiiGyration,
                                                out Point2d extentsLow,
                                                out Point2d extentsHigh);

TheMaster

  • Guest
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #9 on: April 09, 2012, 08:44:05 PM »
2) The second to fourth arguments aren't out parameters. I take them as origin and X- and Y-axis of a reference coordinate system, so that the function can ensure they lie in the same plane as your region - see 1).

I wasn't following this thread, but now that I see C#bird's last reply,
I'll put my $0.02 in.

If the native code requires a pointer or reference parameter, as is the case
here (AcGePoint3d& and AcGeVector3d&), you can't pass the equivalent
managed wrapper instance if it is a reference type, you have to pass a
reference to the managed wrapper instance, so the marshaler can supply
the location of the instance in memory, because that's what the native
API is expecting.

Incidentally, you should be able to pass a double[3] for either of those two
parameters as well, without having to pass it by reference, since  it's already
a reference type.
« Last Edit: April 09, 2012, 08:48:25 PM by TheMaster »

kaefer

  • Guest
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #10 on: April 10, 2012, 04:44:08 AM »
2) The second to fourth arguments aren't out parameters. I take them as origin and X- and Y-axis of a reference coordinate system, so that the function can ensure they lie in the same plane as your region - see 1).

Now I'm thoroughly confused. It is correct that the native code according to arxref.chm expects the passing of const AcGePoint3d& and const AcGeVector3d&, respectively. The difference between out and ref parameters is immaterial then.

But testing with the 64-bit version suggested that those were passed by value, not by reference.

Incidentally, you should be able to pass a double[3] for either of those two
parameters as well, without having to pass it by reference, since  it's already
a reference type.

Indeed, these extern declarations will both work under 32-bit:
Code - C#: [Select]
  1. static extern ErrorStatus GetAreaProp(
  2.     IntPtr region,
  3.     ref Point3d origin,
  4.     ref Vector3d xAxis,
  5.     ref Vector3d yAxis, ...)
  6. static extern ErrorStatus GetAreaProp(
  7.     IntPtr region,
  8.     double[] origin,
  9.     double[] xAxis,
  10.     double[] yAxis, ...)

TheMaster

  • Guest
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #11 on: April 10, 2012, 10:41:38 AM »
Now I'm thoroughly confused. It is correct that the native code according to arxref.chm expects the passing of const AcGePoint3d& and const AcGeVector3d&, respectively. The difference between out and ref parameters is immaterial then.


I don't believe the code you posted used either.

Quote

But testing with the 64-bit version suggested that those were passed by value, not by reference.


They are not passed by value, because the native API expects the
address of (e.g., pointer to) a struct (C++ references are functionally similar to
pointers with some semantic differences, the main one being that a reference
is implicitly dereferenced when it is used).

See http://msdn.microsoft.com/en-us/magazine/cc164123.aspx#S7


kaefer

  • Guest
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #12 on: April 10, 2012, 12:16:28 PM »
Now I'm thoroughly confused. It is correct that the native code according to arxref.chm expects the passing of const AcGePoint3d& and const AcGeVector3d&, respectively. The difference between out and ref parameters is immaterial then.

I don't believe the code you posted used either.

Right. The code I posted originally doesn't run on a 32-bit system, either.

Quote
Quote
But testing with the 64-bit version suggested that those were passed by value, not by reference.

They are not passed by value, because the native API expects the
address of (e.g., pointer to) a struct (C++ references are functionally similar to
pointers with some semantic differences, the main one being that a reference
is implicitly dereferenced when it is used).

See http://msdn.microsoft.com/en-us/magazine/cc164123.aspx#S7

Link doesn't help. It illustrates the concept of passing a reference, and does not elaborate in the context of p/invoke. At any rate, I was wrong and csharpbird seems to have found the solution.

I'm just amazed that in apparent contradiction to the native signature it is possible at all to call GetAreaProp with the aforementioned structs on the stack. If that isn't call-by-value then I don't know what is. Here's the code I originally posted, which does run on a 64-bit system, decompiled into IL.

Code: [Select]
.locals init (
...
[3] valuetype [acdbmgd]Autodesk.AutoCAD.Geometry.CoordinateSystem3d,
...
[18] class [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Region,
...
)
...
IL_00ae: ldloc.s 18
IL_00b0: callvirt instance native int [acdbmgd]Autodesk.AutoCAD.Runtime.DisposableWrapper::get_UnmanagedObject()
IL_00b5: ldloca.s 3
IL_00b7: call instance valuetype [acdbmgd]Autodesk.AutoCAD.Geometry.Point3d [acdbmgd]Autodesk.AutoCAD.Geometry.CoordinateSystem3d::get_Origin()
IL_00bc: ldloca.s 3
IL_00be: call instance valuetype [acdbmgd]Autodesk.AutoCAD.Geometry.Vector3d [acdbmgd]Autodesk.AutoCAD.Geometry.CoordinateSystem3d::get_Xaxis()
IL_00c3: ldloca.s 3
IL_00c5: call instance valuetype [acdbmgd]Autodesk.AutoCAD.Geometry.Vector3d [acdbmgd]Autodesk.AutoCAD.Geometry.CoordinateSystem3d::get_Yaxis()
...
IL_00de: call valuetype [acdbmgd]Autodesk.AutoCAD.Runtime.ErrorStatus Foo.Class1::GetAreaProp(
native int,
valuetype [acdbmgd]Autodesk.AutoCAD.Geometry.Point3d,
valuetype [acdbmgd]Autodesk.AutoCAD.Geometry.Vector3d,
valuetype [acdbmgd]Autodesk.AutoCAD.Geometry.Vector3d,
...
)

TheMaster

  • Guest
Re: How to P/Invoke AcdbRegion::getAreaProp in AutoCAD 2008?
« Reply #13 on: April 10, 2012, 01:26:41 PM »

Right. The code I posted originally doesn't run on a 32-bit system, either.


Well, all I can say is that when it is done correctly it works on any platform.

Quote

Link doesn't help. It illustrates the concept of passing a reference, and does not elaborate in the context of p/invoke.


Sure it does (quoted from that page):

Quote

When marshaling pointers through P/Invoke, ref and out are only used with value types in managed code. You can tell a parameter is a value type when its CLR type is defined using the struct keyword. Out and ref are used to marshal pointers to these data types because normally a value type variable is the object or data, and you don't have a reference to a value type in managed code. In contrast, the ref and out keywords are not necessary when marshaling reference type objects because the variable already is a reference to the object.


Quote

I'm just amazed that in apparent contradiction to the native signature it is possible at all to call GetAreaProp with the aforementioned structs on the stack. If that isn't call-by-value then I don't know what is. Here's the code I originally posted, which does run on a 64-bit system, decompiled into IL.


Because your code was passing the value of a read-only property of the CoordinateSystem3d instance to the native API, the native API won't complain or crash if it gets the wrong memory address, as long as what's located at that address can be read as an array of 3 doubles (where the memory address is the base address of the array). In other words, the fact that it didn't crash was pure dumb luck, and what values were actually read at that memory address could have been anything, including what looked like 3 doubles, but not necessarily the same three doubles you intended to pass it.