• 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
    ";
    //此处要把字符串使用Unicode编码转换为byte[],然后再传入委托接口,不然到CAD之后会乱码
    byte[] bytes = Encoding.Unicode.GetBytes(str); int i = pc.Invoke(bytes); } } }
  • 相关阅读:
    [每日一讲] Python系列:浅拷贝与深拷贝
    [每日一讲] Python系列:变量、内存管理与传递
    [每日一讲] Python系列:字典
    [每日一讲] Python系列:列表与元组
    [已开源/文章教程]独立开发 一个社交 APP 的源码/架构分享 (已上架)
    从选择到上传,可能是最贴心的高仿朋友圈编辑了
    完整的社交app源码android+laravel
    基于Laravel+Swoole开发智能家居后端
    APP架子迁移指南(三)
    Laravel如何优雅的使用Swoole
  • 原文地址:https://www.cnblogs.com/bomb12138/p/5913983.html
Copyright © 2020-2023  润新知