cad.net 反射com獲取包圍盒從參數獲取返回的數據+net5不能用Marshal.GetActiveObject解決方案


故事

首先是飛詩在問了一個問題:Acad2007的com包圍盒無法正確獲取文字的包圍盒,問有沒有其他方法?
但是他測試了lisp的獲取是正確的,所以他想反射調用里面內置的.

而他會反射,但是獲取不到在參數傳回返回值.edata解決了這個問題,
他在《精通.NET互操作:P/Invoke,C++ Interop和COM.Interop》 黃際洲 崔曉源 編著
264頁中找到一段從參數返回結果的.

然后我測試的時候,發現net5桌面程序不能用:Marshal.GetActiveObject真是一個問題接一個..

這可怎么辦好呢?
雖然我在學net5的教學中知道了這個,但是它沒有給我解決方案.
然后南勝提醒了我一下,有using Microsoft.VisualBasic.Interaction.CreateObject可以用.
為此我完成了之前沒有完成的東西:com啟動Acad

在測試期間我還發現Acad08啟動之后獲取com對象,會獲取到17.1和17兩個對象,因此才知道了我之前做的com發送命令為什么發送了兩次...

發現啟動cad的幾種方式

1,注冊表獲取acad.exe路徑啟動:這樣
2,com版本號啟動,指定精確版本:"AutoCAD.Application.17.1".
3,com版本號啟動,指定大版本號,啟動的可能是17系列的,但是2007必然是這個啊:"AutoCAD.Application.17"
4,com不指定版本號,啟動最后一次的啟動:"AutoCAD.Application".
5,根據guid啟動,可以找注冊表上面對應的exe,也可以像下面一樣找com版本號

開干

寫一個net5的控制台調用這個,當然了,你喜歡低版本也可以...

new ReflectionAcad().GetBoundingBox();

然后復制粘貼這個就好了,各種的com技術都蠻有意思的.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Windows;
using Microsoft.VisualBasic;

namespace 測試_exe發送cad
{
    public class AcadClsId
    {
        //var acadGuid = AcadClsId.GetAcadGuid("17.1");
        //var bvv = AcadClsId.GetAcadVer("6AB55F46-2523-4701-2222-B226F46252BA");

        public static Dictionary<string, Guid> AcadClsIds = new Dictionary<string, Guid>()
        {
           {"15",   new Guid("8E75D911-3D21-11d2-85C4-080009A0C626")},
           {"16",   new Guid("1365A45F-0C8F-4806-A26A-6B22AD37EC66")},
           {"16.1", new Guid("FC280999-88C6-4499-9622-3B795A8B4A5F")},
           {"16.2", new Guid("F131FB74-0E12-4533-8091-D71FE9CCD91D")},
           {"17",   new Guid("28B7AA99-C0F9-4C47-995E-8A8D729603A1")},
           {"17.1", new Guid("6AB55F46-2523-4701-A912-B226F46252BA")},
           {"17.2", new Guid("2F1F7574-ECCA-4361-B4DE-C411BF7EEE23")},
           {"18",   new Guid("6D7AE628-FF41-4CD3-91DD-34825BB1A251")},
           {"18.1", new Guid("C92FB640-AD4D-498A-9979-A51A2540C977")},
           {"18.2", new Guid("B77E471C-FBF3-4CB5-880F-D7528AD4B349")},
           {"19",   new Guid("BD0DEB94-63DB-4392-9420-6EEE05094B1F")},
           {"19.1", new Guid("7DE1BE5C-CEBA-4F1D-ACBC-9CE11EE9A2A1")},
           {"20",   new Guid("0B628DE4-07AD-4284-81CA-5B439F67C5E6")},
           {"20.1", new Guid("5370C727-1451-4700-A960-77630950AF6D")},
           {"21",   new Guid("0D327DA6-B4DF-4842-B833-2CFF84F0948F")},
           {"22",   new Guid("9AAF0EB6-42D8-46C1-A2EF-679511B37A0D")},
           {"23",   new Guid("4AC6DFE1-607B-45B2-B289-D7FBCD44169C")},
           {"23.1", new Guid("D1DE6864-2236-48B7-99C3-D29C757903A4")},
        };

        /// <summary>
        /// 通過guid找cad版本號
        /// </summary>
        /// <param name="clsid"></param>
        /// <returns>不存在返回null</returns>
        public static string GetAcadVer(string clsid)
        {
            return GetAcadVer(new Guid(clsid));
        }

        /// <summary>
        /// 通過guid找cad版本號
        /// </summary>
        /// <param name="clsid"></param>
        /// <returns>不存在返回null</returns>
        public static string GetAcadVer(Guid clsid)
        {
            return AcadClsIds.FirstOrDefault(q => q.Value == clsid).Key;
        }

        /// <summary>
        /// 通過cad版本號找guid
        /// </summary>
        /// <param name="cadVer"></param>
        /// <returns></returns>
        public static Guid GetAcadGuid(string cadVer)
        {
            AcadClsIds.TryGetValue(cadVer, out Guid value);
            return value;
        }

    }


    public class AcadCom
    {
#if DEBUG2
        public AcadCom(string acadClsId)
        {
            this.Ver = acadClsId;
        }
#endif 
        public AcadCom(Guid acadClsId, object acadCom)
        {
            this.Com = acadCom;
            this.Guid = acadClsId;
            this.Ver = AcadClsId.GetAcadVer(acadClsId);
        }
        public double VerNumber
        {
            get
            {
                double.TryParse(Ver, out double verdouble);
                return verdouble;
            }
        }
        public string Ver { get; }
        public Guid Guid { get; }
        public object Com { get; }
    }

    //http://www.cadgj.com/?p=297
    public partial class AcadComTool
    {
        [DllImport("ole32.dll", EntryPoint = "CreateBindCtx")]
        static extern int _CreateBindCtx(int reserved, out IBindCtx ppbc);

        /// <summary>
        /// 獲取已經啟動的CAD進程的com
        /// </summary>
        /// <param name="apps"></param>
        public static void GetActiveAcadCom(List<AcadCom> apps, Guid? acadClsId = null)
        {
            _CreateBindCtx(0, out IBindCtx pbc);
            pbc.GetRunningObjectTable(out IRunningObjectTable pprot);

            pprot.EnumRunning(out IEnumMoniker ppenumMoniker);
            ppenumMoniker.Reset();
            IMoniker[] rgelt = new IMoniker[1];
            rgelt[0] = null;
            int rc = ppenumMoniker.Next(1, rgelt, IntPtr.Zero);

            List<Guid> guids = new();
            if (acadClsId == null)
            {
                guids = AcadClsId.AcadClsIds.Values.ToList();
            }
            else
            {
                guids.Add(acadClsId.Value);
            }

            while (rgelt[0] != null)
            {
                rgelt[0].GetDisplayName(pbc, null, out string ppszDisplayName);
#if DEBUG
                //不啟動cad的時候運行一次,看看有什么GUID,然后再啟動一個cad對比就知道了
                Debug.WriteLine(ppszDisplayName);
#endif 
                var guid = ConversionGuid(ppszDisplayName);
                if (guid != null && guids.Contains(guid.Value))
                {
                    //17.1存在的時候17也會存在,所以會有兩個obj被保存
                    pprot.GetObject(rgelt[0], out object obj);
                    if (obj == null)
                    {
                        continue;
                    }
                    apps.Add(new AcadCom(guid.Value, obj));
                }
                rgelt[0] = null;
                rc = ppenumMoniker.Next(1, rgelt, IntPtr.Zero);
            }
        }

        /// <summary>
        /// 轉為Guid: "!{6AB55F46-2523-4701-2222-B226F46252BA}"
        /// </summary>
        /// <param name="ppszDisplayName"></param>
        /// <returns></returns>
        static Guid? ConversionGuid(string ppszDisplayName)
        {
            //轉為GUID再匹配
            if (string.IsNullOrEmpty(ppszDisplayName))
            {
                return null;
            }
            //c#將此建議為下句 ppszDisplayName = ppszDisplayName.Substring(2, ppszDisplayName.Length - 3);
            ppszDisplayName = ppszDisplayName[2..^1];

            Guid guid;
            try
            {
                guid = new Guid(ppszDisplayName);
            }
            catch
            {
                return null;
            }
            return guid;
        }
    }


    public partial class ReflectionAcad
    {
        const string ProgID = "AutoCAD.Application";

        /// <summary>
        /// com方式啟動cad
        /// </summary>
        /// <returns></returns>
        private object StartAcad()
        {
            object acAcadApp = null;  
#if !NET50
            dynamic acAcadApp = Marshal.GetActiveObject(progID); 
#else
            //兩種方法,都會啟動一個新的Acad.
#if true2
            //**不阻塞啟動**
            var path = @"C:\Program Files\Autodesk\AutoCAD 2021\acad.exe";
            path = @"C:\Program Files (x86)\AutoCAD 2008\acad.exe";
            int acadid = Interaction.Shell(path, AppWinStyle.NormalFocus); //正常大小啟動 ,返回進程id
#else
            //**阻塞啟動**                 
            // acAcadApp = Interaction.GetObject("", ProgID);
            acAcadApp = Interaction.CreateObject(ProgID);
#endif
#endif    
            //acAcadApp = Activator.CreateInstance(comObjectName);

            return acAcadApp;
        }

        /// <summary>
        /// 通過cad的com進行反射調用VBA函數獲取包圍盒
        /// </summary>
        public void GetBoundingBox()
        {
            //與指定 ProgID 關聯的類型,即獲取相應的Com對象       
            var comObjectName = Type.GetTypeFromProgID(ProgID);
            if (comObjectName == null)
            {
                MessageBox.Show($"本機不存在:{ProgID}");
                return;
            }

            List<AcadCom> acadComs = new();
            AcadComTool.GetActiveAcadCom(acadComs);
            if (acadComs.Count == 0)
            {
                // Acad沒有啟動,那就去啟動它
                var acAcadApp2 = StartAcad();
                if (acAcadApp2 == null)
                {
                    MessageBox.Show($"無法打開程序:{ProgID}");
                    return;
                }
                acadComs.Add(new AcadCom(new Guid(), acAcadApp2));//此處GUID是無意義的
            }

#if DEBUG2
            acadComs.Add(new AcadCom("16"));
            acadComs.Add(new AcadCom("16.1"));
            acadComs.Add(new AcadCom("16.2"));
            acadComs.Add(new AcadCom("15.1"));
            acadComs.Add(new AcadCom("18.2"));
            acadComs.Add(new AcadCom("17.5"));
            acadComs.Add(new AcadCom("12.5"));
            acadComs.Add(new AcadCom("12.4"));
            acadComs.Add(new AcadCom("12"));
#endif 
            if (acadComs.Count > 1)//啟動cad的時候會是1,減少運算
            {
                //開啟了Acad08,那么它是17.1,此時17也會存在,所以會有兩個obj被保存,需要避免發送兩次命令到同一個cad內
                //因此,此處需要過濾同系列號的,舉出最高版本
                //排序17.2>17.1>17>16.1>16,將17.2保留,刪除17.1>17,保留16.1,刪除16
                acadComs = acadComs.OrderByDescending(item => item.VerNumber).ToList();
                for (int i = 0; i < acadComs.Count; i++)
                {
                    double dete = acadComs[i].VerNumber - (int)acadComs[i].VerNumber;//求小數部分
                    for (int j = i + 1; j < acadComs.Count; j++)
                    {
                        if (acadComs[i].VerNumber - acadComs[j].VerNumber <= dete)
                        {
                            acadComs.Remove(acadComs[j]);
                            j--;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }

            foreach (var comClass in acadComs)
            {
                var acAcadApp = comClass.Com;
#if true
                //參數
                object[] args = new object[1];
                //設置需要設置的參數值
                args[0] = true;
                //設置屬性-可視,顯示窗體
                comObjectName.InvokeMember("Visible", BindingFlags.SetProperty, null, acAcadApp, args);

                //獲取屬性
                object comAcDoc = comObjectName.InvokeMember("ActiveDocument", BindingFlags.GetProperty, null, acAcadApp, null);
                object comAcMsSpace = comObjectName.InvokeMember("ModelSpace", BindingFlags.GetProperty, null, comAcDoc, null);
                //調用VBA函數也就是com暴露的函數,畫在"激活的文檔"的"模型空間"然后輸入畫一條線的坐標數據.
                object[] lines = new object[] { new double[] { 100, 100, 0 }, new double[] { 300, 300, 0 } };
                object comAcLine = comObjectName.InvokeMember("AddLine", BindingFlags.InvokeMethod, null, comAcMsSpace, lines);

                //pts就是包圍盒返回的點集
                object[] pts = new object[2] { null, null };

                //由於需要從參數中返回結果,所以需要設置 ParameterModifier 作用在 InvokeMember 上
                var paramMod = new ParameterModifier(2);
                paramMod[0] = true;//設置為true才能改寫
                paramMod[1] = true;

                //求得這條線的包圍盒,返回給pts.
                comObjectName.InvokeMember("GetBoundingBox",
                    BindingFlags.SuppressChangeType | BindingFlags.InvokeMethod,
                    null, comAcLine, pts, new ParameterModifier[] { paramMod }, null, null);

                //全屏顯示
                comObjectName.InvokeMember("ZoomAll", BindingFlags.InvokeMethod, null, acAcadApp, null);
#else
                //c#4等效代碼
                acAcadApp.ZoomAll();
                dynamic acAcadDoc = acAcadApp.ActiveDocument;
                if (acAcadDoc != null)
                {
                    //acAcadDoc.SendCommand("_.Line 100,100 300,300  ");
                    dynamic acMsSpace = acAcadDoc.ModelSpace;
                    double[] p1 = new double[3] { 100.0, 100.0, 0.0 };
                    double[] p2 = new double[3] { 300.0, 300.0, 0.0 };

                    dynamic acLine = acMsSpace.AddLine(p1, p2);
                    object ptMin = new object();
                    object ptMax = new object();
                    acLine.GetBoundingBox(out ptMin, out ptMax);
                    MessageBox.Show(ptMin.ToString() + "\n" + ptMax.ToString());
                }
#endif 
                var a = (double[])pts[0];
                var b = (double[])pts[1];
                Debug.WriteLine($"MinPoint={a[0]},{a[1]},{a[2]}");
                Debug.WriteLine($"MaxPoint={b[0]},{b[1]},{b[2]}");
            }
        }
    }
}


(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM