用C#做CATIA二次开发(1)

CATIA默认的二次开发语言是CATScript,语法和VBA很像,是纯文本格式,用通用的文本编辑器就可以处理,不像VBA必须要专用编辑器才能打开。这种开发语言还可以在Unix下运行。没错,CATIA有Unix版本,只不过国内很少见。

理论上,C语言可以连通所有的计算机程序,COM也可以支持,但是C/C++需要额外的头文件.h、导出库.lib.a,这些都不在CATIA默认安装中,需要通过额外的二次开发包CAA RADE来支持。就是说开发者除了CATIA软件本体以外,还需要额外安装RADE,实际上很多高级的二次开发都是基于RADE的,而且很多都已经做到了商业化。但是RADE这个软件包市面上很少有流出。在没有RADE支持的情况下使用C/C++做CATIA二次开发就受到了严重的限制,理论上可以用dllexport直接读取CATIA的二进制文件,然后根据导出的符号来推断其函数名和参数,但是CATIA有大量的二进制文件,这工作量不仅超级大,而且读出来的很可能不是那种适合二次开发的接口,很可能是那种很难使用的内部接口。

在Windows平台,CATIA还支持VBA进行二次开发。CATIA的VBA支持是通过COM实现的,也就是说只要编程语言支持COM,其实都可以做CATIA二次开发,而C#也支持COM,所以C#也支持CATIA的二次开发。VBA只能用自带的VBA编辑器,语法高亮、自动提示等功能都没有,而且有些底层的VB函数可能支持的还不好。C#可以使用Visual Studio,语法高亮、自动提示、点击跳转......多种IDE支持都很好,而且其语法也比VBA好得多,标准库功能也更完善。VBA被逆向可是超级简单的,毕竟只有一个密码保护;虽然C#也很容易被逆向,不过至少还是二进制的,还有代码混淆等保护手段。

今天初步尝试了使用C#进行CATIA二次开发,记录下来以供将来回顾。

.net Framework的方法

入门第一步就是让C#能够连接到正在运行的CATIA程序。

在Visual Studio创建一个基于.net framework的程序,然后在"C#项目->引用"上点"右键->添加引用",然后选择"COM",把"CATIA"开头的都勾选上,另外还要勾选"CATStiWIPBridgeSurrogateCOMExe"。理论上讲,不同的CATIA功能只需要勾选对应的模块即可,ApplicationFrame就是CATIA基本框架,CATPartInterfaces是CAITA零件设计模块,CATAssemblyInterfaces是CATIA装配模块,Drafting2DLInterface是CATIA二维绘图模块......但是我懒得分辨,干脆全勾上,用到哪个算哪个。

程序代码如下

cs 复制代码
using INFITF; // CATIA V5 ApplicationFramework最基本的CATIA库
using MECMOD; // CATIA PartDesign模块
using System;

namespace MyProgram
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    // INFITF也有Window子模块,所以这里必须要指明
    public partial class MainWindow : System.Windows.Window
    {
        private INFITF.Application mainInstance; // CATIA进程对象
        public MainWindow()
        {
            InitializeComponent();
        }

        private void MyButtonClick(object sender, RoutedEventArgs e)
        {
            mainInstance = CatiaAutomation.GetCatia();
            MECMOD.PartDocument ActivePartDocument = (MECMOD.PartDocument)mainInstance.ActiveDocument;
            MECMOD.Part ActivePart = ActivePartDocument.Part;
            myButton.Content = ActivePart.get_Name();
        }
    }

    public static class CatiaAutomation
    {
        // 获取正在运行的CATIA应用实例
        public static INFITF.Application GetCatia()
        {
            try
            {
                // 尝试获取已运行的实例,并将其转化为CATIA实例类型INFITF.Application
                return (INFITF.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("CATIA.Application");
            }
            catch (COMException)
            {
                // 如果未找到,抛出异常
            }
        }
    }
}

关键点在

cs 复制代码
System.Runtime.InteropServices.Marshal.GetActiveObject("CATIA.Application");

CATIA.Application是CATIA注册的COM类型标识,Marshal.GetActiveObject函数通过标识符就可以搜索这个类型,如果系统中有正在运行的CATIA实例就能找到,随后的操作自然是在CATIA实例中操作。如果找不到,那就是系统中没有正在运行的CATIA实例

https://learn.microsoft.com/zh-cn/dotnet/api/system.runtime.interopservices.marshal.getactiveobject?view=netframework-4.8.1

在界面的xaml中,添加一个Button,点击这个Button时会调用MyButtonClick函数,如果想要在C#中操作这个Button,它的关联名是myButton

XML 复制代码
<Button x:Name="myButton" Content="连接CATIA" Click="MyButtonClick"/>

在C#代码中可以看到MyButtonClick函数的内容

cs 复制代码
private void MyButtonClick(object sender, RoutedEventArgs e)
{
    mainInstance = CatiaAutomation.GetCatia();
    MECMOD.PartDocument ActivePartDocument = (MECMOD.PartDocument)mainInstance.ActiveDocument;
    MECMOD.Part ActivePart = ActivePartDocument.Part;
    myButton.Content = ActivePart.get_Name();
}

首先获取了当前正在运行的CATIA实例,然后通过MECMOD模块获取了当前实例中的当前文档,接着获取了当前文档中的活动零件,最后把myButton上的内容改为活动零件的名字。

为了保证这段代码能够正确运行,需要

  1. 启动CATIA
  2. 在启动的CATIA中新建或者打开一个零件

如果没有启动CATIA,这段C#代码找不到正在运行的CATIA实例,那么mainInstance就得不到

如果CATIA没有正在处理任何文件,那么ActivePartDocument就获取不到

如果CATIA当前正在处理的不是零件文件(而是装配或者其他类型的文件),那么ActivePart就得不到

其实这些代码如果想要更加健壮,完全可以增加其他的保护性代码,比如说对得不到的情况做各种判断。不过本文只是引领入门,这些意外情况暂不讨论。

也就是说在获得了CATIA运行实例后,就可以对这个实例进行各种操作,还可以对实例外的东西------比如说myButton------进行各种操作,这样就可以对CATIA做二次开发了。

从代码的适应性来看,如果CATIA实例没有运行,那么由C#代码直接启动一个CATIA实例似乎是可行的

cs 复制代码
public static class CatiaHelper
{
    /// <summary>
    /// 获取 CATIA 应用实例,如果未运行则启动
    /// </summary>
    /// <param name="makeVisible">是否使 CATIA 可见(新启动时有效)</param>
    /// <returns>CATIA 应用实例</returns>
    public static INFITF.Application GetOrStartCatia(bool makeVisible = true)
    {
        try
        {
            // 首先尝试获取已运行的实例
            return (INFITF.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("CATIA.Application");
        }
        catch (COMException)
        {
            // 如果未找到,创建新实例
            Type catiaType = Type.GetTypeFromProgID("CATIA.Application");
            INFITF.Application newApp = (INFITF.Application)System.Activator.CreateInstance(catiaType);

            if (makeVisible)
            {
                newApp.Visible = true;
                //newApp.WindowState = CATWindowState.catWindowStateNormal; // 正常窗口状态
            }

            // 可选:给 CATIA 一点时间完成初始化
            System.Threading.Thread.Sleep(2000);

            return newApp;
        }
    }
}

找到CATIA对应的COM类型,然后创建一个新的实例。这是COM操作的标准处理方法,适用于一般的COM库。不过CATIA不是一般的COM库,在

https://blog.csdn.net/silent_missile/article/details/134646817?spm=1011.2415.3001.5331

介绍了CATIA的启动模式。

可以看到CATIA实例的启动其实需要很多额外的参数,简单的创建一个COM实例是无法启动CATIA的。

想要启动CATIA也很简单,直接用C#调用系统可执行文件,然后按照文章中所述的方式加上参数即可启动。

.net的方法

.net Framework只支持windows,为了真正跨平台,微软推出了.net core,随后又将其升级为.net,这个命名确实容易让人糊涂。不管怎么说,.net才是未来的发展趋势。

前面的代码中有一个问题,System.Runtime.InteropServices.Marshal.GetActiveObject是.net Framework中的函数,在.net中的Marshal类里没有GetActiveObject函数。

bing AI这样说

The Marshal .GetActiveObject method is not available in .NET Core . However , you can achieve similar functionality using P /Invoke to call the GetActiveObject function from the Windows API.

这里介绍一下处理方法。

在Visual Studio中创建一个.net的程序,并在依赖项中添加COM引用。

程序代码如下

cs 复制代码
using INFITF;
using MECMOD;
using PARTITF;
using ProductStructureTypeLib;
using System;

namespace MyProgram
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        private INFITF.Application mainInstance;
        public MainWindow()
        {
            InitializeComponent();
        }

        public static INFITF.Application GetOrStartCatia(bool makeVisible = true)
        {
            try
            {
                // 获取已运行的实例
                return (INFITF.Application)ComInterop.GetActiveObject("CATIA.Application");
            }
            catch (COMException)
            {
                // 如果没有获取到,抛出异常
            }
        }

        private void MyButtonClick(object sender, RoutedEventArgs e)
        {
            mainInstance = GetOrStartCatia();
            MECMOD.PartDocument ActivePartDocument = (MECMOD.PartDocument)mainInstance.ActiveDocument;
            MECMOD.Part ActivePart = ActivePartDocument.Part;
            myButton.Content = ActivePart.get_Name();
        }
    }

    public static class ComInterop
    {
        public static object GetActiveObject(string progId, bool throwOnError = false)
        {
            if (progId == null)
                throw new ArgumentNullException(nameof(progId));
            var hr = CLSIDFromProgIDEx(progId, out var clsid);
            if (hr < 0)
            {
                if (throwOnError)
                    Marshal.ThrowExceptionForHR(hr);
                return null;
            }
            hr = GetActiveObject(clsid, IntPtr.Zero, out var obj);
            if (hr < 0)
            {
                if (throwOnError)
                    Marshal.ThrowExceptionForHR(hr);
                return null;
            }
            return obj;
        }
        [DllImport("ole32.dll")]
        private static extern int CLSIDFromProgIDEx([MarshalAs(UnmanagedType.LPWStr)] string lpszProgID, out Guid lpclsid);
        [DllImport("oleaut32.dll")]
        private static extern int GetActiveObject([MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, IntPtr pvReserved, [MarshalAs(UnmanagedType.IUnknown)] out object ppunk);
    }
}

.net的Marshal类没带GetActiveObject函数没关系,我们自己创建一个GetActiveObject函数就可以了。

上面的代码由bing AI提供,创建了一个ComInterop类,里面只有一个函数GetActiveObject实现了.net Framework中Marshal.GetActiveObject的功能。

通过这个自己创建的ComInterop.GetActiveObject函数获得了正在运行的CATIA实例后,剩下的就一样了。

https://www.catiawidgets.net/2024/03/10/c-catia-connection/

也有一位老哥提供了类似的解决思路,他的代码比bing AI提供的代码更健壮。在正常情况下bing AI的代码足够用了。这里给出他的代码

cs 复制代码
using INFITF;
using MECMOD;
using PARTITF;
using ProductStructureTypeLib;
using System;

namespace MyProgram
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        private INFITF.Application mainInstance;
        public MainWindow()
        {
            InitializeComponent();
        }

        public static INFITF.Application GetOrStartCatia(bool makeVisible = true)
        {
            try
            {
                // 首先尝试获取已运行的实例
                return (INFITF.Application)CustomMarshal.GetActiveObject("CATIA.Application");
            }
            catch (COMException)
            {
                // 没有找到正在运行的CATIA实例,抛出异常
            }
        }

        private void MyButtonClick(object sender, RoutedEventArgs e)
        {
            mainInstance = GetOrStartCatia();
            MECMOD.PartDocument ActivePartDocument = (MECMOD.PartDocument)mainInstance.ActiveDocument;
            MECMOD.Part ActivePart = ActivePartDocument.Part;
            myButton.Content = ActivePart.get_Name();
        }
    }

    // https://www.catiawidgets.net/2024/03/10/c-catia-connection/
    public static class CustomMarshal
    {
        internal const String OLEAUT32 = "oleaut32.dll";
        internal const String OLE32 = "ole32.dll";

        [System.Security.SecurityCritical]  // auto-generated_required
        public static Object GetActiveObject(String progID)
        {
            Object? obj = null;
            Guid clsid;

            // Call CLSIDFromProgIDEx first then fall back on CLSIDFromProgID if
            // CLSIDFromProgIDEx doesn't exist.
            try
            {
                CLSIDFromProgIDEx(progID, out clsid);
            }
            //            catch
            catch (Exception)
            {
                CLSIDFromProgID(progID, out clsid);
            }

            GetActiveObject(ref clsid, IntPtr.Zero, out obj);
            return obj;
        }

        //[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
        [DllImport(OLE32, PreserveSig = false)]
        [ResourceExposure(ResourceScope.None)]
        [SuppressUnmanagedCodeSecurity]
        [System.Security.SecurityCritical]  // auto-generated
        private static extern void CLSIDFromProgIDEx([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);

        //[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
        [DllImport(OLE32, PreserveSig = false)]
        [ResourceExposure(ResourceScope.None)]
        [SuppressUnmanagedCodeSecurity]
        [System.Security.SecurityCritical]  // auto-generated
        private static extern void CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);

        //[DllImport(Microsoft.Win32.Win32Native.OLEAUT32, PreserveSig = false)]
        [DllImport(OLEAUT32, PreserveSig = false)]
        [ResourceExposure(ResourceScope.None)]
        [SuppressUnmanagedCodeSecurity]
        [System.Security.SecurityCritical]  // auto-generated
        private static extern void GetActiveObject(ref Guid rclsid, IntPtr reserved, [MarshalAs(UnmanagedType.Interface)] out Object ppunk);
    }
}

这两份代码都可以用,不出意外的话,运行速度也是一样的。如果出了意外,后者健壮性更好,不过对于真正的"意外"恐怕也没什么抵御能力,聊胜于无。重要的是:千万别用精简版的Windows,一定要用正常安装的Windows

到底应该选择.net Framework还是.net呢?

如果选择.net Framework,那么在编译生成的可执行文件中是不包含任何.net Framework的库文件的,这些库文件都要操作系统安装对应的.net Framework才有。而在开发时,必须要选定某一个版本的.net Framework,不同版本的.net Framework并不兼容,并不存在高版本兼容低版本,尤其是3.5版本之前的

win7自带的.net Framework是3.5,win10自带的.net Framework是4.7,而win11自带的.net Framework是4.8。如果想要在win10/win11启用.net Framework 3.5的话非常麻烦。

如果说终端机数量较多,分别运行着不同版本的操作系统,它们上面有其他正在运行的程序依赖某个特定版本的.net Framework,它们不可能为了一个新开发的程序而放弃正在运行的程序,而不同的终端机依赖的版本可能还不一样,那就会非常麻烦。如果某个终端机比较老旧,运行着win7+.net Framework 3.5,那就超级麻烦了。开发者必须要针对不同的.net Framework分别编译,然后分别分发,这还是在代码不需要修改的前提下,如果需要针对不同的.net Framework修改代码,那工作量还要大幅上升。开发者需要准备win7+.net Framework 3.5版、win10+.net Framework 4.7版、win11+.net Framework 4.8版至少3个版本。

不过好处也是很明显的,生成的可执行文件体积非常小,而且如果能依赖操作系统自带的.net Framework,运行速度还不错。

如果选择.net,那么在编译的时候,会把.net的库函数直接打包编译进可执行文件,这样不论在哪台终端机上运行都不需要担心库依赖的问题,一个版本就可以分发到所有的终端机。坏处就是生成的可执行文件体积会大------而且大很多,如果需要向很多台终端机部署,文件传输的耗时就会长很多。运行速度倒是不用担心,从.net7开始,运行速度优化了很多。

相关推荐
Leinwin13 小时前
Bulutistan:融合本地与云端,借 Azure Arc 开启创新之旅
microsoft·azure
xiaopengbc19 小时前
如果使用微软 Azure 托管的 OpenAI 服务
microsoft·flask·azure
程序员大辉21 小时前
微软常用运行库
microsoft
正义的大古1 天前
OpenLayers地图交互 -- 章节十一:拖拽文件交互详解
javascript·vue.js·microsoft·openlayers
产业家1 天前
AI重塑流量背后,微软广告打造下一代广告生态
人工智能·microsoft
CodeCraft Studio1 天前
Visual Studio 2026 Insiders 重磅发布:AI 深度集成、性能飞跃、全新设计
ide·人工智能·microsoft·visual studio
小年糕是糕手1 天前
【C语言】C语言预处理详解,从基础到进阶的全面讲解
linux·c语言·开发语言·数据结构·c++·windows·microsoft
FreeBuf_2 天前
微软修复CVSS 10.0分高危Entra ID漏洞CVE-2025-55241
python·microsoft·flask
OK_boom2 天前
C#异步协同常用例子
windows·microsoft·c#