Aerotech系列(6)Aerotech运动控制器回调机制的管理器

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Aerotech.A3200.Exceptions;
using Aerotech.A3200.SystemDLLWrapper;

namespace Aerotech.A3200.Callbacks;

internal class CallbackRegistrar
{
    private class PerTaskCallbackHandler : IDisposable
    {
        private static readonly Dictionary<int, TypeCode> callbackTypeMappings;

        private readonly object listenLock = new object();

        private readonly Thread listener;

        private readonly ControllerHandle hAerCtrl;

        private readonly Dictionary<int, EventHandler<CallbackOccurredEventArgs>> registeredCallbacks = new Dictionary<int, EventHandler<CallbackOccurredEventArgs>>();

        private readonly Controller controller;

        private readonly CallbackRegistrar callbackRegistrar;

        private readonly TaskId task;

        private volatile bool shouldExit;

        private bool disposed;

        private volatile bool shouldListen = true;

        public TaskId TaskId => task;

        private bool Listening
        {
            get
            {
                lock (listenLock)
                {
                    return shouldListen;
                }
            }
            set
            {
                lock (listenLock)
                {
                    shouldListen = value;
                    if (shouldListen)
                    {
                        Monitor.Pulse(listenLock);
                    }
                    else
                    {
                        ExceptionResolver.ResolveThrow(Wrapper.AerCallbackWaitCancel(hAerCtrl.Value));
                    }
                }
            }
        }

        internal event EventHandler<CallbackErrorEventArgs> ErrorOccurred;

        static PerTaskCallbackHandler()
        {
            callbackTypeMappings = new Dictionary<int, TypeCode>();
            callbackTypeMappings[0] = TypeCode.Int32;
            callbackTypeMappings[1] = TypeCode.Double;
            callbackTypeMappings[3] = TypeCode.String;
        }

        public PerTaskCallbackHandler(Controller controller, CallbackRegistrar callbackRegistrar, TaskId task)
        {
            hAerCtrl = controller.Tasks.AllTasks[(int)task].TaskControllerHandle;
            this.controller = controller;
            this.callbackRegistrar = callbackRegistrar;
            this.task = task;
            listener = new Thread(InternalUtilities.WrapDelegateForCulture(listenForCallbacks));
            listener.Name = $"Callback listener ({this.task})";
            listener.IsBackground = true;
        }

        internal bool GetCanRegister(int callbackNumber)
        {
            lock (registeredCallbacks)
            {
                if (registeredCallbacks.ContainsKey(callbackNumber))
                {
                    return true;
                }
            }

            int pdwCount_ = 0;
            ExceptionResolver.ResolveThrow(Wrapper.AerIntCommServiceGetRegisteredInterruptCount(hAerCtrl.Value, 1, callbackNumber, (int)TaskId, ref pdwCount_));
            return pdwCount_ == 0;
        }

        void IDisposable.Dispose()
        {
            Dispose(disposing: true);
        }

        private void Dispose(bool disposing)
        {
            if (!(!disposed && disposing))
            {
                return;
            }

            lock (registeredCallbacks)
            {
                Listening = false;
                foreach (int key in registeredCallbacks.Keys)
                {
                    Wrapper.AerCallbackUnregister(hAerCtrl.Value, (int)task, key);
                }

                registeredCallbacks.Clear();
                hAerCtrl.Dispose();
                shouldExit = true;
                Listening = true;
            }

            disposed = true;
        }

        public void AddCallbackRegistration(int callbackNumber, EventHandler<CallbackOccurredEventArgs> evt)
        {
            if (evt == null)
            {
                throw new ArgumentNullException("handler");
            }

            if (callbackNumber < 0)
            {
                throw new ArgumentOutOfRangeException("callbackNumber");
            }

            if (!listener.IsAlive)
            {
                listener.Start();
            }

            ErrorData err = ErrorData.NoError;
            lock (registeredCallbacks)
            {
                if (registeredCallbacks.ContainsKey(callbackNumber))
                {
                    Dictionary<int, EventHandler<CallbackOccurredEventArgs>> dictionary = registeredCallbacks;
                    dictionary[callbackNumber] = (EventHandler<CallbackOccurredEventArgs>)Delegate.Combine(dictionary[callbackNumber], evt);
                    return;
                }

                Listening = false;
                err = Wrapper.AerCallbackRegister(hAerCtrl.Value, (int)task, callbackNumber);
                if (err.Code == 0)
                {
                    registeredCallbacks.Add(callbackNumber, evt);
                }

                Listening = true;
            }

            ExceptionResolver.ResolveThrow(err);
        }

        public void RemoveCallbackRegistration(int callbackNumber, EventHandler<CallbackOccurredEventArgs> handler)
        {
            ErrorData err = ErrorData.NoError;
            lock (registeredCallbacks)
            {
                if (!registeredCallbacks.ContainsKey(callbackNumber))
                {
                    return;
                }

                Dictionary<int, EventHandler<CallbackOccurredEventArgs>> dictionary = registeredCallbacks;
                dictionary[callbackNumber] = (EventHandler<CallbackOccurredEventArgs>)Delegate.Remove(dictionary[callbackNumber], handler);
                if (registeredCallbacks[callbackNumber] != null && registeredCallbacks[callbackNumber].GetInvocationList().Length != 0)
                {
                    return;
                }

                Listening = false;
                err = Wrapper.AerCallbackUnregister(hAerCtrl.Value, (int)task, callbackNumber);
                if (err.Code == 0)
                {
                    registeredCallbacks.Remove(callbackNumber);
                }

                Listening = true;
            }

            ExceptionResolver.ResolveThrow(err);
        }

        private void listenForCallbacks()
        {
            while (true)
            {
                lock (listenLock)
                {
                    if (!shouldListen)
                    {
                        Monitor.Wait(listenLock);
                    }
                }

                if (shouldExit)
                {
                    break;
                }

                int dwTask_ = 0;
                int dwID_ = 0;
                ErrorData err = Wrapper.AerCallbackWait(hAerCtrl.Value, ref dwTask_, ref dwID_, -1);
                if (err.Equals(LibraryCallbackWaitCancel))
                {
                    continue;
                }

                if (err.Code != 0)
                {
                    try
                    {
                        ExceptionResolver.ResolveThrow(err);
                    }
                    catch (Exception exception)
                    {
                        raiseErrorOccurred(controller, new CallbackErrorEventArgs(controller, TaskId, exception));
                        continue;
                    }
                }

                lock (registeredCallbacks)
                {
                    if (!registeredCallbacks.ContainsKey(dwID_))
                    {
                        continue;
                    }

                    object[] arguments;
                    try
                    {
                        arguments = getArguments();
                    }
                    catch (A3200Exception ex)
                    {
                        Wrapper.AerCallbackReturnVoid(hAerCtrl.Value, dwTask_, ex.ErrorCode, 0.0, 0.0);
                        goto end_IL_00af;
                    }
                    catch (Exception)
                    {
                        Wrapper.AerCallbackReturnVoid(hAerCtrl.Value, dwTask_, new ErrorData(98, 2), 0.0, 0.0);
                        goto end_IL_00af;
                    }

                    CallbackOccurredEventArgs callbackOccurredEventArgs = new CallbackOccurredEventArgs(hAerCtrl, controller, arguments, (TaskId)dwTask_, dwID_);
                    ErrorData taskFault_ = ErrorData.NoError;
                    double dInfoVar0_ = 0.0;
                    double dInfoVar1_ = 0.0;
                    try
                    {
                        registeredCallbacks[dwID_](controller, callbackOccurredEventArgs);
                        dInfoVar0_ = callbackOccurredEventArgs.Info0;
                        dInfoVar1_ = callbackOccurredEventArgs.Info1;
                    }
                    catch (A3200Exception ex3)
                    {
                        taskFault_ = ex3.ErrorCode;
                    }
                    catch (Exception)
                    {
                        taskFault_ = new ErrorData(98, 2);
                    }

                    TypeCode typeCode = Convert.GetTypeCode(callbackOccurredEventArgs.ReturnValue);
                    try
                    {
                        switch (typeCode)
                        {
                            case TypeCode.Double:
                                ExceptionResolver.ResolveThrow(Wrapper.AerCallbackReturnDouble(hAerCtrl.Value, dwTask_, (double)callbackOccurredEventArgs.ReturnValue, taskFault_, dInfoVar0_, dInfoVar1_));
                                break;
                            case TypeCode.String:
                                ExceptionResolver.ResolveThrow(Wrapper.AerCallbackReturnString(hAerCtrl.Value, dwTask_, (string)callbackOccurredEventArgs.ReturnValue, taskFault_, dInfoVar0_, dInfoVar1_));
                                break;
                            case TypeCode.Empty:
                                ExceptionResolver.ResolveThrow(Wrapper.AerCallbackReturnVoid(hAerCtrl.Value, dwTask_, taskFault_, dInfoVar0_, dInfoVar1_));
                                break;
                        }
                    }
                    catch (Exception)
                    {
                    }

                end_IL_00af:;
                }
            }
        }

        private object[] getArguments()
        {
            int nArgs_ = 0;
            ExceptionResolver.ResolveThrow(Wrapper.AerCallbackArgsGetCount(hAerCtrl.Value, (int)task, ref nArgs_));
            object[] array = new object[nArgs_];
            for (int i = 0; i < nArgs_; i++)
            {
                int type_ = 0;
                ExceptionResolver.ResolveThrow(Wrapper.AerCallbackArgsGetType(hAerCtrl.Value, (int)task, i, ref type_));
                callbackTypeMappings.TryGetValue(type_, out var value);
                object obj = null;
                switch (value)
                {
                    case TypeCode.Int32:
                        {
                            int value_2 = 0;
                            ExceptionResolver.ResolveThrow(Wrapper.AerCallbackArgsGetDWORD(hAerCtrl.Value, (int)task, i, ref value_2));
                            obj = value_2;
                            break;
                        }
                    case TypeCode.Double:
                        {
                            double value_ = 0.0;
                            ExceptionResolver.ResolveThrow(Wrapper.AerCallbackArgsGetDouble(hAerCtrl.Value, (int)task, i, ref value_));
                            obj = value_;
                            break;
                        }
                    case TypeCode.String:
                        {
                            StringBuilder stringBuilder = new StringBuilder(188);
                            ExceptionResolver.ResolveThrow(Wrapper.AerCallbackArgsGetString(hAerCtrl.Value, (int)task, i, stringBuilder, stringBuilder.Capacity));
                            obj = stringBuilder.ToString();
                            break;
                        }
                }

                array[i] = obj;
            }

            return array;
        }

        private void raiseErrorOccurred(object sender, CallbackErrorEventArgs e)
        {
            this.ErrorOccurred?.Invoke(controller, e);
        }
    }

    private static readonly ErrorData LibraryCallbackWaitCancel = new ErrorData(95, 17);

    private readonly Controller controller;

    private readonly Dictionary<TaskId, PerTaskCallbackHandler> callbackHandlers = new Dictionary<TaskId, PerTaskCallbackHandler>();

    public CallbackRegistrar(Controller controller)
    {
        this.controller = controller;
        foreach (TaskId value2 in Enum.GetValues(typeof(TaskId)))
        {
            PerTaskCallbackHandler value = new PerTaskCallbackHandler(this.controller, this, value2);
            callbackHandlers.Add(value2, value);
        }
    }

    public void AddCallbackRegistration(TaskId task, int callbackNumber, EventHandler<CallbackOccurredEventArgs> evt)
    {
        callbackHandlers[task].AddCallbackRegistration(callbackNumber, evt);
    }

    public void RemoveCallbackRegistration(TaskId task, int callbackNumber, EventHandler<CallbackOccurredEventArgs> evt)
    {
        callbackHandlers[task].RemoveCallbackRegistration(callbackNumber, evt);
    }

    public void AddErrorOccurredRegistration(TaskId task, EventHandler<CallbackErrorEventArgs> evt)
    {
        callbackHandlers[task].ErrorOccurred += evt;
    }

    public void RemoveErrorOccurredRegistration(TaskId task, EventHandler<CallbackErrorEventArgs> evt)
    {
        callbackHandlers[task].ErrorOccurred -= evt;
    }

    public bool GetCanRegister(TaskId task, int callbackNumber)
    {
        return callbackHandlers[task].GetCanRegister(callbackNumber);
    }

    public void ShutDown()
    {
        foreach (PerTaskCallbackHandler value in callbackHandlers.Values)
        {
            ((IDisposable)value).Dispose();
        }
    }
}

Aerotech A3200 运动控制系统中 CallbackRegistrar 类的详细解析:


一、核心功能概述

该类是 运动控制器回调机制的管理器,负责:

  1. 注册/注销硬件事件回调(如限位触发、运动完成等)

  2. 多任务环境下的事件路由(支持多个独立任务线程)

  3. 处理控制器与.NET应用程序间的跨线程通信

  4. 提供类型安全的参数传递和返回值处理


二、关键设计模式

  1. 观察者模式

    • 通过 EventHandler<CallbackOccurredEventArgs> 实现事件订阅

    • 使用 ErrorOccurred 事件处理异常

  2. 资源管理

    • 实现 IDisposable 确保原生资源释放

    • 使用 lock 保证线程安全

  3. 桥接模式

    • 通过 Aerotech.A3200.SystemDLLWrapper 桥接非托管代码


三、核心类解析

1. PerTaskCallbackHandler(内部类)

职责:单个任务线程的回调管理

关键字段
字段类型作用
hAerCtrlControllerHandle控制器硬件句柄
registeredCallbacksDictionary<int, EventHandler>回调ID与处理函数的映射
listenerThread独立监听线程
shouldListenvolatile bool线程安全的状态控制
核心方法
  • listenForCallbacks()
    监听线程主循环,通过 AerCallbackWait 阻塞等待硬件事件,触发时:

// 伪代码流程
1. 等待事件 -> AerCallbackWait()
2. 解析参数 -> getArguments()
3. 触发回调 -> registeredCallbacks[dwID_](...)
4. 返回结果 -> AerCallbackReturnDouble/String/Void()

getArguments()
动态解析非托管代码传递的参数,支持三种类型:

TypeCode.Int32   // 通过 AerCallbackArgsGetDWORD
TypeCode.Double  // 通过 AerCallbackArgsGetDouble
TypeCode.String  // 通过 AerCallbackArgsGetString
2. CallbackRegistrar(外部类)

职责:多任务回调的统一入口

关键逻辑

 

四、线程安全实现

  1. 双重锁定机制

    • listenLock 控制监听线程的暂停/恢复

lock (listenLock) {
    shouldListen = false;
    Monitor.Pulse(listenLock); // 唤醒等待线程
}

volatile 修饰符

 

private volatile bool shouldExit; // 确保多线程可见性

回调字典的细粒度锁

lock (registeredCallbacks) {
    // 添加/移除回调时的同步
}

五、异常处理设计

  1. 统一错误解析
    通过 ExceptionResolver.ResolveThrow 转换硬件错误码为.NET异常

  2. 错误事件传递

try { /*...*/ } 
catch (Exception ex) {
    raiseErrorOccurred(controller, new CallbackErrorEventArgs(...));
}

资源释放保护

void Dispose(bool disposing) {
    if (!disposed) {
        // 确保所有回调注销
        Wrapper.AerCallbackUnregister(...);
    }
}

六、典型使用场景

1. 注册位置到达回调
var registrar = new CallbackRegistrar(controller);
registrar.AddCallbackRegistration(TaskId.Task1, 
    callbackId: 102, 
    (sender, e) => {
        Console.WriteLine($"Axis reached: {e.Arguments[0]}");
        e.ReturnValue = 1.0; // 返回值给控制器
    });

2. 错误处理

registrar.AddErrorOccurredRegistration(TaskId.Task1, 
    (sender, e) => Logger.Error(e.Exception));

七、性能优化点

  1. 参数缓存
    当前每次回调都重新解析参数,可缓存频繁使用的回调参数类型

  2. 线程池优化
    每个任务独立线程可能产生资源竞争,建议改为线程池

  3. Native调用批处理
    合并连续的 AerCallbackArgsGetXXX 调用


八、与硬件交互的关键API

DLL函数作用对应.NET封装
AerCallbackRegister注册硬件事件Wrapper.AerCallbackRegister
AerCallbackWait阻塞等待事件监听线程核心
AerCallbackReturnDouble返回双精度值通过CallbackOccurredEventArgs

该设计完美体现了工业控制软件对 实时性 和 可靠性 的要求,通过严格的线程管理和资源控制,确保在高速运动控制场景下的稳定运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值