cad.net 句柄遍历数据库

说明

青蛙拿到了一种遍历数据库的新方式,然后发现这个东西遍历数据库奇慢.
这是一种利用了句柄来作为遍历的方式.
而句柄是递增模式的,直到递增到 db.Handseed.Value ,这个值非常大,有long长度的14位那么大,仅是天正导致?
为了停止它,我使用了一个变量,免得出现"生产消费模式"引起循环错误.

青蛙的信息来源

ObjectIds上缓慢的迭代
同时因为id = db.GetObjectId(false, handle, 0);在acad08的表现非常慢,所以我被迫调用了Arx函数.
但是当我和e大做完了之后,我发现上面链接的第二页居然有....

遍历组码

同时发现这是遍历dwg所有组码的方式,因为entget并不能获取动态块组码信息,所以才需要它.
貌似还可以ent.DxfOut

有了组码,就可以获取动态块可见性名称了,其他的或许也可以...
分析动态块组码见动态块专篇

Arx代码见e大博文遍历动态块块定义的可见性名称方法

代码

#if !HC2020
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Acap = Autodesk.AutoCAD.ApplicationServices.Application;
#else
using GrxCAD.DatabaseServices;
using GrxCAD.Runtime;
using GrxCAD.EditorInput;
using Acap = GrxCAD.ApplicationServices.Application;
#endif
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace JoinBox.试验库
{
    public class CmdTest_FastIterationClass
    {
        [CommandMethod("CmdTest_FastIteration")]
        public void CmdTest_FastIteration()
        {
            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;
            ed.WriteMessage($"{Environment.NewLine}利用句柄遍历数据库");
            var dic = db.HandleToObjectId();

            ed.WriteMessage($"{Environment.NewLine}利用句柄找到动态块可见性的名称");
            int entNum = 0;
            db.Action(tr => {
                foreach (var dicItem in dic)
                {
                    var id = dicItem.Value;
                    if (!id.IsOk())
                        continue;
                    try
                    {
                        string strDxfName = id.GetDxfName(tr);
                        //if (string.IsNullOrEmpty(strDxfName))
                        //    continue;
                        ed.WriteMessage($"{Environment.NewLine}图元{++entNum}:DxfName={strDxfName};句柄={dicItem.Key};Id={id}");

                        if (strDxfName == "BLOCKVISIBILITYPARAMETER")
                        {
                            AcdbAdsHelper.AcdbGetAdsName(out AdsName eName, id);
                            var rb = new ResultBuffer();
                            var elist = AcdbAdsHelper.AcdbEntGet(ref eName);
                            if (elist == IntPtr.Zero)
                                elist = AcdbAdsHelper.AcdbEntGetX(ref eName, rb.UnmanagedObject);
                            if (elist == IntPtr.Zero)
                                continue;
                            rb = ResultBuffer.Create(elist, true);
                            foreach (var tv in rb)
                            {
                                if (tv.TypeCode == 303)//动态块的可见性参数被改的名称记录位置
                                    PrintDxf(tv, ed);
                            }
                        }
                    }
                    catch (System.Exception e)
                    {
                        Debugger.Break();
                    }
                }
            });
            ed.WriteMessage($"{Environment.NewLine}结束了!");
        }

        public void PrintDxf(TypedValue ty, Editor ed)
        {
            ed.WriteMessage($"{Environment.NewLine}TypeCode:{ty.TypeCode};;;Value:{ty.Value}");
        }
    }

    public static class DatabaseHelper
    {
        public static string GetDxfName(this ObjectId id, Transaction tr)
        {
            string strDxfName;
#if !NET35
            strDxfName = id.ObjectClass.DxfName;
#else
            DBObject obj = tr.GetObject(id, OpenMode.ForRead);
            //strDxfName = RXClass.GetClass(obj.GetType()).DxfName;//这里并不能准确获取到dxfName
            strDxfName = obj.GetRXClass().DxfName;
#endif
            return strDxfName;
        }

        /// <summary>
        /// 遍历句柄获取数据库的ids
        /// 可能是已经删除的,不一定满足id.IsOk()
        /// </summary>
        /// <param name="db"></param>
        /// <param name="ed">控制是否打印时间</param>
        public static Dictionary<Handle, ObjectId> HandleToObjectId(this Database db, Editor ed = null)
        {
            Dictionary<Handle, ObjectId> dicResult = new();

            DateTime start = DateTime.Now;//时间计数
            long time = 0;                //时间计数

            db.TraverseHandle(h => {
                var handle = new Handle(h);
                var id = db.TryGetObjectId(handle);
                if (id != ObjectId.Null)
                    dicResult.Add(handle, id);
                if (ed != null)
                {
                    //计时输出
                    var len = DateTime.Now - start;
                    if (len.Seconds != time && len.Seconds % 5 == 0)//5秒输出一次
                    {
                        time = len.Seconds;
                        ed.WriteMessage($"{Environment.NewLine}数量: {h},已经过了{len.Days}天,{len.Hours}小时,{len.Minutes}分,{len.Seconds}秒");
                        ed.UpdateScreenEx();//此函数写在 https://www.cnblogs.com/JJBox/p/11354224.html
                    }
                }
            });
            return dicResult;
        }

        /// <summary>
        /// 遍历句柄
        /// </summary>
        /// <param name="db">数据库</param>
        /// <param name="action">扔出去个迭代句柄数字</param>
        static void TraverseHandle(this Database db, Action<long> action)
        {
#if !NET35
            var entlast = Autodesk.AutoCAD.Internal.Utils.EntLast().OldIdPtr;
            var btids = db.BlockTableId.OldIdPtr;
            if (IntPtr.Size == 4)
            {
                var endHandle = entlast.ToInt32();
                var h2 = btids.ToInt32();
                for (var h = h2; h < endHandle; h++)
                    action(h);
            }
            else if (IntPtr.Size == 8)
            {
                var endHandle = entlast.ToInt64();
                var h2 = btids.ToInt64();
                for (var h = h2; h < endHandle; h++)
                    action(h);
            }
#else
            var endHandle = db.Handseed.Value;//写到这里防止句柄出现递增
            var h2 = db.BlockTableId.Handle.Value;
            for (var h = h2; h < endHandle; h++)
                action(h);
#endif
        }
    }

    public static class TryGetObjectIdHelper
    {
        /*
         * id = db.GetObjectId(false, handle, 0);
         * 参数意义: db.GetObjectId(如果没有找到就创建,句柄号,标记..将来备用)
         * 在vs的输出会一直抛出:
         * 引发的异常:“Autodesk.AutoCAD.Runtime.Exception”(位于 AcdbMgd.dll 中)
         * "eUnknownHandle"
         * 这就是为什么慢的原因,所以直接运行就好了!而Debug还是需要用arx的API替代.
         */

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/,
           EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QAE?AW4ErrorStatus@Acad@@AAVAcDbObjectId@@_NABVAcDbHandle@@K@Z")]
        extern static int getAcDbObjectId17x32(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/,
          EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QEAA?AW4ErrorStatus@Acad@@AEAVAcDbObjectId@@_NAEBVAcDbHandle@@K@Z")]
        extern static int getAcDbObjectId17x64(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/,
           EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QAE?AW4ErrorStatus@Acad@@AAVAcDbObjectId@@_NABVAcDbHandle@@K@Z")]
        extern static int getAcDbObjectId18x32(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/,
          EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QEAA?AW4ErrorStatus@Acad@@AEAVAcDbObjectId@@_NAEBVAcDbHandle@@K@Z")]
        extern static int getAcDbObjectId18x64(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved);

        /// <summary>
        /// 句柄转id,NET35(08~12)专用的
        /// </summary>
        /// <param name="db">数据库</param>
        /// <param name="handle">句柄</param>
        /// <param name="id">返回的id</param>
        /// <param name="createIfNotFound">不存在则创建</param>
        /// <param name="reserved">保留,用于未来</param>
        /// <returns>成功0,其他值都是错误.可以强转ErrorStatus</returns>
        static int GetAcDbObjectId(IntPtr db, Handle handle, out ObjectId id, bool createIfNotFound = false, uint reserved = 0)
        {
            id = ObjectId.Null;
            switch (Acap.Version.Major)
            {
                case 17:
                {
                    if (IntPtr.Size == 4)
                        return getAcDbObjectId17x32(db, out id, createIfNotFound, ref handle, reserved);
                    else
                        return getAcDbObjectId17x64(db, out id, createIfNotFound, ref handle, reserved);
                }
                case 18:
                {
                    if (IntPtr.Size == 4)
                        return getAcDbObjectId18x32(db, out id, createIfNotFound, ref handle, reserved);
                    else
                        return getAcDbObjectId18x64(db, out id, createIfNotFound, ref handle, reserved);
                }
            }
            return -1;
        }

        /// <summary>
        /// 句柄转id
        /// </summary>
        /// <param name="db">数据库</param>
        /// <param name="handle">句柄</param>
        /// <returns>id</returns>
        public static ObjectId TryGetObjectId(this Database db, Handle handle)
        {
#if !NET35
            //高版本直接利用
            var es = db.TryGetObjectId(handle, out ObjectId id);
            //if (!es)
#else
            var es = GetAcDbObjectId(db.UnmanagedObject, handle, out ObjectId id);
            //if (ErrorStatus.OK != (ErrorStatus)es)
#endif
            return id;
        }

        /// <summary>
        /// 此命令用来测试:内部调用NET35(08~12)专用的 GetAcDbObjectId,
        /// 外部调用封装的 TryGetObjectId.
        /// </summary>
        [CommandMethod("CmdTest_handloop")]
        public static void CmdTest_handloop()
        {
            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;

            for (long i = 1; i < db.Handseed.Value; i++)
            {
                Handle handle = new(i);

                var es = GetAcDbObjectId(db.UnmanagedObject, handle, out ObjectId id);
                if (ErrorStatus.OK == (ErrorStatus)es)
                    ed.WriteMessage("\n执行成功");
                if (id.IsNull)
                    continue;
                ed.WriteMessage("\nid不是空的");
                if (id.IsValid)
                    ed.WriteMessage($"\n有效ID:{id},句柄是{handle}");
                if (id.IsErased)
                {
                    ed.WriteMessage("是已经删除的ID");
                    continue;
                }
                using (var tr = db.TransactionManager.StartTransaction())
                {
                    DBObject obj = tr.GetObject(id, OpenMode.ForRead);
                    if (obj == null)
                        continue;
                    if (obj != null)
                        ed.WriteMessage($"\n句柄={handle};DxfName={id.GetDxfName(tr)}");

                    //九个符号表
                    if (obj is BlockTable)
                        ed.WriteMessage("\nID是table");
                }
            }
        }
    }
}

缺省函数

AcdbAdsHelper.AcdbEntGet

db.Action

操作备注

edata
而且我发现一个问题,这个句柄,如果你保存了,可能会写入很多的附加对象,句柄就会浪费很多..
比如打开文件,句柄是2c5,继续画直线,2c6.... 2c7...2c8 如果保存了.
就从 313 开始..中间因保存,重新打开后句柄浪费了75个左右...
ctrl+c应该是先复制一个对象到 剪贴板,然后粘贴.
所以要多生成几次.co 就不会乱增..

img

(完)

posted @ 2020-03-14 07:13  惊惊  阅读(1057)  评论(0编辑  收藏  举报