Table of Contents
C# CATIA Connection Class Update
In a previous post (here) we created a background process to check for a CATIA process and to establish a connection if one exists. The problem is while the process is interacting with the CATIA process nothing else can interact with CATIA, and can potentially lock up your application. So we are going to change the method so it can accepts an Action to process.
Marshall
Since .Net Core (.Net 8) does not have a Marshalling class we still have to use the Custom Marshall Class shown below.
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); }
CATIA Connection Class
In this version we have made some changes to improve the functionality.
- Added the Interface to allow dependency injection.
- The ability to pass in an Action to process if there is an Active connection.
- The ability to Remove and Set the Action while the process is running.
- An event handler that gets raised when the connection connects or disconnects.
- A Custom Event Args class and Connection class for the event handler.
- When there is no connection the interval between checks increments from 2 seconds to 20 seconds and then resets.
using INFITF; using Serilog; using System; using System.ComponentModel; namespace FromToAttributeMapper.Helpers { /// <summary> /// Interface for managing connections to CATIA V6. /// </summary> public interface ICATIAV6Connection { event EventHandler? OnConnectedChanged; Connection? Connection { get; } BackgroundWorker? Backgroundworker { get; } void StartBackGroundWorker(); void StopBackGroundWorker(); void SetAction(Action<Application?>? functionToProcess); void RemoveAction(); } /// <summary> /// Manages connections to CATIA V6. /// </summary> public class CATIAV6Connection : ICATIAV6Connection { private int _timeSpan = 2000; private ILogger _logger; private Action<Application?>? _functionToProcess = null; private string _appName = string.Empty; private bool _currentConnectionStatus = false; private int _connectionCounter = 1; private int _maxDoubles = 10; /// <summary> /// Event raised when the connection status changes. /// </summary> public event EventHandler? OnConnectedChanged; /// <summary> /// Gets the current connection. /// </summary> public Connection? Connection { get; private set; } = null; /// <summary> /// Gets the background worker used for monitoring the connection. /// </summary> public BackgroundWorker? Backgroundworker { get; } = null; /// <summary> /// Initializes a new instance of the CATIAV6Connection class. /// </summary> /// <param name="logger">The logger instance.</param> /// <param name="functionToProcess">The action to process.</param> /// <param name="StartConnection">Specifies whether to start the connection immediately.</param> /// <param name="TimeSpan">The time span between connection attempts.</param> /// <param name="appName">The name of the CATIA application.</param> public CATIAV6Connection(ILogger logger, Action<Application?>? functionToProcess = null, bool StartConnection = true, int TimeSpan = 2000, string appName = "Catia.Application") { _logger = logger; _functionToProcess = functionToProcess; _appName = appName; logger.Information($"CATIAV6Connection Constructor -> Start Connection : {StartConnection}, Timespan : {TimeSpan}."); _timeSpan = TimeSpan; Backgroundworker = new BackgroundWorker(); Backgroundworker.DoWork += new DoWorkEventHandler(DoWork); Backgroundworker.ProgressChanged += new ProgressChangedEventHandler(ProgressChanged); Backgroundworker.WorkerReportsProgress = true; Backgroundworker.WorkerSupportsCancellation = true; if (StartConnection == true) { _logger.Information($"CATIAV6Connection - StartBackGroundWorker - With Timespan : {TimeSpan}"); Backgroundworker.RunWorkerAsync(TimeSpan); } } /// <summary> /// Starts the background worker. /// </summary> public void StartBackGroundWorker() { if (Backgroundworker != null) { _logger.Information("CATIAV6Connection - StartBackGroundWorker."); Backgroundworker.RunWorkerAsync(_timeSpan); } } /// <summary> /// Stops the background worker. /// </summary> public void StopBackGroundWorker() { if (Backgroundworker != null) { if (!Backgroundworker.IsBusy) { _logger.Information("CATIAV6Connection - StopBackGroundWorker."); Backgroundworker.CancelAsync(); Backgroundworker.Dispose(); GC.Collect(); } } } /// <summary> /// Sets the action to be executed when the connection status changes. /// </summary> /// <param name="functionToProcess">The action to process.</param> public void SetAction(Action<Application?>? functionToProcess) { _functionToProcess = functionToProcess; } /// <summary> /// Removes the action to be executed when the connection status changes. /// </summary> public void RemoveAction() { _functionToProcess = null; } /// <summary> /// Background worker task to monitor the connection status. /// </summary> private void DoWork(object? sender, DoWorkEventArgs? e) { if (sender == null) return; BackgroundWorker? worker = (BackgroundWorker)sender; while (!worker.CancellationPending) { System.Threading.Thread.Sleep(_timeSpan * _connectionCounter); Application? App = null; if (Backgroundworker != null) { try { App = (Application)CustomMarshal.GetActiveObject(_appName); _connectionCounter = 1; } catch (Exception ex) { _logger.Error($"{_connectionCounter}:{_maxDoubles} CATIAV6Connection - DoWork - Exception Caught {ex.Message}."); _connectionCounter = (_connectionCounter <= _maxDoubles) ? _connectionCounter = _connectionCounter + 1 : _connectionCounter = 1; } finally { Backgroundworker.ReportProgress(0, new Connection(App)); } } } } /// <summary> /// Handles the progress change event of the background worker. /// </summary> private void ProgressChanged(object? sender, ProgressChangedEventArgs? e) { if (e == null || e.UserState == null) return; // Get the Connection Object Connection = (Connection)e.UserState; // Check for a Connection if (Connection.isConnected == true) { if (_currentConnectionStatus != true) { _currentConnectionStatus = true; OnConnectedChanged?.Invoke(this, new ConnectionEventArgs(Connection)); _logger.Information($"CATIAV6Connection - CATIA Connection Opened at : {DateTime.Now}."); } if (_functionToProcess != null) _functionToProcess(Connection.CatApp); } else { if (_currentConnectionStatus != false) { _currentConnectionStatus = false; OnConnectedChanged?.Invoke(this, new ConnectionEventArgs(Connection)); _logger.Information($"CATIAV6Connection - CATIA Connection Closed at : {DateTime.Now}."); } } } } /// <summary> /// Represents event arguments for connection events. /// </summary> public class ConnectionEventArgs : EventArgs { /// <summary> /// Gets the connection. /// </summary> public Connection? Connection { get; } = null; /// <summary> /// Initializes a new instance of the ConnectionEventArgs class. /// </summary> /// <param name="connection">The connection object.</param> public ConnectionEventArgs(Connection connection) { Connection = connection; } } /// <summary> /// Represents a connection with an application. /// </summary> public class Connection { /// <summary> /// Gets or sets the associated application. /// </summary> public Application? CatApp { get; set; } = null; /// <summary> /// Gets or sets a value indicating whether the connection is established. /// </summary> public bool isConnected { get; set; } = false; /// <summary> /// Initializes a new instance of the <see cref="Connection"/> class. /// </summary> /// <param name="catApp">The application associated with the connection.</param> public Connection(Application? catApp) { // Assign the provided application to the CatApp property CatApp = catApp; // Determine if the connection is established based on the presence of the application isConnected = (catApp != null); } } }
Passing the Method to be Called
Within the main application I created a simple method that takes in the CATIA application object, and logs the application name.
private void TestAction(Application? application) { if (application == null) return; _logger.Warning(application.FullName); }
Then we can pass this method into the Connection class, to do this we have to create an Action with a generic type of CATIA Application. since this is the object type that will be passed into our method, then equate it to our Test Action. We can then use the SetAction method in the connection class, by passing in the Action. Finally we can invoke the Action by passing in the CATIA application object.
Action<Application?> testActionDelegate = TestAction; _catiaV6Connection.SetAction(testActionDelegate); testActionDelegate(catiaV6Connection?.Connection?.CatApp);
With this in place we can now validate the connection and if one exists, then process the method, this way the interaction between the connection class and the passed in action is sequential and can not cerate a blocking situation.