当前位置: 首页 > news >正文

用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二维绘图模块……但是我懒得分辨,干脆全勾上,用到哪个算哪个。

程序代码如下

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.Applicationreturn (INFITF.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("CATIA.Application");}catch (COMException){// 如果未找到,抛出异常}}}
}

关键点在

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

<Button x:Name="myButton" Content="连接CATIA" Click="MyButtonClick"/>

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

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实例似乎是可行的

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引用。

程序代码如下

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的代码足够用了。这里给出他的代码

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_requiredpublic 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);}//            catchcatch (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-generatedprivate 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-generatedprivate 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-generatedprivate 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开始,运行速度优化了很多。

http://www.dtcms.com/a/406691.html

相关文章:

  • 以事件响应为驱动的 iOS 混淆策略,把混淆做成可测、可回溯、可改进的安全能力(iOS 混淆、IPA 加固、事件响应)
  • 廊坊免费网站建设模板带着做计算机项目的网站
  • 帆软普通报表根据条件限制展示不同报表
  • 《2025年AI产业发展十大趋势报告》六十九
  • HTTP 请求方式当中GET请求需要请求头吗?
  • 如何做一个属于自己的网站秦皇岛黄金海岸
  • 【Android View】窗口机制
  • 基于Spring Boot的竞赛管理系统架构设计
  • php代码删除网站温州市建设工程管理网站
  • 【开题答辩全过程】以 Bug交流网站为例,包含答辩的问题和答案
  • Agent开发02-关键思想(ReAct、ReWOO、Reflexion、LLM Compiler等)
  • 【Python语法基础学习笔记】输入输出进阶
  • 整站排名服务手机p2p网站开发
  • Flex布局完全指南:从容器到项目的全方位解析
  • 网站开发搜索功能怎么实现装潢公司设计效果图
  • 营销网站建设都是专业技术人员广西玉林网站建设
  • AI论文写作工作流:从输入到输出的五步模型
  • 《从0到1搭建客户画像系统:AI工具矩阵如何解决开发困局》
  • 阿里云云效将本地的maven相关文件批量推送到阿里云仓库以及使用
  • Docker Swarm主机编排
  • [论文阅读] 人工智能+ | 突破LLM情报分析瓶颈!MAKR多Agent框架让长文本因果推理更精准
  • apifox对一批文件批量请求一个接口
  • 情绪识别论文阅读——Eyemotion
  • 帝国建站软件全栈网站开发工程师
  • 嵌入式单线程编程模型的整理
  • 永磁同步电机无速度算法--改进滑模观测器(幂次指数趋近律)
  • 综合案例2:爬取某客栈的漫画
  • C++笔记(基础)初始化输入输出 const
  • 成都网站设计培训有了网址可以建网站吗
  • 【Redis】分布式集群