Kean的Blog

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

.NET和AutoCAD(2006.6.28)
AutoCAD2004中首先定义了.NET API的原型。

在那时,这是具有革命性的。

创建混合模式的DLL来暴露托管接口,并可通过非托管的ObjectARX调用来配置(marshal)这些接口(如何配置?)。

对开发者来说,.NET有许多有趣的东西。


用AutoCAD和.NET开始(2006.7.3)

用ObjectARX Wizard与VS一起工作(2006.7.5)

使用VS调试(2006.7.6)

在.NET程序中调用ObjectARX函数(2006.7.10)
.NET的一个引人注目的特点是调用非托管C++API的能力。

Autodesk认为它的合作开发者们在程序开发中已经投入了很多,它们不能把这些投入抛开而去支持最新最强大(有时甚至是“一月一更新”)的编程技术。

比如,多年来我们一直确信可以为已有的LISP程序创建一个VB或VBA的用户接口,或者为一个ObjectARX 程序创建一个.NET用户接口。

有时,我们可以暴露我们的互操作函数来实现这一点(比如LISP函数可调用ActiveX DLL)。

那么如何从中调用一个ObjectARX函数呢?答案就是Platform Invoke(或者简写为P/Invoke)。

微软并未通过.NET框架暴露所有Win32 API的功能,就像Autodesk并未通过AutoCAD托管API暴露所有的ObjectARX功能一样,但P/Invoke能帮助我们进行弥补。

首先,要了解一下ObjectARX的一些背景和P/Invoke能够帮助我们做些什么工作。

ObjectARX是从DLL和EXE中输出的一系列API。

大多数被输出的函数在汇编的时候
会被“修饰”或“处理”,除非有特定的编译指令来指示函数不会被处理(比如,所有旧式的ADS函数就是这样的情况,他们被声明为extern C的方式,因此不会被处理)。

编译器会给函数分配一个唯一的基于函数签名的名字,这样做是有意义的:在C++中,两个有相同名字的函数是合法的,只要他们的参数或返回值不一样。

这些被修饰的名字包含完整的函数名,这就是以下所要讲的技术——用来查询正确的输出函数——起作用的原因。

注意:这种技术对于C语言风格的函数或C++静态函数运行得很好,但它不适用于实例成员函数,因为不可能在托管代码定义的类中实例化一个非托管类的对象。

如果你需要为托管代码暴露一个类的实例方法,你需要编写并暴露一些本地的C++代码来实例化这个类、调用方法并返回结果。

我们从C#和的角度来示范调用acedGetUserFavoritesDir()的过程。

此函数在ObjectARX头文件中声明如下:
extern Adesk::Boolean acedGetUserFavoritesDir(ACHAR* szFavoritesDir);
根据ObjectARX参考文档,“此函数提供对当前用户的Windows收藏夹这个文件夹的访问”。

第一步,识别输出函数的位置。

下面的批处理文件能实现这一目的。

@echo off
if "%1" == "" goto usage
:normal
for %%i IN (*.exe *.dll *.arx *.dbx *.ocx *.ddf) DO dumpbin /exports %%i | findstr "%%i %1" goto end
:usage
echo findapi "function name"
:end
把上面的代码拷贝并粘贴到一个名为“findapi.bat”的文件中,然后把这个文件放到AutoCAD的程序文件夹中,然后需要从命令行中运行findapi。

命令行是在安装Visual Studio 的时候创建的VS命令行,它会帮助我们找到dumpbin.exe。

当然你也可以把输出重定位到一个文本文件,比如:
C:\Program Files\AutoCAD 2007>findapi acedGetUserFavoritesDir > results.txt
因为此批处理文件要查找AutoCAD程序文件夹中所有的DLL、EXE等文件来找到结果,所以需要一些时间。

打开这个文本文件你就会看到acedGetUserFavoritesDir函数在哪里输出:
[ from the results for AutoCAD 2007 ]
Dump of file acad.exe
436 1B0 004B4DC0 ?acedGetUserFavoritesDir@@YAHPA_W@Z
警告1:这个被修饰的函数名字用来接受或返回的字符串,对于AutoCAD2006和AutoCAD2007是不同的,因为AutoCAD2007使用Unicode来定义字符串。

下面是2004/2005/2006这些以前版本中的声明:
[ from the results for AutoCAD 2006 ]
Dump of file acad.exe
357 161 00335140 ?acedGetUserFavoritesDir@@Y AHPAD@Z
这很容易理解,因为函数签名中接受的参数类型从char*变成了ACHAR*(AutoCAD 2007中,一个用来解决宽字符串或Unicode字符串的数据类型)。

函数签名的改变导致了修饰名字的改变。

这个够简单了,但值得注意的一个潜在移植问题是:如果在一个发布版本中普遍使用这种签名变化的话,对这种被修饰的函数名的依赖会引起大量的移植工作。

警告2:你会发现有些从各种DLL/EXE文件中导出的函数在ObjectARX头文件中并没有相应的声明。

这些函数尽管能导出,但并不会被支持。

这意味着你或许可以知道它们怎样被调用,但使用他们却存在风险。

不支持的API很有可能在不知晓的情况下就改变或消失。

现在,我们已经知道怎样在哪里导出函数,这样我们就能创建一个函数的声明,以便在我们的代码中使用这个函数。

第二步:在代码中正确地声明函数。

这与编程语言相关,不同的语言可能稍微有些不同。

VB程序员习惯使用“Declare”来在他们的工程中创建P/Invoke。

而在C#中则是使用DllImport。

这些声明应该是类级别的(而不是在一个单独的函数定义中)。

Private Declare Auto Function acedGetUserFavoritesDir Lib "acad.exe" Alias "?acedGetUserFavoritesDir@@Y AHPA_W@Z" (<MarshalAs(UnmanagedType.LPWStr)> ByVal sDir As StringBuilder) As Boolean
C#
[DllImport("acad.exe", EntryPoint = "?acedGetUserFavoritesDir@@Y AHPA_W@Z", CharSet = CharSet.Auto)]
public static extern bool acedGetUserFavoritesDir([MarshalAs(UnmanagedType.LPWStr)] StringBuilder sDir);
注意:1、指定字符集是“Auto”,这不是默认的设置。

编译器会决定是使用Unicode还是ANSI,因此请放心使用。

2、在2007中,需要使用MarshalAs(UnmanagedType.LPWStr)来声明Unicode字符变量,不论使用String还是StringBuilder都是如此。

3、StringBuilder用于输出字符参数,因为标准的String被认为是不变的。

String则适合输入参数。

第三步:在代码中使用函数。

Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor
Dim sDir As New StringBuilder(256)
Dim bRet As Boolean = acedGetUserFavoritesDir(sDir)
If bRet And sDir.Length > 0 Then
ed.WriteMessage("Your favorites folder is: " + sDir.ToString)
End If
C#
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
StringBuilder sDir = new StringBuilder(256);
bool bRet = acedGetUserFavoritesDir(sDir);
if (bRet && sDir.Length > 0)
ed.WriteMessage("Your favorites folder is: " + sDir.ToString());
注意:把StringBuilder变量(sDir)声明为256个字符长度。

AutoCAD期望我们提供足够长的缓冲区来拷贝数据。

在我的系统上这两段代码都会在AutoCAD的命令行上产生如下的结果:
Your favorites folder is: C:\My Documents\Favorites
这样,你就应该能从.NET中调用ObjectARX全局函数了。

这种技术也可以被用于调用你自己的来自于Dll中暴露的函数。

这是允许你使用.NET和已有的C++代码创建很好的UI
的一个方法。

网站/中有很好的关于P/Invoke的资料。

用现代的GUI与LISP程序交互(2006.7.31)

使用.NET从外部DWG文件中导入块(2006.8.18)
我们要使用一个辅助数据库“side database”――一个被载入内存,但不在AutoCAD编辑器的显示的图纸,使用这个辅助数据库来从其它的图纸中导入块到编辑器中的活动图纸。

下面是关于此的C#代码,其中的注释描述了每个步骤。

顺便说一句,下面的代码很容易被转换成一个RealDWG程序,这个程序工作于AutoCAD外部(只需要简单地把destDb 从MdiActiveDocument的数据库改变成HostApplicationService的WorkingDatabase,并使用不同的用户接口来为用户得到和表现字符串(?))。

using Autodesk.AutoCAD;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using System.Collections.Generic;
namespace BlockImport
{
public class BlockImportClass
{
[CommandMethod("IB")]
public void ImportBlocks()
{
DocumentCollection dm = Application.DocumentManager;
Editor ed = dm.MdiActiveDocument.Editor;
Database destDb = dm.MdiActiveDocument.Database;
Database sourceDb = new Database(false, true);
PromptResult sourceFileName;
try
{
//从命令行要求用户输入以得到要导入的块所在的源DWG文件的名字
sourceFileName = ed.GetString("\nEnter the name of the source drawing: ");
//把源DWG读入辅助数据库
sourceDb.ReadDwgFile(sourceFileName.StringResult,
System.IO.FileShare.Read, true, "");
//用集合变量来存储块ID的列表
ObjectIdCollection blockIds = new ObjectIdCollection();
Autodesk.AutoCAD.DatabaseServices.TransactionManager tm =
sourceDb.TransactionManager;
using (Transaction myT = tm.StartTransaction())
{
//打开块表
BlockTable bt = (BlockTable)tm.GetObject(sourceDb.BlockTableId,
OpenMode.ForRead, false);
//在块表中检查每个块
foreach (ObjectId btrId in bt)
{
BlockTableRecord btr = (BlockTableRecord)tm.GetObject(btrId,
OpenMode.ForRead, false);
//只添加有名块和非layout块(layout块是非MS和非PS的块)
if (!btr.IsAnonymous && !btr.IsLayout)
blockIds.Add(btrId);
btr.Dispose(); //释放块表记录引用变量所占用的资源
}
bt.Dispose();//释放块表引用变量所占用的资源
//没有作改变,不需要提交事务
myT.Dispose();
}
//用WblockCloneObjects把所有的块从源库拷贝块到目的库的块表中
//这只能实现导入块到指定的数据库中,但不是深度克隆,若对块参照
//实行深度克隆的话,其所引用的块也会被克隆到指定的数据库中
IdMapping mapping = new IdMapping();
sourceDb.WblockCloneObjects(blockIds, destDb.BlockTableId,
mapping, DuplicateRecordCloning.Replace, false);
ed.WriteMessage("\nCopied " + blockIds.Count.ToString()
+ " block definitions from " + sourceFileName.StringResult
+ " to the current drawing.");
}
catch(Autodesk.AutoCAD.Runtime.Exception ex)
{
ed.WriteMessage("\nError during copy: " + ex.Message);
}
sourceDb.Dispose();
}
}
}
庖丁解牛——近距离观察导入块的C#代码(2006.8.21)
在上一个主题中我没能花太多时间去讨论代码(周五我贴出它的时候夜已经很深了)。

下面对一些重要函数进行分析。

代码中我们进行的第一个重要任务是声明和初始化一个新数据库对象。

这个对象就是只在内存中的图形(side database)。

我们可以存取这个图形中的信息,但是它不会在AutoCAD 的编辑器中出现。

Database sourceDb = new Database(false, true);
注意,第一个参数(buildDefaultDrawing)值为false。

只有在两种情况下,这个值才需设置为true。

如果你错误地在不需要的情况下传给它true值,函数将返回且不会报错,但是DWG 文件几乎肯定不能正常加载。

这种情况时常发生,所以你真的需要关注这个复杂而微妙的问题。

下面是需要给buildDefaultDrawing参数赋true值的两种情况:
1、你自己创建一个DWG文件,而不是从其它地方读取,被创建的DWG中有最基本的数据库结构。

2、读取R12及更早版本的AutoCAD图纸文件。

虽然例子中并不涉及这项技术,但如果你打算在程序的辅助数据库中读入R13之前版本的DWG文件,那么你需要在程序中检查DWG文件的版本并为数据库的构造函数传入相对应的buildDefaultDrawing参数。

这里你可能要问了:“那如何在读入DWG文件前检查它的版本?”很幸运,DWG文件的前六个字节标识了它的版本号(你可以将DWG用记事本打开来查看它的原始字符):
AC1.50 = R2.05
AC1002 = R2.6
AC1004 = R9
AC1006 = R10
AC1009 = R11/R12
AC1012 = R13
AC1014 = R14
AC1015 = 2000/2000i/2002
AC1018 = 2004/2005/2006
AC1021 = 2007
你可以在你选择的编程环境中使用文件存取方法来读取文件的前6个字节,如果是AC1009或者小于它,第一个参数就传入true值,否则是false。

接下来是要求用户输入DWG文件的路径和文件名:
sourceFileName = ed.GetString("\nEnter the name of the source drawing: ");
这里没有什么特别有趣的东西,事实上我没有检查输入的文件路径是否存在,甚至没有检查用户是不是输入其它乱七八糟的东西。

这样做的理由很简单:如果文件不存在,接下来的函数ReadDwgFile将会抛出异常,try-catch块会捕获这个异常并报告给用户。

当然我们也可以自己进行错误检查处理,并输出比"Error during copy: eFileNotFound"更花哨好看的消息,但坦白地说,这些不过是些花活而已,没必要。

try-catch已经能足够好地捕获和处理异常。

sourceDb.ReadDwgFile(sourceFileName.StringResult, System.IO.FileShare.Read, true, ""); 这就是读取图形到辅助数据库的函数。

我们把GetString()函数的返回值赋给第一个参数filename。

第二个参数表明我们只是读取而不改写这个文件(文件锁定是一种可以让其它程
序和我们同时读取同一文件但不改写它的简单方法)。

第三个参数表明我们希望AutoCAD 尝试使用code-page(一种和本地化字符相关的Pre-Unicode概念)对不同OS上的DWG进行默认转化。

最后一个参数代表一个空密码(我们假设图形文件没有密码保护或者密码已经输入到密码缓存段中)。

然后我们初始化一个集合对象,用来存储打算从辅助数据库拷贝到当前图形的所有块的id。

ObjectIdCollection blockIds = new ObjectIdCollection();
我们接着创建一个事务处理从而让我们得以进入到DWG的引人入胜之处(这是使用.NET存取DWG内容时的推荐方案)。

使用事务处理我们打开辅助数据库的块表进行读取。

注意我们只希望读取没有被删除的块:
BlockTable bt = (BlockTable)tm.GetObject(sourceDb.BlockTableId,
OpenMode.ForRead, false);
在这里我们只需要简单使用一个标准的foreach循环就可以检查每个块定义(即AcDb中的块表纪录)——这是使用AutoCAD托管API的美妙处之一。

foreach (ObjectId btrId in bt)
{
BlockTableRecord btr = (BlockTableRecord)tm.GetObject(btrId, OpenMode.ForRead, false);
if (!btr.IsAnonymous && !btr.IsLayout)
blockIds.Add(btrId);
btr.Dispose();
}
上面的代码只是简单地打开每个块并将非匿名布局块(模型空间和图纸空间也作为块定义储存在DWG中,我们不想复制它们)的ID加入到复制列表中。

使用完每个块之后调用dispose函数,这是一个好的习惯。

最后是完成实际复制工作的函数:
sourceDb.WblockCloneObjects(blockIds, destDb.BlockTableId,
mapping, DuplicateRecordCloning.Replace, false); WblockCloneObjects()函数可以读入一个对象列表(blockIds)并将它们从数据库中克隆到目标数据库,其中第二个参数指定目的库中所有者,在使用这个函数的时候要注意,第一个参数与第二个参数的关系,第二个参数必须是在目的库中成为第一个参数的所有者(如,第一参为块ID,则第二参为块表ID,而不能是第一参为实体ID,第二参为块表ID)。

我们设定复制对象所有者为目标数据库的块表(destDb.BlockTableId)。

如果块已经在目标数据库中存在,将被覆盖(DuplicateRecordCloning.Replace参数)。

你也可以设定如果已经存在块则拷贝不会发生(DuplicateRecordCloning.Ignore参数)
支持多个AutoCAD版本――C#的条件编译(2006.8.22)
为不同版本的AutoCAD开发,有一些要做的变化。

首先,要确定是否有正确版本的acmgd.dll和acdbmgd.dll在工程中被引用(通过AutoCAD 的安装路径就可分辨)。

然后,需要稍微更改一下代码,比如WblockCloneObjects()在从2006到2007的过程中就已经改变了它的签名。

下面的代码段展示了如何使用预处理指令来实现C#中的条件编译。

需要在工程中设置AC2006或AC2007为“条件编译符号”,然后在代码中使用:
#if AC2006
mapping = sourceDb.WblockCloneObjects(blockIds, destDb.BlockTableId,
DuplicateRecordCloning.Replace, false);
#elif AC2007
sourceDb.WblockCloneObjects(blockIds, destDb.BlockTableId, mapping,
DuplicateRecordCloning.Replace, false);
#endif
可能要在上面使用#else而不是#elif,这会使代码更具有前瞻性:它会自动地支持新版本,而不需特别地添加代码来检查。

实际上可以指定整个方法只被编译成某个版本。

首先,需要导入System.Diagnostics命名空间:using System.Diagnostics;然后只需使用Conditional这个属性来为指定版本的AutoCAD声明一个特别的方法(下面的情况是一个命令的方法)。

你可通过为DEBUG预编译符号添加Conditional属性,就可指定一个只Debug的命令,如下:
namespace BlockImport
{
public class BlockImportClass
{
[Conditional("AC2007"),CommandMethod("IB")]
public void ImportBlock()
{
...
从.NET中调用AutoCAD命令(2006.8.30)
下面的方法可从.NET程序中发送命令到AutoCAD:
1、托管文档对象中的SendStringToExecute;
2、COM文档对象中的SendCommand;
3、通过P/Invoke调用acedPostCommand;
4、通过P/Invoke调用ads_queueexpr()。

下面是一些代码,需要添加对AutoCAD2007类型库的COM引用,还有标准的.NET引用,acmgd.dll和acdbmgd.dll。

Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.Interop
Public Class SendCommandTest
Private Declare Auto Function ads_queueexpr Lib "acad.exe" _
(ByVal strExpr As String) As Integer
Private Declare Auto Function acedPostCommand Lib "acad.exe" _
Alias "?acedPostCommand@@Y AHPB_W@Z" _
(ByVal strExpr As String) As Integer
<CommandMethod("TEST1")> _
Public Sub SendStringToExecuteTest()
Dim doc As Autodesk.AutoCAD.ApplicationServices.Document
doc = Application.DocumentManager.MdiActiveDocument
doc.SendStringToExecute("_POINT 1,1,0 ", False, False, True)
End Sub
<CommandMethod("TEST2")> _
Public Sub SendCommandTest()
Dim app As AcadApplication = Application.AcadApplication
app.ActiveDocument.SendCommand("_POINT 2,2,0 ")
End Sub
<CommandMethod("TEST3")> _
Public Sub PostCommandTest()
acedPostCommand("_POINT 3,3,0 ")
End Sub
<CommandMethod("TEST4")> _
Public Sub QueueExprTest()
ads_queueexpr("(command""_POINT"" ""4,4,0"")")
End Sub
End Class
在使用C#的情况下,下面是使用acedPostCommand()和ads_queueexpr()的声明:[DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] extern static private int ads_queueexpr(string strExpr);
[DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedPostCommand@@Y AHPB_W@Z")]
extern static private int acedPostCommand(string strExpr);
需要指定using System.Runtime.InteropServices;来使DllImport能够工作。

为.NET定义的命令传递参数(2006.9.1)
通常都是用命令来要求用户在执行命令的时候提供额外输入。

这些输入的信息可通过脚本或来自于LISP的命令,或者就是简单地用户手工键入或粘贴。

尽管如此,这些命令实际上都不带参数的。

可能好像他们带了参数,但他们没有。

他们所做的是要求用户使用对话框或命令行。

下面的要点是关于如何支持在命令中传递信息。

为你的要求通过命令行输入的命令定义一个版本
在.NET程序中很容易定义漂亮的UI。

但提供一个能通过命令行和脚本调用的代用品也是有用的。

我对你定义的命令提供下面两个建议:
1、定义一个标准版本(如,MYCOMMAND)和一个命令行版本(如,-MYCOMMAND)。

通常要定义命令的命令行版本要使用一个连字符。

2、一个替代品是检查FILEDIA和CMDDIA系统变量的值,可看CAD的关于这些命令的文档来理解他们对命令意味着什么。

当实现一个命令行版本的命令时,只要简单地使用Autodesk.AutoCAD.EditorInput命名空间的标准用户函数(GetXXX ())。

下面是一些的代码。

<CommandMethod("TST")> _
Public Sub Test()
Dim ed As Editor
ed = Application.DocumentManager.MdiActiveDocument.Editor
Dim name As PromptResult = ed.GetString(vbCr + "Enter name: ")
ed.WriteMessage("You entered: " + name.StringResult)
End Sub
当代码运行时,得到下面的结果(红色部分是键入的文本)
Command: tst
Enter name: Hello
You entered: Hello
Command:
顺便说一下,我在使用GetXXX函数时,放了个vbCr(或都在ObjectARX中是“\n”)在提示的前面,因为它可能在AutoCAD中终止一个文本并输入一个空格:这意味着输入一个空格会发送命令。

为你的命令定义一个LISP版本
<LispFunction>属性允许你声明可被LISP调用的.NET函数。

.NET中的自定义对象(2006.9.4)
ADN(Autodesk开发人员网络)提供给其成员的一个服务是表现其成员对Autodesk项目团队的期望,帮助影响产品的方向。

比如,每年我们都要开展一个对一系列我们产品(如,AutoCAD,ADT,Revit和Map 3D) 的API期望列表的调查。

对每种产品的在线调查由ADN 的成员填写,允许他们提供对直接、合适的产品管理部门接触的有目标的反馈。

我要说的是在最近的AutoCAD API调查中有一个很明显的特征――.NET中自定义对象的暴露。

我要特别提出这个问题的原因是在期望列表上对这个问题提到了很多了,并且我不希望我们还不能解决它。

因此,今天这个帖子部分是为了解释这个问题。

不幸的是,我不能说明怎样和什么时候这个问题会被解决,但我能保证的是你提供的反馈会被汇报并带给AutoCAD项目组来帮助影响他们的方向。

自定义对象在Release13中被引入。

其机制是ARX(即AutoCAD运行时扩展,后来发展成ObjectARX)的核心,此机制允许Autodesk和外部的开发人员实现在AutoCAD中的一些新的功能而不用修改核心的产品。

这在当时是具有革命性的,它允许开发人员可以深度访问AutoCAD,可以形成与AutoCAD平台的紧密整合。

Autodesk中基于AutoCAD的程序(如ADT,Map3D等)和其它复杂的第三方产品都是基于自定义对象的。

尽管此机制被证明是强大的,但也有弱势:
1、实现一个完全完善的自定义实体所要求的努力是很高的。

许多不同的虚函数需要实现并且实体需要在不同的场景上测试――open, save, wblock, undo, explode, move, copy, scale, object snaps, grip stretching, trim, extend, refedit, audit等等。

2、在一些其它的系统中需要Object Enabler或.DBX模块,自定义对象才能起作用。

3、自定义对象是DWG中存储的自定义数据和.DBX模块定义的自定义行为的混合体,现在
在ObjectARX中没有办法只要其中之一。

许多开发者实现自定义对象只是需要这些对象所有功能的一个子集;有些只需要自定义的图形或行为,他们不需要存储大量的自定义二进制数据。

在技术上,暴露ObjectARX的当前自定义对象机制到托管环境中是相对简单的。

可是,在根本上,并没有对当前的机制作更多的改进:从开发者的角度,好处是使用或C#来实现各种各样的回调函数需要定义数据存储到DWF和DXF格式中,需要定义对象在AutoCAD中的行为。

尽管允许开发者更多地巩固它们在单个.NET语言下的开发,但它不会减少需要实现的回调函数的数量,也不会使对象更容易开发。

C++是一种较复杂的编程语言:要求它来暴露自定义对象,这大量减少了这项技术的拥护者。

既然那可能是,也可能不是一个坏东西,这就看你的观点了:这个体系的复杂性使它很难“大众化”,因此,有这个C++的困难在这,自然就限制了强烈推荐的人们。

因此,做什么是对的呢?

AutoCAD .NET程序的初始化代码(2006.9.6)
通常,在载入程序模块时,需要执行一些代码,在卸载程序或AutoCAD终止时,需要做一些清理工作。

托管AutoCAD程序通过Autodesk.AutoCAD.Runtime.IExtensionApplication 接口的实现,来实现程序的初始化和清理工作,这需要Initialize()和Terminate()方法。

在Initialize()方法中,典型地,要设置系统变量和可能调用命令来执行一些预先存在的程序的初始化代码。

下面一些代码显示了使用C#.NET如何实现这个接口:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using System;
public class InitializationTest : Autodesk.AutoCAD.Runtime.IExtensionApplication
{
public void Initialize()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("Initializing - do something useful.");
}
public void Terminate()//此方法被调用的时候CAD已经在关闭进程了
{
Console.WriteLine("Cleaning up...");
}
[CommandMethod("TST")]
public void Test()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("This is the TST command.");
}
}
下面分析这段代码:
.NET程序只有在AutoCAD终止时才会被卸载。

尽管这是个受开发者欢迎的要求(因为这会使调试更加容易),但我的理解是这是一个.NET实现的一个固有的问题。

这意味着在Terminate()方法被调用时,AutoCAD已经在关闭进程了。

就是是为什么我在上面的Terminate()中使用Console.Write()而不是ed.WriteMessage(),因为现在在AutoCAD中没有命令行来写了。

这也说明,能也应该使用Terminate()来关闭任何打开的文件,数据库连接等。

在实现这个的时候,可能会遇到一些其它的问题…。

在上面的例子中,我有两个原因实现单个的命令:第一个是下个帖子中我会把这个命令隔离到一个不同的类中来展示如何使你的程序结构更有逻辑性,有更好的载入性能。

第二个原因是我要提出一个微妙的在编码时会遇到的问题:你可能看到上面的那个初始化的字符串在程序载入时被送到命令行,但随后当在命令行中输入“TST”时,这个命令却找不到了。

如果你经历过这样的行为,你可能遇到了一个在对AutoCAD2007进行托管程序编码时出现的问题,就是:在这个版本中有个变化以便于acmgd.dll被装载;并且在某种条件下,如果这个程序集被发现有一个其它的路径,它可能中止装载,这导致你的命令不会起作用。

这个问题能机警地辨别,但有下面的一些解决方法:
1、编辑注册表来不允许acmgd.dll的“load on startup”要求载入。

(这是个坏方法,…)
2、确保AutoCAD是从它自己的工作路径启动的――通常在调试的时候会遇到这个问题,因为VS不会自动地得到调试程序的工作路径。

3、在工程中设置acmgd.dll的“Copy Local”属性标志为False。

此属性告诉VS是否在建的线程是否应该复制这个工程引用的程序集到它的输出文件夹中。

我的偏好是第三种,因为在很多的场合中,我曾用来自于其它版本的AutoCAD的acmgd.dll和acdbmgd.dll覆盖了这两个文件(在安装AutoCAD时不注意)。

这也在跨版本间测试工程时候经常发生(我曾为了方便设置直接设置输出为AutoCAD的程序文件夹),并且我在构建程序的时候忘记改变这个程序集的引用了。

因此,实际上我会设置这两个引用的“Copy Local=False”。

优化AutoCAD .NET程序的载入(2006.9.8)
在前面的帖子中,我描述了如何使用Autodesk.AutoCAD.Runtime.IExtensionApplication 接口来实现.NET模块的初始化。

在此基础上,现在我们要看看如何使用这个接口来优化托管模块在AutoCAD中更快的载入。

首先是来自于SDK中的“Using .NET for AutoCAD documention”:当AutoCAD载入托管程序时,它会询问程序的程序集的ExtensionApplication自定义属性。

若找到此属性,AutoCAD设置与此属性相关的类型为程序的入口点。

若没有找到此属性,AutoCAD就寻找所有输出类型的IExtensionApplication实现。

若没有此实现,AutoCAD就简单地跳过程序指定的初始化步骤。

…除了寻找IExtensionApplication实现,AutoCAD还询问程序的程序集中一个或多个CommandClass属性。

若找到此属性的实例,AutoCAD就只寻找他们相关类型的命令方法,否则,它会寻找所有的输出类型。

在这个博客中我前面的例子都没有展示在代码中如何使用ExtensionApplication或CommandClass属性,因为这对程序的运行并不是重要的。

但如果有大型的.NET模块在AutoCAD中载入,AutoCAD需要时间来检查程序集中各种各样的对象来找到哪个是
ExtensionApplication和哪些是各种各样的CommandClass。

需要实现的属性很简单,如下:
C#:
[assembly: ExtensionApplication(typeof(InitClass))]
[assembly: CommandClass(typeof(CmdClass))]
:
<Assembly: ExtensionApplication(GetType(InitClass))>
<Assembly: CommandClass(GetType(CmdClass))>
这些是程序级的属性,简单地告诉我AutoCAD哪里去寻找各种需要辨识的对象。

文档中对这些属性作了说明:ExtensionApplication属性只能被一种类型关联。

它关联的这个类型必须实现IExtensionApplication接口。

…可为任意类型声明CommandClass属性,只要它定义了AutoCAD命令处理函数。

若程序使用CommandClass属性,它必须为每个包含AutoCAD 命令处理方法的类型声明这个属性的一个实例。

下面把昨天的代码来优化以减少载入时间,我轻微地改变了程序的结构,以使它更有逻辑。

上面的属性在一个命名空间内使用类,因此,我决定把代码分为命令实现(Commands 类)和初始化代码(Initialization类),但还是让他们都在同一个名字空间(ManagedApplication)中。

如下:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using System;
[assembly: ExtensionApplication(typeof(ManagedApplication.Initialization))]
[assembly: CommandClass(typeof(mands))]
namespace ManagedApplication
{
public class Initialization : Autodesk.AutoCAD.Runtime.IExtensionApplication
{
public void Initialize()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("Initializing - do something useful.");
}
public void Terminate()
{
Console.WriteLine("Cleaning up...");
}
}
public class Commands
{
[CommandMethod("TST")]
public void Test()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("This is the TST command.");。

相关文档
最新文档