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
类的详细解析:
一、核心功能概述
该类是 运动控制器回调机制的管理器,负责:
-
注册/注销硬件事件回调(如限位触发、运动完成等)
-
多任务环境下的事件路由(支持多个独立任务线程)
-
处理控制器与.NET应用程序间的跨线程通信
-
提供类型安全的参数传递和返回值处理
二、关键设计模式
-
观察者模式
-
通过
EventHandler<CallbackOccurredEventArgs>
实现事件订阅 -
使用
ErrorOccurred
事件处理异常
-
-
资源管理
-
实现
IDisposable
确保原生资源释放 -
使用
lock
保证线程安全
-
-
桥接模式
-
通过
Aerotech.A3200.SystemDLLWrapper
桥接非托管代码
-
三、核心类解析
1. PerTaskCallbackHandler
(内部类)
职责:单个任务线程的回调管理
关键字段
字段 | 类型 | 作用 |
---|---|---|
hAerCtrl | ControllerHandle | 控制器硬件句柄 |
registeredCallbacks | Dictionary<int, EventHandler> | 回调ID与处理函数的映射 |
listener | Thread | 独立监听线程 |
shouldListen | volatile 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
(外部类)
职责:多任务回调的统一入口
关键逻辑
四、线程安全实现
-
双重锁定机制
-
listenLock
控制监听线程的暂停/恢复
-
lock (listenLock) {
shouldListen = false;
Monitor.Pulse(listenLock); // 唤醒等待线程
}
volatile 修饰符
private volatile bool shouldExit; // 确保多线程可见性
回调字典的细粒度锁
lock (registeredCallbacks) {
// 添加/移除回调时的同步
}
五、异常处理设计
-
统一错误解析
通过ExceptionResolver.ResolveThrow
转换硬件错误码为.NET异常 -
错误事件传递
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));
七、性能优化点
-
参数缓存
当前每次回调都重新解析参数,可缓存频繁使用的回调参数类型 -
线程池优化
每个任务独立线程可能产生资源竞争,建议改为线程池 -
Native调用批处理
合并连续的AerCallbackArgsGetXXX
调用
八、与硬件交互的关键API
DLL函数 | 作用 | 对应.NET封装 |
---|---|---|
AerCallbackRegister | 注册硬件事件 | Wrapper.AerCallbackRegister |
AerCallbackWait | 阻塞等待事件 | 监听线程核心 |
AerCallbackReturnDouble | 返回双精度值 | 通过CallbackOccurredEventArgs |
该设计完美体现了工业控制软件对 实时性 和 可靠性 的要求,通过严格的线程管理和资源控制,确保在高速运动控制场景下的稳定运行。