ADN Open CIS
Сообщество программистов Autodesk в СНГ

18/04/2014

Вызов PInvoke ObjectARX AcArray для Hatch.AppendLoop in C#

Это смешно, но иногда такие вещи встречаются. Совсем недавно общался с разработчиком, который рассказывал мне о проблемах использования смешанного (“mixed”) кода (C++) Ему приходилось привязываться к конкретной версии .NET, так как без этого не работали другие связанные с этой части программы.

Я предпочитаю не пользоваться смешанным (“mixed”) кодом, так как он действительно добавляет головной боли и мешает развивать программу. Поэтому очевиден вопрос: "Зачем вам нужен смешанный режим DLL?" После долгой беседы, оказывается, что единственная причина – необходимость вызвать конкретную версию ObjectARX AcDbHatch::appendLoop, которая почему-то не реализована в .NET. Вот её определение в ObjectARX:

Код - C++: [Выделить]
  1. Acad::ErrorStatus appendLoop( Adesk::Int32 loopType,
  2. const AcGePoint2dArray& vertices,
  3. const AcGeDoubleArray& bulges );

 

Главная проблема P/Invoke этой функции заключается в том, что  нет эквивалента AcGeDoubleArray в .NET API (Думаю, что именно по этой причине этот метод и не был реализован в .NET). Во всяком случае, мы (DevTech) заявили разработчикам некоторое время назад, что эта функция не будет поддержана в P/Invoke, потому что разработчик настаивал, что она нуждался в смешанном режиме DLL.

Тем не менее всё можно реализвать через P/Invoke – всё зависит от того сколько времени и сил вы готовы потратить для этого. Я полагаю, что обходные пути для этого были бы:

  1. PInvoke
  2. Работа с кривизной (Bulge) используя типы AcGeCurves и Edge

Я считаю, что P/Invoke было бы намного проще (и доставит намного больше удовольствия! )

Так, вызов  P/Invoke этого метода appendLoop() следует делать осторожно, так как определения массива doubles в .NET и передача его в «неуправляемый» код следует делать очень осторожно. Основная причина – сборка мусора в .NET – всё что размещено - может быть перенесено .NET GC , что приведет к краху неуправляемого кода, который его использует. Так что требуется зафиксировать размещенную GC память, чтобы GC знал, что её не следует трогать. Фиксация памяти in C#.NET делается при помощи ключевого слова fixed.

Другой важный момент – размер и выравнивание элементов в классах/структурах в .NET должны быть точно такими же, как и в неуправляемом коде. Для этой цели используется атрибут StructLayout,

Т.е.:

[StructLayout(LayoutKind.Auto)]

Перед тем как смотреть код, я хотел бы напомнить вам об указателях на ячейки памяти в .NET!! Всё это требует ключевое слово unsafe  для того, чтобы отметить часть кода как доступный для переполнения буфера, и т.д., так что исполняющая система .NET могла как-нибудь защититься от хакеров

Код - C#: [Выделить]
  1. // Вызов appendLoop() при помощи P/Invoke, используя "fixed" память
  2. // in "небезопасном" ("unsafe") .NET контексте
  3. // by Fenton Webb, DevTech, Autodesk, 05/06/2012
  4. [CommandMethod("HatchLoop")]
  5. static public void HatchLoop()
  6. {
  7.   Document doc = Application.DocumentManager.MdiActiveDocument;
  8.   Database db = doc.Database;
  9.   Editor ed = doc.Editor;
  10.   using (Transaction Tx = db.TransactionManager.StartTransaction()) {
  11.     Hatch hatch = new Hatch();
  12.     hatch.SetDatabaseDefaults();
  13.     hatch.Elevation = 0.0;
  14.     hatch.Normal = Vector3d.ZAxis;
  15.     hatch.SetHatchPattern(HatchPatternType.PreDefined, "AR-B816");
  16.     Point2dCollection vertices = new Point2dCollection();
  17.     vertices.Add(new Point2d(0, 0));
  18.     vertices.Add(new Point2d(100, 0));
  19.     vertices.Add(new Point2d(100, 50));
  20.     vertices.Add(new Point2d(0, 50));
  21.     vertices.Add(new Point2d(0, 0));
  22.     DoubleCollection bulges = new DoubleCollection();
  23.     bulges.Add(0.0);
  24.     bulges.Add(0.0);
  25.     bulges.Add(0.0);
  26.     bulges.Add(0.0);
  27.     bulges.Add(0.0);
  28.     unsafe {
  29.       // Создаем маршалер класс AcGePoint2dArray
  30.       AcArray verticesAcArray = new AcArray();
  31.       // преобразуем в массив double
  32.       Point2d[] verticesArray = vertices.ToArray();
  33.       // получаем указатель и закрепляем его, чтобы GC не мог его переместить
  34.       fixed (Point2d* verticesPtr = &verticesArray[0]) {
  35.         // прописываем свойства acarray
  36.         verticesAcArray.array = (IntPtr)verticesPtr;
  37.         verticesAcArray.mPhysicalLen = verticesArray.Length;
  38.         verticesAcArray.mLogicalLen = verticesArray.Length;
  39.         // создаем маршалер класс AcGeDoubleArray
  40.         AcArray bulgesAcArray = new AcArray();
  41.         double[] bulgesArray = bulges.ToArray();
  42.         // получаем указатель и закрепляем его, чтобы GC не мог его переместить
  43.         fixed (double* bulgesPtr = &bulgesArray[0]) {
  44.           // записываем свойства acarray
  45.           bulgesAcArray.array = (IntPtr)bulgesPtr;
  46.           bulgesAcArray.mPhysicalLen = bulgesArray.Length;
  47.           bulgesAcArray.mLogicalLen = bulgesArray.Length;
  48.           appendLoop(
  49.           hatch.UnmanagedObject,
  50.           HatchLoopType.kDefault,
  51.           ref verticesAcArray.array,
  52.           ref bulgesAcArray.array);
  53.           hatch.EvaluateHatch(false);
  54.           BlockTableRecord btr = Tx.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord;
  55.           btr.AppendEntity(hatch);
  56.           Tx.AddNewlyCreatedDBObject(hatch, true);
  57.           Tx.Commit();
  58.         }
  59.       }
  60.     }
  61.   }
  62. }
  63. enum HatchLoopType
  64. {
  65.   kDefault = 0,
  66.   kExternal = 1,
  67.   kPolyline = 2,
  68.   kDerived = 4,
  69.   kTextbox = 8,
  70.   kOutermost = 0x10,
  71.   kNotClosed = 0x20,
  72.   kSelfIntersecting = 0x40,
  73.   kTextIsland = 0x80,
  74.   kDuplicate = 0x100,
  75.   kIsAnnotative = 0x200,
  76.   kDoesNotSupportScale = 0x400,
  77.   kForceAnnoAllVisible = 0x800,
  78.   kOrientToPaper = 0x1000,
  79.   kIsAnnotativeBlock = 0x2000
  80. };
  81. [StructLayout(LayoutKind.Auto)]
  82. struct AcArray
  83. {
  84.   public IntPtr array;
  85.   public int mPhysicalLen;
  86.   public int mLogicalLen;
  87.   public int mGrowLen;
  88. };
  89. // Для AutoCAD 2013 и 2014 - "acdb19.dll", для AutoCAD 2015 - "acdb20.dll"
  90. [DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall,
  91. CharSet = CharSet.Unicode,
  92. EntryPoint = "?appendLoop@AcDbHatch@@QAE?AW4ErrorStatus@Acad" +
  93. "@@JABV?$AcArray@VAcGePoint2d@@V?$AcArrayMemCopyReallocator@" +
  94. "VAcGePoint2d@@@@@@ABV?$AcArray@NV?$AcArrayMemCopyReallocator@N@@@@@Z")]
  95. private static extern int appendLoop32(IntPtr ths,
  96. HatchLoopType loopType,
  97. ref IntPtr vertices,
  98. ref IntPtr bulges);
  99. // Для AutoCAD 2013 и 2014 - "acdb19.dll", для AutoCAD 2015 - "acdb20.dll"
  100. [DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall,
  101. CharSet = CharSet.Unicode,
  102. EntryPoint = "?appendLoop@AcDbHatch@@QEAA?AW4ErrorStatus@Acad" +
  103. "@@JAEBV?$AcArray@VAcGePoint2d@@V?$AcArrayMemCopyReallocator@" +
  104. "VAcGePoint2d@@@@@@AEBV?$AcArray@NV?$AcArrayMemCopyReallocator@N@@@@@Z")]
  105. private static extern int appendLoop64(IntPtr ths,
  106. HatchLoopType loopType,
  107. ref IntPtr vertices,
  108. ref IntPtr bulges);
  109. private static int appendLoop(IntPtr ths,
  110. HatchLoopType loopType,
  111. ref IntPtr vertices,
  112. ref IntPtr bulges)
  113. {
  114.   if (Marshal.SizeOf(IntPtr.Zero) > 4)
  115.     return appendLoop64(ths, loopType, ref vertices, ref bulges);
  116.   return appendLoop32(ths, loopType, ref vertices, ref bulges);
  117. }

Источник: http://adndevblog.typepad.com/autocad/2012/06/pinvoking-an-objectarx-acarray-for-hatchappendloop-in-cnet.html

Обсуждение: http://adn-cis.org/forum/index.php?topic=692

Опубликовано 18.04.2014
Отредактировано 19.04.2014 в 00:27:08