3. Проверка валидности ObjectId с помощью P/Invoke

    В этом разделе мы напишем класс, унаследованный от абстрактного класса DBSearcher и реализующий проверку валидности ObjectId посредством P/Invoke, дабы избежать возможности генерации исключений, которые нам приходилось бы обрабатывать в блоке try/catch, что в свою очередь весьма негативно сказалось бы на производительности. Обозначенному классу присвоим имя DBSearcher_PInvoke. 

    Забегая вперёд, хочу поблагодарить Александра Ривилиса, написавшего P/Invoke методы (под каждую версию AutoCAD - свой метод), используемые мною ниже в коде! Благодаря этим методам удалось избежать возникновения исключений (Exceptions), существенно сократив тем самым общее время итерации.

Класс DBSearcher_PInvoke

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//Microsoft
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;
//AutoCAD
using acad = Autodesk.AutoCAD.ApplicationServices.Application;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
namespace Bushman.AutoCAD.Common {
/// <summary>
/// Класс, предназначенный для извлечения информации из базы данных чертежа и,
/// в случае необходимости, редактирования существующих в ней объектов.
/// Во всех методах класса из обработки ИСКЛЮЧАЮТСЯ объекты, значение
/// идентификатора которых равно ObjectId.Null, либо свойство IsValid
/// идентификатора равно false
/// </summary>
public sealed class DBSearcher_PInvoke : DBSearcher {
public DBSearcher_PInvoke(Database db) : base(db) { }
/// <summary>
/// Метод, с помощью которого определяется валидность ObjectId. В данной реализации
/// проверка определяется через P/Invoke
/// </summary>
/// <param name="h">Проверяемый хэндл</param>
/// <param name="id">ссылка на ObjectId, в котором следует сохранить результат
/// в случае успешной проверки</param>
/// <returns>true - проверка прошла успешно, иначе - false</returns>
protected override bool IsValid(Handle h, ref ObjectId id) {
return getAcDbObjectId(TargetDb.UnmanagedObject, ref id, false, ref h, 0) == 0 && id != ObjectId.Null && id.IsValid;
}
//Ниже приведён набор методов, с помощью которых можно получить значение идентификатора, минуя при этом генерацию
//исключения, если запрашиваемый идентификатор отсутствует в базе данных чертежа (это существенно ускоряет
//производительность)
//НИЗКИЙ ПОКЛОН АЛЕКСАНДРУ РИВИЛИСУ, НАПИСАВШЕМУ ЭТИ МЕТОДЫ!!!!!
//###############################################################################################################
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall,
EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QAE?AW4ErrorStatus@Acad@@AAVAcDbObjectId@@_NABVAcDbHandle@@K@Z")]
extern static int getAcDbObjectId17x32(IntPtr db, ref ObjectId id, bool createnew, ref Handle h, Int32 reserved);
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall,
EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QEAA?AW4ErrorStatus@Acad@@AEAVAcDbObjectId@@_NAEBVAcDbHandle@@K@Z")]
extern static int getAcDbObjectId17x64(IntPtr db, ref ObjectId id, bool createnew, ref Handle h, Int32 reserved);
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall,
EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QAE?AW4ErrorStatus@Acad@@AAVAcDbObjectId@@_NABVAcDbHandle@@K@Z")]
extern static int getAcDbObjectId18x32(IntPtr db, ref ObjectId id, bool createnew, ref Handle h, Int32 reserved);
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall,
EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QEAA?AW4ErrorStatus@Acad@@AEAVAcDbObjectId@@_NAEBVAcDbHandle@@K@Z")]
extern static int getAcDbObjectId18x64(IntPtr db, ref ObjectId id, bool createnew, ref Handle h, Int32 reserved);
/// <summary>
/// Получение идентификатора объекта на основе его "хэндла". Идентификатор будет получен только в том случае если
/// он существует
/// </summary>
/// <param name="db">Указатель на базу данные. Этому параметру следует передавать свойство
/// UnmanagedObject объекта Database</param>
/// <param name="id">Ссылка не экземпляр ObjectId, которому следует присвоить значение, если оно существует</param>
/// <param name="createnew">Следует ли создавать идентификатор, если его не существует</param>
/// <param name="h">Ссылка на "хэндл" объекта</param>
/// <param name="reserved">В этот параметр передаём всегда 0</param>
/// <returns>Если удалось получить идентификатор - возвращается число, не равное 0, иначе - 0</returns>
private static int getAcDbObjectId(IntPtr db, ref ObjectId id, bool createnew, ref Handle h, Int32 reserved) {
switch (Application.Version.Major) {
case 17: {
if (IntPtr.Size == 4) return getAcDbObjectId17x32(db, ref id, createnew, ref h, reserved);
else return getAcDbObjectId17x64(db, ref id, createnew, ref h, reserved);
}
case 18: {
if (IntPtr.Size == 4) return getAcDbObjectId18x32(db, ref id, createnew, ref h, reserved);
else return getAcDbObjectId18x64(db, ref id, createnew, ref h, reserved);
}
}
return -1;
}
//###############################################################################################################
}
}

     Обратите внимание на ряд методов, с помощью которых мы можем вызывать нужную нам функцию из внешней неуправляемой библиотеки AutoCAD. Наличие нескольких функций обусловлено тем, что каждая из них ориентирована на работу со своей версией AutoCAD. Управление этими методами берёт на себя метод getAcDbObjectId, который на основании версии и разрядности AutoCAD определяет, какой именно из  extern-методов нужно запустить для получения значения ObjectId. Если в результате работы метода будет возвращён 0 - значит идентификатор не действителен, в противном случае идентификатор будет находится в переменной, которая передаётся посредством ссылки параметру id.

    Визуально класс DBSearcher_PInvoke можно представить следующим образом:


        Теперь давайте напишем код, с помощью которого мы протестируем на предмет скорости работу методов GetByTypes и GetAllObjects.

Тест производительности класса DBSearcher_PInvoke

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//Microsoft
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
//AutoCAD
using acad = Autodesk.AutoCAD.ApplicationServices.Application;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Interop.Common;
//[assembly: CommandClass(typeof(Bushman.AutoCAD.Common.DBSearcher_Pinvoke_Test))]
namespace Bushman.AutoCAD.Common {
public sealed class DBSearcher_Pinvoke_Test {
[CommandMethod("PInvokeTest1", CommandFlags.Modal)]
public void PInvokeTest1() {
Document dwg = acad.DocumentManager.MdiActiveDocument;
Database TargetDb = dwg.Database;
Editor ed = dwg.Editor;
long amount = 0;
Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
IDBSearcher dbs = new DBSearcher_PInvoke(TargetDb);
Dictionary<string, List<ObjectId>> dict = dbs.GetByTypes((n, m) => ++amount > 0);
sw.Stop();
//На консоль AutoCAD выводим информацию (упорядочив по алфавиту) о том, какого класса
//сколько объектов имеется в БД.
foreach (string item in dict.Keys.OrderBy(n => n))
ed.WriteMessage(string.Format("Класс: {0};\tКол-во: {1}\n", item, dict[item].Count));
ed.WriteMessage(string.Format("Всего объектов в БД: {0}\nВремя итерации: {1}\n",
amount, sw.Elapsed));
}
[CommandMethod("PInvokeTest2", CommandFlags.Modal)]
public void PInvokeTest2() {
Document dwg = acad.DocumentManager.MdiActiveDocument;
Database TargetDb = dwg.Database;
Editor ed = dwg.Editor;
Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
IDBSearcher dbs = new DBSearcher_PInvoke(TargetDb);
ObjectId[] ids = dbs.GetAllObjects(DBObjectStatus.NotErased);
sw.Stop();
ed.WriteMessage(string.Format("Всего объектов в БД: {0}\nВремя итерации: {1}\n",
ids.Length, sw.Elapsed));
}
}
}

    Результат работы методов PInvokeTest1 и PInvokeTest2 будет следующим:

Command: PInvokeTest1
Класс: AcDb2dPolyline;  Кол-во: 9
Класс: AcDb2dVertex;       Кол-во: 1518
Класс: AcDb3dPolyline;  Кол-во: 10
Класс: AcDb3dPolylineVertex; Кол-во: 290
Класс: AcDbAlignedDimension; Кол-во: 50
Класс: AcDbArc;  Кол-во: 28331
Класс: AcDbBlockBegin;  Кол-во: 479
Класс: AcDbBlockEnd;       Кол-во: 479
Класс: AcDbBlockReference;      Кол-во: 2789
Класс: AcDbBlockTable;  Кол-во: 1
Класс: AcDbBlockTableRecord; Кол-во: 479
Класс: AcDbCellStyleMap;    Кол-во: 2
Класс: AcDbCircle;     Кол-во: 1857
Класс: AcDbDataTable; Кол-во: 1
Класс: AcDbDictionary;  Кол-во: 95
Класс: AcDbDictionaryVar;     Кол-во: 9
Класс: AcDbDictionaryWithDefault;      Кол-во: 1
Класс: AcDbDimAssoc;       Кол-во: 30
Класс: AcDbDimStyleTable;     Кол-во: 1
Класс: AcDbDimStyleTableRecord;    Кол-во: 5
Класс: AcDbEllipse;      Кол-во: 142
Класс: AcDbFontTable; Кол-во: 1
Класс: AcDbFontTableRecord;       Кол-во: 12
Класс: AcDbHatch;    Кол-во: 1611
Класс: AcDbLayerTable;  Кол-во: 1
Класс: AcDbLayerTableRecord; Кол-во: 66
Класс: AcDbLayout;     Кол-во: 5
Класс: AcDbLine;   Кол-во: 511892
Класс: AcDbLinetypeTable;     Кол-во: 1
Класс: AcDbLinetypeTableRecord;    Кол-во: 28
Класс: AcDbMaterial;       Кол-во: 3
Класс: AcDbMLeaderStyle;    Кол-во: 1
Класс: AcDbMlineStyle;  Кол-во: 1
Класс: AcDbMText;    Кол-во: 152
Класс: AcDbPlaceHolder;   Кол-во: 1
Класс: AcDbPoint;    Кол-во: 22000
Класс: AcDbPolyline;       Кол-во: 161900
Класс: AcDbRasterImage;   Кол-во: 12
Класс: AcDbRasterImageDef;      Кол-во: 2
Класс: AcDbRasterImageDefReactor;      Кол-во: 12
Класс: AcDbRasterVariables;       Кол-во: 1
Класс: AcDbRegAppTable;   Кол-во: 1
Класс: AcDbRegAppTableRecord;  Кол-во: 22
Класс: AcDbScale;    Кол-во: 595
Класс: AcDbSequenceEnd;   Кол-во: 19
Класс: AcDbSolid;    Кол-во: 372
Класс: AcDbSortentsTable;     Кол-во: 15
Класс: AcDbSpline;     Кол-во: 275
Класс: AcDbTableStyle;  Кол-во: 2
Класс: AcDbText;   Кол-во: 648
Класс: AcDbTextStyleTable;      Кол-во: 1
Класс: AcDbTextStyleTableRecord;     Кол-во: 13
Класс: AcDbUCSTable;       Кол-во: 1
Класс: AcDbViewport;       Кол-во: 6
Класс: AcDbViewportTable;     Кол-во: 1
Класс: AcDbViewportTableRecord;    Кол-во: 1
Класс: AcDbViewTable; Кол-во: 1
Класс: AcDbVisualStyle;   Кол-во: 16
Класс: AcDbVXTable;      Кол-во: 1
Класс: AcDbVXTableRecord;     Кол-во: 3
Класс: AcDbXrecord;      Кол-во: 41
Класс: AcDbZombieObject;    Кол-во: 9
Всего объектов в БД: 736323
Время итерации: 00:00:16.1713293

Command: PInvokeTest2
Всего объектов в БД: 736323
Время итерации: 00:00:14.1793398

    Результаты тестов показывают, что в базе данных чертежа содержится 736 323 примитива - это полностью совпадает с количеством, полученным ранее, без использования P/Invoke, а вот на полную итерацию в этот раз потребовалась для первого теста 16 сек., вместо 5 мин. 30 сек, а для второго - 14 сек. вместо 5 мин. 36 сек.

    Однако такой результат так же нельзя назвать удовлетворительным, поскольку время выполнения оставляет желать лучшего. Рассмотрим третий вариант проверки - с помощью метода TryGetObjectId объекта Database.

Comments