使用C#进行AutoCAD二次开发,有时候由于C#接口不够完善,或者低版本AutoCAD中的接口缺少,有些工作不能直接通过C#接口来实现,所以需要通过P/Invoke的方式调用AutoCAD的其他DLL中的接口来实现。
最常见的是向AutoCAD发送同步命令,在高版本的AutoCAD.NET接口中好像是可以发送同步命令了(据说是从2014或2015开始是可以了,不过我没有进行验证),但在低版本AutoCAD.NET中是没有发送同步命令接口的,SendStringToExecute和COM接口中的SendCommand都是异步操作,只有通过acedCmd、acedCommand、acedPostCommand才可能发送同步命令,这三个接口在AutoCAD2013之前是在acad.exe中的,而从AutoCAD2013开始放到了accore.dll中,并且acedPostCommand这个接口是官方没有公布但实际是可以使用的。
正常通过P/Invoke方式调用需要进行以下声明:
AutoCAD2013以前的版本
[System.Security.SuppressUnmanagedCodeSecurity] [DllImport("acad.exe", EntryPoint = "acedCmd", CallingConvention = CallingConvention.Cdecl)] private static extern int acedCmd(IntPtr vlist);
[DllImport("acad.exe", EntryPoint = "acedCommand")] private static extern int acedCommand(IntPtr vlist);
//CAD2008和2008以前版本中的EntryPoint名称和2009以及2009之后的版本不一样 [DllImport("acad.exe", CharSet = CharSet.Auto, EntryPoint = "?acedPostCommand@@YAHPB_W@Z", CallingConvention = CallingConvention.Cdecl)] private static extern int acedPostCommand(string strExpr);
//CAD2008之后的版本中 [DllImport("acad.exe", CharSet = CharSet.Auto, EntryPoint = "?acedPostCommand@@YAHPEB_W@Z", CallingConvention = CallingConvention.Cdecl)] private static extern int acedPostCommand(string strExpr);
AutoCAD2013及更新版本
[System.Security.SuppressUnmanagedCodeSecurity] [DllImport("accore.dll", EntryPoint = "acedCmd", CallingConvention = CallingConvention.Cdecl)] private static extern int acedCmd(IntPtr vlist); [DllImport("accore.dll", EntryPoint = "acedCommand")] private static extern int acedCommand(IntPtr vlist); //CAD2008和2008以前版本中的EntryPoint名称和2009以及2009之后的版本不一样 [DllImport("accore.dll", CharSet = CharSet.Auto, EntryPoint = "?acedPostCommand@@YAHPB_W@Z", CallingConvention = CallingConvention.Cdecl)] private static extern int acedPostCommand(string strExpr); //CAD2008之后的版本中 [DllImport("accore.dll", CharSet = CharSet.Auto, EntryPoint = "?acedPostCommand@@YAHPEB_W@Z", CallingConvention = CallingConvention.Cdecl)] private static extern int acedPostCommand(string strExpr);
使用时直接进行调用即可:
ResultBuffer rb = new ResultBuffer(); rb.Add(new TypedValue((int)LispDataType.Text, "_.mycmd")); int i = acedCmd(rb.UnmanagedObject);
要注意的是这些接口不能使用Loadlibrary的方式进行调用,因为Loadlibrary会把指定的DLL或EXE进行加载,加载后应该和当前程序所寄存的AutoCAD主进程不是一回事了,调用会有问题或调用结果达不到预期效果。
今天要介绍的是另外一种不使用P/Invoke方式就可以在自己的插件中调用acad.exe或accore.dll中接口的方法,主要思路如下:
1、获取当前进程Process,因为插件是被加载到AutoCAD中运行的,所以获取到的是AutoCAD进程。
2、获取当前进程加载的所有模块ProcessModuleCollection。
3、遍历ProcessModuleCollection找到自己要调用的接口所在的模块ProcessModule。
4、声明一个和要调用的接口格式相同的委托。
5、获取ProcessModule的EntryPointAddress,然后使用Marshal.GetDelegateForFunctionPointer方法根据函数句柄得到一个委托实例。
6、调用委托实例进行执行即可。
以acedPostCommand为例完整代码如下:
//此处声明的委托和acedPostCommand(string strExpr)不相同是因为经过测试,如果参数声明为string类型,调用的时候传送到CAD的命令是乱码
public delegate int DelegateAcedPostCommand(byte[] cmd); static DelegateAcedPostCommand pc; public void ShowMsg() { //不同版本接口所在的程序模块不同 string mdName = Application.Version.Major >= 19 ? "accore.dll" : "acad.exe"; //CAD2007和2008中接口入口点名称不一样,2007以前的没有看,想看的可以用depends工具查看 string apiName = (Application.Version.Major >= 17 && Application.Version.Minor <= 1) ? "?acedPostCommand@@YAHPB_W@Z" : "?acedPostCommand@@YAHPEB_W@Z"; //获取当前CAD进程 Process pro = Process.GetCurrentProcess(); //获取CAD加载的所有程序模块(引用了什么DLL、EXE) ProcessModuleCollection pmc = pro.Modules; IntPtr iptr = IntPtr.Zero; for (int i = 0; i < pmc.Count; i++) { ProcessModule pm = pmc[i]; if (pm.ModuleName == mdName) { iptr = pm.EntryPointAddress; break; } } if (iptr != IntPtr.Zero) { pc = (DelegateAcedPostCommand)Marshal.GetDelegateForFunctionPointer(iptr, typeof(DelegateAcedPostCommand)); if (pc != null) { string str = "_.remmenu\n";
//此处要把字符串使用Unicode编码转换为byte[],然后再传入委托接口,不然到CAD之后会乱码 byte[] bytes = Encoding.Unicode.GetBytes(str); int i = pc.Invoke(bytes); } } }