AutoCAD.NET 不使用P/Invoke方式调用acad.exe或accore.dll中的接口(如acedCommand、acedPostCommand等)

使用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); } } }

 

posted @ 2016-09-27 19:24  尼克劳斯  阅读(4177)  评论(5编辑  收藏  举报