CATIA二次开发(2)C#启用AOT
微软为.net引入了AOT技术,这样源代码可以编译为二进制代码,而不是IL代码。IL代码是虚拟机用代码,运行时由虚拟机即时编译(JIT)成二进制,然后再运行,IL代码极容易被逆向;二进制代码是在开发时就编译成二进制,运行时直接二进制代码本地运行,不需要虚拟机在运行时临时再编译一遍。JIT的好处就是跨平台更容易;AOT的好处就是速度更快,而且不容易被逆向。
如果希望软件发布后不要被逆向,那就要选择AOT。
AOT对控制台程序和类库支持挺好,但是对于GUI程序支持都很差,不论是WinForm、WPF、WinUI还是MAUI。而基于CATIA二次开发的软件必然是GUI的,所以AOT的支持是个麻烦。处理方法就是:把GUI程序的界面和功能做剥离,把功能AOT编译成类库,供JIT的GUI界面调用。
Visual Studio解决方案设置
GUI界面和类库的分别设置
Visual Studio创建一个解决方案,类型选WPF应用程序,类别选择.net,别选.net Framework。
取消勾选“将解决方案和项目放在同一个目录中。
解决方案下包含多个项目,这多个项目其中一个时WPF的GUI界面,另外再创建一个C#的类库。新建的时候可以给WPF项目的名称,C#类库等到新建类库项目时再给。
名字和目录设置好后下一步就创建好了。
然后在“解决方案资源管理器”的“解决方案”上点“右键->添加->新建项目”,在对话框中类型选择C#类库,并设定名字和目录。
在“解决方案资源管理器”上左键点击C#类库,在xml编辑界面的<PropertyGroup>节点插入<PublishAot>true</PublishAot>
<PropertyGroup><PublishAot>true</PublishAot>
</PropertyGroup>
在“解决方案资源管理器”上右键点击C#类库,在弹出菜单中选“属性”,可以设置目标平台,比如x64
GUI界面对类库的依赖设置
GUI界面程序必要依赖类库。如果不顾一切,其实可以把C#类库编译发布后,以DllImport或者LibraryImport供GUI界面调用。但既然开发者有类库源代码,那最好还是直接依赖,这样在IDE中有提示和跳转,方便很多。
在“解决方案资源管理器”上的GUI界面项目下的“依赖项”点右键,“添加项目引用”,Visual Studio会自动识别到当前解决方案中的其他项目——C#类库项目,勾选就可以了。将来在开发或者发布的时候Visual Studio会根据解决方案自动识别依赖关系,自动处理编译顺序和打包。
设定COM依赖
在C#类库的“依赖项”点右键,进入COM选单,把CATIA V5的各个COM都勾选上。如果在C#类库对所有需要的CATIA COM函数都做了封装,理论上GUI界面可以完全不用管CATIA COM,但是正常来讲这是不可能的,所以GUI界面也要把CATIA COM的依赖都勾选。
C#类库的编写
编辑C#类库的源码文件
#nullable enable
// 引入CATIA命名空间
using INFITF;
using MECMOD;
using PARTITF;
using ProductStructureTypeLib;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Text;
using System.Windows;
namespace CATIAConnect
{public class Connect{private INFITF.Application mainInstance;public INFITF.Application CATIAInstance { get { return mainInstance; } }public Connect(){GetOrStartCatia();}public void GetOrStartCatia(bool makeVisible = true){try{// 首先尝试获取已运行的实例object? comInstance = CustomMarshal.GetActiveObject("CATIA.Application");mainInstance = comInstance == null ? throw new COMException() : (INFITF.Application)comInstance;}catch (COMException comExc){// 启动新的CATIA进程实例}}}// https://www.catiawidgets.net/2024/03/10/c-catia-connection/public static partial 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, bool throwOnError = false){object? obj;Guid clsid;int hr;// Call CLSIDFromProgIDEx first then fall back on CLSIDFromProgID if// CLSIDFromProgIDEx doesn't exist.try{hr = CLSIDFromProgIDEx(progID, out clsid, out hr);if (hr != 0) // S_OK in HRESULT{Marshal.ThrowExceptionForHR(hr);}}catch (Exception){hr = CLSIDFromProgID(progID, out clsid, out hr);if (hr != 0) // S_OK in HRESULT{if (throwOnError)Marshal.ThrowExceptionForHR(hr);return null;}}try{hr = GetActiveObject(clsid, IntPtr.Zero, out obj);if (hr != 0) // S_OK in HRESULT{if (throwOnError)Marshal.ThrowExceptionForHR(hr);}}catch (Exception){return null;}return obj;}//https://learn.microsoft.com/zh-cn/dotnet/api/system.runtime.interopservices.preservesigattribute?view=net-8.0//https://learn.microsoft.com/zh-cn/dotnet/api/system.runtime.interopservices.dllimportattribute?view=net-8.0//[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]//[DllImport(OLE32, PreserveSig = false)][LibraryImport(OLE32)][ResourceExposure(ResourceScope.None)][SuppressUnmanagedCodeSecurity][System.Security.SecurityCritical] // auto-generatedprivate static partial int CLSIDFromProgIDEx([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid, out int @return);//[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]//[DllImport(OLE32, PreserveSig = false)][LibraryImport(OLE32)][ResourceExposure(ResourceScope.None)][SuppressUnmanagedCodeSecurity][System.Security.SecurityCritical] // auto-generatedprivate static partial int CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid, out int @return);//[DllImport(Microsoft.Win32.Win32Native.OLEAUT32, PreserveSig = false)][DllImport(OLEAUT32, PreserveSig = true)][ResourceExposure(ResourceScope.None)][SuppressUnmanagedCodeSecurity][System.Security.SecurityCritical] // auto-generatedprivate static extern int GetActiveObject([MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, IntPtr pvReserved, [MarshalAs(UnmanagedType.IUnknown)] out object ppunk);}
}
在类库中提供一个属性,并设置为只读(只有get,没有set),以供GUI界面调用。
而且尽可能的把DllImport替换为了LibraryImport,PreserveSig设置为true,尽可能将HRESULT返回,这里的演示代码只是做了简单处理,实际产品开发中可以做更丰富的处理。
GUI界面的编写
编辑GUI界面的源代码
#nullable enable
// 引入CATIA命名空间
using INFITF;
using MECMOD;
using PARTITF;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using CATIAConnect;
using System.Runtime.InteropServices;
using CATPspPlantShipTypeLib;namespace CATIAFrontEnd
{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : System.Windows.Window{private CATIAConnect.Connect? connect;public MainWindow(){InitializeComponent();try{connect = new CATIAConnect.Connect();}catch (COMException){connect = null;}}private void MyButtonClick(object sender, RoutedEventArgs e){MECMOD.PartDocument ActivePartDocument = (MECMOD.PartDocument)connect.CATIAInstance.ActiveDocument;MECMOD.Part ActivePart = ActivePartDocument.Part;myButton.Content = ActivePart.get_Name();}}
}
连接到CATIA后,通过属性获取CATIA进程实例,然后针对这个实例进行各种操作。