using System;
using System.Reflection;
using System.Text;
namespace DanroJump.Hardware.Com
{
///
/// Создает COM-транспорт через reflection-обертку над System.IO.Ports.SerialPort.
///
public sealed class ReflectionSerialComTransportFactory : IComTransportFactory
{
///
/// Создает транспорт для указанного serial-порта.
///
public IComTransport Create(SerialSettings settings)
{
return ReflectionSerialComTransport.Create(settings);
}
}
///
/// Reflection-адаптер serial-порта, который изолирует проект от прямой ссылки на System.IO.Ports.
///
internal sealed class ReflectionSerialComTransport : IComTransport
{
private const BindingFlags InstancePublicFlags = BindingFlags.Public | BindingFlags.Instance;
private static readonly Type SerialPortType = Type.GetType("System.IO.Ports.SerialPort, System");
private static readonly Type ParityType = Type.GetType("System.IO.Ports.Parity, System");
private static readonly Type StopBitsType = Type.GetType("System.IO.Ports.StopBits, System");
private static readonly PropertyInfo IsOpenProperty = SerialPortType?.GetProperty("IsOpen", InstancePublicFlags);
private static readonly PropertyInfo DataBitsProperty = SerialPortType?.GetProperty("DataBits", InstancePublicFlags);
private static readonly PropertyInfo ParityProperty = SerialPortType?.GetProperty("Parity", InstancePublicFlags);
private static readonly PropertyInfo StopBitsProperty = SerialPortType?.GetProperty("StopBits", InstancePublicFlags);
private static readonly PropertyInfo ReadTimeoutProperty = SerialPortType?.GetProperty("ReadTimeout", InstancePublicFlags);
private static readonly PropertyInfo WriteTimeoutProperty = SerialPortType?.GetProperty("WriteTimeout", InstancePublicFlags);
private static readonly PropertyInfo NewLineProperty = SerialPortType?.GetProperty("NewLine", InstancePublicFlags);
private static readonly PropertyInfo EncodingProperty = SerialPortType?.GetProperty("Encoding", InstancePublicFlags);
private static readonly PropertyInfo DtrEnableProperty = SerialPortType?.GetProperty("DtrEnable", InstancePublicFlags);
private static readonly PropertyInfo RtsEnableProperty = SerialPortType?.GetProperty("RtsEnable", InstancePublicFlags);
private static readonly MethodInfo OpenMethod = SerialPortType?.GetMethod("Open", InstancePublicFlags, null, Type.EmptyTypes, null);
private static readonly MethodInfo CloseMethod = SerialPortType?.GetMethod("Close", InstancePublicFlags, null, Type.EmptyTypes, null);
private static readonly MethodInfo DiscardInBufferMethod = SerialPortType?.GetMethod("DiscardInBuffer", InstancePublicFlags, null, Type.EmptyTypes, null);
private static readonly MethodInfo DiscardOutBufferMethod = SerialPortType?.GetMethod("DiscardOutBuffer", InstancePublicFlags, null, Type.EmptyTypes, null);
private static readonly MethodInfo ReadLineMethod = SerialPortType?.GetMethod("ReadLine", InstancePublicFlags, null, Type.EmptyTypes, null);
private static readonly MethodInfo WriteLineMethod = SerialPortType?.GetMethod("WriteLine", InstancePublicFlags, null, new[] { typeof(string) }, null);
private readonly object instance;
private ReflectionSerialComTransport(object instance)
{
this.instance = instance;
}
///
/// Показывает, открыт ли underlying serial-порт.
///
public bool IsOpen => GetBoolProperty(IsOpenProperty);
///
/// Создает и настраивает экземпляр SerialPort через reflection.
///
public static ReflectionSerialComTransport Create(SerialSettings settings)
{
if (SerialPortType == null)
{
throw new InvalidOperationException("System.IO.Ports.SerialPort is not available.");
}
EnsureRequiredMetadata();
object serialPort = Activator.CreateInstance(SerialPortType, settings.PortName, settings.BaudRate);
var transport = new ReflectionSerialComTransport(serialPort);
transport.SetProperty(DataBitsProperty, settings.DataBits);
transport.SetProperty(ParityProperty, ParseEnum(ParityType, settings.Parity.ToString()));
transport.SetProperty(StopBitsProperty, ParseEnum(StopBitsType, settings.StopBits.ToString()));
transport.SetProperty(ReadTimeoutProperty, settings.ReadTimeoutMs);
transport.SetProperty(WriteTimeoutProperty, settings.WriteTimeoutMs);
transport.SetProperty(NewLineProperty, "\n");
transport.SetProperty(EncodingProperty, Encoding.UTF8);
transport.SetProperty(DtrEnableProperty, true);
transport.SetProperty(RtsEnableProperty, true);
return transport;
}
///
/// Открывает serial-порт.
///
public void Open()
{
Invoke(OpenMethod);
}
///
/// Закрывает serial-порт.
///
public void Close()
{
Invoke(CloseMethod);
}
///
/// Очищает входной буфер serial-порта.
///
public void DiscardInBuffer()
{
Invoke(DiscardInBufferMethod);
}
///
/// Очищает выходной буфер serial-порта.
///
public void DiscardOutBuffer()
{
Invoke(DiscardOutBufferMethod);
}
///
/// Читает одну строку из serial-порта.
///
public string ReadLine()
{
return (string)Invoke(ReadLineMethod);
}
///
/// Записывает одну строку в serial-порт.
///
public void WriteLine(string value)
{
Invoke(WriteLineMethod, value);
}
///
/// Освобождает underlying SerialPort, если он поддерживает IDisposable.
///
public void Dispose()
{
if (instance is IDisposable disposable)
{
disposable.Dispose();
}
}
///
/// Проверяет наличие всех reflection-членов заранее, чтобы ошибки были явными.
///
private static void EnsureRequiredMetadata()
{
Require(DataBitsProperty, "DataBits");
Require(ParityProperty, "Parity");
Require(StopBitsProperty, "StopBits");
Require(ReadTimeoutProperty, "ReadTimeout");
Require(WriteTimeoutProperty, "WriteTimeout");
Require(NewLineProperty, "NewLine");
Require(EncodingProperty, "Encoding");
Require(DtrEnableProperty, "DtrEnable");
Require(RtsEnableProperty, "RtsEnable");
Require(IsOpenProperty, "IsOpen");
Require(OpenMethod, "Open()");
Require(CloseMethod, "Close()");
Require(DiscardInBufferMethod, "DiscardInBuffer()");
Require(DiscardOutBufferMethod, "DiscardOutBuffer()");
Require(ReadLineMethod, "ReadLine()");
Require(WriteLineMethod, "WriteLine(string)");
}
private static void Require(MemberInfo member, string memberName)
{
if (member == null)
{
throw new MissingMemberException(SerialPortType?.FullName ?? "System.IO.Ports.SerialPort", memberName);
}
}
///
/// Читает bool-свойство SerialPort через заранее найденный PropertyInfo.
///
private bool GetBoolProperty(PropertyInfo property)
{
return property.GetValue(instance) is bool value && value;
}
///
/// Записывает свойство SerialPort через заранее найденный PropertyInfo.
///
private void SetProperty(PropertyInfo property, object value)
{
property.SetValue(instance, value);
}
///
/// Вызывает метод SerialPort через reflection и пробрасывает реальную inner-ошибку.
///
private object Invoke(MethodInfo method, params object[] args)
{
try
{
return method.Invoke(instance, args);
}
catch (TargetInvocationException exception) when (exception.InnerException != null)
{
// Наружу пробрасываем реальную ошибку serial-порта, а не reflection-обертку.
throw exception.InnerException;
}
}
private static object ParseEnum(Type enumType, string value)
{
if (enumType == null)
{
throw new InvalidOperationException("System.IO.Ports enum metadata is not available.");
}
return Enum.Parse(enumType, value);
}
}
}