using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; namespace DanroJump.Hardware.Com { /// /// Получает список COM-портов через reflection, чтобы проект не падал в Unity-профилях без System.IO.Ports. /// public sealed class ReflectionComPortProvider : IComPortProvider { private static readonly Type SerialPortType = FindSerialPortType(); private static bool diagnosticLogged; /// /// Возвращает имена доступных serial-портов или пустой список, если API недоступен. /// public string[] GetPortNames() { LogSerialPortDiagnosticOnce(); string[] registryPorts = GetWindowsRegistryPortNames(); if (SerialPortType == null) { Debug.LogWarning("[ComSystem] System.IO.Ports.SerialPort is not available in this Unity profile."); return registryPorts; } try { var getPortNamesMethod = SerialPortType.GetMethod("GetPortNames", BindingFlags.Public | BindingFlags.Static); if (getPortNamesMethod == null) { Debug.LogWarning("[ComSystem] System.IO.Ports.SerialPort.GetPortNames() is not available."); return registryPorts; } var ports = (string[])getPortNamesMethod.Invoke(null, null) ?? Array.Empty(); Debug.Log($"[ComSystem] SerialPort.GetPortNames returned: {(ports.Length > 0 ? string.Join(", ", ports) : "")}"); return ports.Length > 0 ? ports : registryPorts; } catch (Exception exception) { Exception unwrapped = Unwrap(exception); Debug.LogWarning($"[ComSystem] Failed to list COM ports via SerialPort.GetPortNames: {unwrapped.GetType().Name}: {unwrapped.Message}"); return registryPorts; } } private static string[] GetWindowsRegistryPortNames() { #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN try { Type registryType = FindType("Microsoft.Win32.Registry"); Type registryKeyType = FindType("Microsoft.Win32.RegistryKey"); if (registryType == null || registryKeyType == null) { Debug.Log("[ComSystem] Windows registry API is not available for COM fallback."); return Array.Empty(); } object localMachine = registryType.GetProperty("LocalMachine", BindingFlags.Public | BindingFlags.Static) ?.GetValue(null); if (localMachine == null) { localMachine = registryType.GetField("LocalMachine", BindingFlags.Public | BindingFlags.Static) ?.GetValue(null); } if (localMachine == null) { Debug.Log("[ComSystem] Windows registry fallback could not access LocalMachine."); return Array.Empty(); } MethodInfo openSubKey = registryKeyType.GetMethod( "OpenSubKey", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string) }, null); MethodInfo getValueNames = registryKeyType.GetMethod( "GetValueNames", BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); MethodInfo getValue = registryKeyType.GetMethod( "GetValue", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string) }, null); if (openSubKey == null || getValueNames == null || getValue == null) { Debug.Log("[ComSystem] Windows registry fallback methods are not available."); return Array.Empty(); } object serialCommKey = openSubKey.Invoke(localMachine, new object[] { @"HARDWARE\DEVICEMAP\SERIALCOMM" }); if (serialCommKey == null) { Debug.Log("[ComSystem] Windows registry fallback did not find HARDWARE\\DEVICEMAP\\SERIALCOMM."); return Array.Empty(); } using (serialCommKey as IDisposable) { var valueNames = (string[])getValueNames.Invoke(serialCommKey, null) ?? Array.Empty(); var ports = new List(); var seen = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string valueName in valueNames) { string portName = getValue.Invoke(serialCommKey, new object[] { valueName }) as string; if (string.IsNullOrWhiteSpace(portName)) { continue; } portName = portName.Trim(); if (!seen.Add(portName)) { continue; } ports.Add(portName); } ports.Sort(StringComparer.OrdinalIgnoreCase); Debug.Log($"[ComSystem] Windows registry COM fallback returned: {(ports.Count > 0 ? string.Join(", ", ports) : "")}"); return ports.ToArray(); } } catch (Exception exception) { Exception unwrapped = Unwrap(exception); Debug.LogWarning($"[ComSystem] Windows registry COM fallback failed: {unwrapped.GetType().Name}: {unwrapped.Message}"); return Array.Empty(); } #else return Array.Empty(); #endif } private static Type FindSerialPortType() { var type = Type.GetType("System.IO.Ports.SerialPort, System", false) ?? Type.GetType("System.IO.Ports.SerialPort, System.IO.Ports", false); if (type != null) { return type; } foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { type = assembly.GetType("System.IO.Ports.SerialPort", false); if (type != null) { return type; } } return null; } private static void LogSerialPortDiagnosticOnce() { if (diagnosticLogged) { return; } diagnosticLogged = true; string message = SerialPortType == null ? "SerialPort API resolution failed." : $"SerialPort API resolved from assembly: {SerialPortType.Assembly.FullName}"; Debug.Log($"[ComSystem] {message}"); } private static Type FindType(string fullName) { foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { Type type = assembly.GetType(fullName, false); if (type != null) { return type; } } return null; } /// /// Возвращает исходную ошибку reflection-вызова без TargetInvocationException-обертки. /// private static Exception Unwrap(Exception exception) { return exception is TargetInvocationException { InnerException: not null } ? exception.InnerException : exception; } } }