外部.NET程序与AutoCAD交互.docx_第1页
外部.NET程序与AutoCAD交互.docx_第2页
外部.NET程序与AutoCAD交互.docx_第3页
外部.NET程序与AutoCAD交互.docx_第4页
外部.NET程序与AutoCAD交互.docx_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

外部.NET程序与AutoCAD交互外部.NET程序与AutoCAD交互(AutoCAD)开发者一般要么将功能集成到AutoCAD(通过其扩展体系来添加命令、用户界面元素、对象等),要么通过程序来驱动AutoCAD,使通用的任务自动化。这两种方式的界线有时候不是那么明显,我今天要关注的是后者。为了后面的解释方便,我先介绍一下两种应用程序的交互。 一、线程外此种情况下,我们需要在两个独立的可执行程序之间进行通信。就好比如我需要一个.EXE的执行程序来驱动AutoCAD,我们就需要找到某种方式来启动AutoCAD并且与之通信最典型的就是使用COM技术或更早的DDE技术。这种通信方式,确切地说,是通过进程间通信IPC(Inter-Process Communication)来完成,这种方式在传输大量数据时是非常低效的。这就是早期的ADS和外部VB应用程序运行很慢的原因。二、线程内当程序代码被编译成DLL,不管是VB建立的Activex Dll,ObjectARX模块,还是.NET的程序集,程序与AutoCAD主线程之间的通信都要高效得多数据结构可以通过指针来传递或直接引用,而不是通过低效的IPC编组来发送数据信息。目前AutoCAD大部分的APIs都是被设计用于线程内的包括LISP,ObjectARX,和.NET API。因为.NET Remoting的实用性,很多人常希望或期盼着AutoCAD能通过.NET从线程外部驱动,不过.NET的托管API并不是这么设计的它其实就是对ObjectARX的一个封装,其运行是以通过指针对内部对象的直接访问为基础的,完全不可能超越线程的界线。COM自动化技术的最大特点之一就是它就是被设计成既可以用于线程外(通过外部EXE)也可以用于线程内(通过VBA或通过GetInterfaceObject()来调用VB6的ActiveX DLL)。目前这仍然是从外部可执行程序驱动AutoCAD的最好方式。通常我不建议在进程间传递太多的信息。如果你需要从外部程序驱动AutoCAD,最好只是通过它运行AutoCAD(或是在可能的情况下连接到一个已运行的实例),接下来加载一个线程内的模块,让它在AutoCAD的进程内完成主要的任务。下面的代码就将展示如何用C来完成这一过程。它会尝试连接到一个已运行的AutoCAD实例(这是随意的你也可以把代码修改成直接运行一个AutoCAD),如果失败则运行。一旦有了正在运行的对象实例,使之可见且运行一个自定义命令。建议将程序设置成自动加载要么在AutoCAD启动时加载,要么在命令被触发时加载,然后运行一个模块中定义的命令。你需要添加对“AutoCAD Type Library”的引用,还有导入以下命名空间。using System; using System.Runtime.InteropServices; using Autodesk.AutoCAD.Interop;下面的代码你可以添加到比如某个按钮的Click事件中或其他有效的函数中去。代码如下/ AutoCAD.Application.17 uses 2007 or 2008, / whichever was most recently run / AutoCAD.Application.17.1 uses 2008, specifically const string progID = AutoCAD.Application.17.1; AcadApplication acApp = null; try acApp = (AcadApplication)Marshal.GetActiveObject(progID); catch try Type acType = Type.GetTypeFromProgID(progID); acApp = (AcadApplication)Activator.CreateInstance( acType, true ); catch MessageBox.Show( Cannot create object of type + progID + ); if (acApp != null) / By the time this is reached AutoCAD is fully / functional and can be interacted with through code acApp.Visible = true; acApp.ActiveDocument.SendCommand(_MYCOMMAND ); 前面讨论了如何通过COM接口从.NET程序运行AutoCAD并执行命令。这样就可以通过托管API与AutoCAD在其进程范围内交互。的确,这种方法在不需要返回结果时还是不错的,但是如果需要返回结果,那就有局限了。你可以通过AutoCAD用户变量或创建文件供程序来读取来达到返回结果的目的,不过这些方法都比较繁琐。所以,我决定尝试,尽管我一开始对此持怀疑态度。下面是我用来实现这一目的的步骤:首先,按照通常引用acmgd.dll,acdbmgd.dll的方法来创建一个类库作为进程内的部分,添加的C#代码如下:代码如下using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.Geometry; using System.Runtime.InteropServices; namespace LoadableComponent ProgId(LoadableComponent.Commands) public class Commands / 一个简单的命令 / 用来测试程序是否被正确加载 CommandMethod(MYCOMMAND) public void MyCommand() Document doc = Application.DocumentManager.MdiActiveDocument; Editor ed = doc.Editor; ed.WriteMessage(nTest command executed.); / 定义函数,将两数相加,并以结果为直径画圆 / 结果以string返回,以测试不同类型的返回值 public string AddNumbers(int arg1,double arg2) / 测试时发现DocumentManager.MdiActiveDocument为空 / 无法供我们使用 / 所以改用HostApplicationServices WorkingDatabase Database db = HostApplicationServices.WorkingDatabase; Document doc = Application.DocumentManager.GetDocument(db); / 执行两数的和运算 double res = arg1 + arg2; / 锁定文档 DocumentLock loc = doc.LockDocument(); using (loc) Transaction tr = db.TransactionManager.StartTransaction(); using (tr) / 创建圆 Circle cir = new Circle( new Point3d(0, 0, 0), new Vector3d(0, 0, 1), res ); cir.SetDatabaseDefaults(db); / 添加圆到目前的空间 BlockTableRecord btr = (BlockTableRecord)tr.GetObject( db.CurrentSpaceId, OpenMode.ForWrite ); btr.AppendEntity(cir); tr.AddNewlyCreatedDBObject(cir, true); / Commit tr.Commit(); / 返回字符串结果 return res.ToString(); 你会发现我为Commands类标记了一个ProgId-LoadableComponent.Commands(这并不必遵照namespace.class的规则,你也可以用其他的名称)。确定将AssemblyInfo.cs文件中的ComVisible编译属性改为true(默认值为false),否则不会有任何类在COM接口中暴露。以上的代码基本都相当简单,它仅包含一个命令用来确保在程序集加载时命令都被注册了。AddNumbers()函数用了一个略微不一样的方法来获得文档对象和数据库,因为我发现MdiActiveDocument在这时候会是null。我怀疑是时效的问题,如果AutoCAD有足够的时间来完成初始化,代码就不必这样写了。也许能有什么简单的方法来等待初始化的完成。编译好的程序需要通过COM注册,我趋向于通过Visual Studio命令提示行来完成。先将目录更改到程序集所在目录,然后运行regasm LoadableComponent.dll命令(你也可以通过/reg选项来创建一个.reg文件而不是直接修改注册表)。现在,我们能以COM方式引用AutoCAD Type Library,AutoCAD/ObjectDBX Common Type Library库来创建应用程序。当然,还需要引用刚创建的.NET程序集。在默认窗体上添加一个按钮,并添加之前提及的代码,适当的修改下逻辑,让程序加载我们创建的组件并且执行AddNumbers()函数。代码如下using Autodesk.AutoCAD.Interop; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Reflection; using System; using LoadableComponent; namespace DrivingAutoCAD public partial class Form1 : Form public Form1() InitializeComponent(); private void button1_Click(object sender, EventArgs e) const string progID = AutoCAD.Application.18; AcadApplication acApp = null; try acApp = (AcadApplication)Marshal.GetActiveObject(progID); catch try Type acType = Type.GetTypeFromProgID(progID); acApp = (AcadApplication)Activator.CreateInstance( acType, true ); catch MessageBox.Show( Cannot create object of type + progID + ); if (acApp != null) try / By the time this is reached AutoCAD is fully / functional and can be interacted with through code acApp.Visible = true; object app = acApp.GetInterfaceObject(LoadableComponent.Commands); if (app != null) / Lets generate the arguments to pass in: / an integer and a double object args = 5, 6.3 ; / Now lets call our method dynamically object res = app.GetType().InvokeMember( AddNumbers, BindingFlags.InvokeMethod, null, app, args ); acApp.ZoomAll(); MessageBox.Show( this, AddNumbers returned: + res.ToString() ); catch (Exception ex) MessageBox.Show( this, Problem executing component: + ex.Message ); 我试着用Application.GetInterfaceObject()的方法来加载这是通过VBA或VLisp加载VB6 ActiveX Dll的经典方法看看这一方法是否对于指定了ProgId的.Net程序是否也有效。貌似不起作用,不过模块中包含的命令都成功的注册了。一个不错的surprise!一开始,我本想在类库中定义了执行程序调用的接口,但是到最后却用了一个更灵活的方法:通过GetInterfaceObject()返回对象的InvokeMember()方法。这样不必去定义接口并暴露,但却增加了操作的一点点不确定性(我最近几周一直在说的:当我们得到灵活性时,也会失去我们早已依赖的编译器的帮助)。如果命令函数被声明成静态,也会碰到问题,不过我推测给InvokeMember()正确的参数可能会解决问题。当我们运行程序并点击按钮时,就会看到一个直径为11.3的圆被创建,两个数的和的结果也被返回。第一段就不翻了,反正就是有人指出代码有问题了。最主要的问题就是-事实上程序并不是在AutoCAD的主线程内执行的,这样,在与AutoCAD进行交互的过程中效率就被限制了。为了解决这一问题,我们可以通过System.EnterpriseServices.ServicedComponent(需要添加对System.EnterpriseServices的引用)来驱动我们的程序。下面是LoadableComponent更新后的C#代码。代码如下using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.Geometry; using System.Runtime.InteropServices; using System.EnterpriseServices; namespace LoadableComponent Guid(5B5B731C-B37A-4aa2-8E50-42192BD51B17) public interface INumberAddition DispId(1) string AddNumbers(int arg1, double arg2); ProgId(LoadableComponent.Commands), Guid(44D8782B-3F60-4cae-B14D-FA060E8A4D01), ClassInterface(ClassInterfaceType.None) public class Commands : ServicedComponent, INumberAddition / A simple test command, just to see that commands / are loaded properly from the assembly CommandMethod(MYCOMMAND) static public void MyCommand() Document doc = Application.DocumentManager.MdiActiveDocument; Editor ed = doc.Editor; ed.WriteMessage(nTest command executed.); / A function to add two numbers and create a / circle of that radius. It returns a string / withthe result of the addition, just to use / a different return type public string AddNumbers(int arg1, double arg2) / During tests it proved unreliable to rely / on DocumentManager.MdiActiveDocument / (which was null) so we will go from the / HostApplicationServices WorkingDatabase Document doc = Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; Editor ed = doc.Editor; ed.WriteMessage( nAdd numbers called with 0 and 1., arg1, arg2 ); / Perform our addition double res = arg1 + arg2; / Lock the document before we access it DocumentLock loc = doc.LockDocument(); using (loc) Transaction tr = db.TransactionManager.StartTransaction(); using (tr) / Create our circle Circle cir = new Circle( new Point3d(0, 0, 0), new Vector3d(0, 0, 1), res ); cir.SetDatabaseDefaults(db); / Add it to the current space BlockTableRecord btr = (BlockTableRecord)tr.GetObject( db.CurrentSpaceId, OpenMode.ForWrite ); btr.AppendEntity(cir); tr.AddNewlyCreatedDBObject(cir, true); / Commit the transaction tr.Commit(); / Return our string result return res.ToString(); 有几点需要指出的是:我们现在用一个接口来暴露组件中的功能,这样使得我们返回数据的方式更加灵活;我们用guidgen.exe生成的特定的GUIDs来标记接口和组件,虽然或许我们可以省略这步;现在我们可以安全地使用MdiActiveDocument属性,还有通过Editor输出消息;当生成组件并且通过regasm.exe来注册时,可以得到如下的注册内容(内容略)外部调用程序更新后的代码如下所示:代码如下using Autodesk.AutoCAD.Interop; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Reflection; using System; using LoadableComponent; namespac

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论