使用P/Invoke调用IOKit时无法触发回调通知
.NET跨平台库macOS下IOKit串口插拔通知未触发问题
我正在开发一个.NET跨平台库,在macOS平台尝试用IOKit监听串口插拔事件,后续用标准.NET串口库处理操作。所有Native方法返回值均正常,但设备插拔时publishedNotificationCallback和terminatedNotificationCallback始终不触发,代码无崩溃,只是通知完全没反应。
配置通知的代码
using NM = Native.NativeMethods; public class SerialPortManager { private static SerialPortManager _self; private nint _portPublishedIterator = 0; private nint _portTerminatedIterator = 0; public SerialPortManager() { if (_self != null) { throw new Exception("already substantiated"); } _self = this; nint masterPort; if (NM.IOMasterPort(0, out masterPort) != NM.IOReturn.Success || masterPort == 0) { throw new Exception("could not get master port"); } var matchingDict = NM.IOServiceMatching(NM.kIOSerialBSDServiceValue); NM.CFRetain(matchingDict); var typeKey = NM.CFStringCreateWithCharacters(NM.kIOSerialBSDTypeKey); var allTypes = NM.CFStringCreateWithCharacters(NM.kIOSerialBSDAllTypes); NM.CFDictionarySetValue(matchingDict, typeKey, allTypes); NM.CFRelease(typeKey); NM.CFRelease(allTypes); var notificationPortRef = NM.IONotificationPortCreate(masterPort); if(notificationPortRef == IntPtr.Zero) { throw new Exception("could not create notification port"); } var runLoopSource = NM.IONotificationPortGetRunLoopSource(notificationPortRef); NM.CFRunLoopAddSource(NM.CFRunLoopGetCurrent(), runLoopSource, NM.kCFRunLoopDefaultMode); var result = NM.IOServiceAddMatchingNotification (notificationPortRef, NM.kIOPublishNotification, matchingDict, publishedNotificationCallback, IntPtr.Zero, out _portPublishedIterator); if (result != NM.IOReturn.Success) { if (_portPublishedIterator != 0) NM.IOObjectRelease(_portPublishedIterator); NM.CFRelease(matchingDict); throw new Exception("could not create notification matching service"); } var device = NM.IOIteratorNext(_portPublishedIterator); while (device != 0) { NM.IOObjectRelease(device); device = NM.IOIteratorNext(_portPublishedIterator); } result = NM.IOServiceAddMatchingNotification (notificationPortRef, NM.kIOTerminatedNotification, matchingDict, terminatedNotificationCallback, IntPtr.Zero, out _portTerminatedIterator); if (result != NM.IOReturn.Success) { if (_portTerminatedIterator != 0) NM.IOObjectRelease(_portTerminatedIterator); throw new Exception("could not create notification matching service"); } device = NM.IOIteratorNext(_portTerminatedIterator); while (device != 0) { NM.IOObjectRelease(device); device = NM.IOIteratorNext(_portTerminatedIterator); } } private static void publishedNotificationCallback(IntPtr context, nint iterator) { var device = NM.IOIteratorNext(iterator); while (device != 0) { // 处理设备逻辑 NM.IOObjectRelease(device); device = NM.IOIteratorNext(iterator); } _self.refreshSerialDevices(); } private static void terminatedNotificationCallback(IntPtr context, nint iterator) { var device = NM.IOIteratorNext(iterator); while (device != 0) { // 处理设备逻辑 NM.IOObjectRelease(device); device = NM.IOIteratorNext(iterator); } _self.refreshSerialDevices(); } private void refreshSerialDevices() { // 刷新串口列表逻辑 } }
原NativeMethods类(问题点标注)
public static class NativeMethods { const string CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; const string IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit"; // 错误:这些常量需为CFString的IntPtr,而非C#字符串 public static readonly string kCFRunLoopDefaultMode = "kCFRunLoopDefaultMode"; public static readonly string kIOSerialBSDServiceValue = "IOSerialBSDClient"; public static readonly string kIOSerialBSDTypeKey = "IOSerialBSDClientType"; public static readonly string kIOSerialBSDAllTypes = "IOSerialStream"; public static readonly string kIOPublishNotification = "IOServicePublish"; public static readonly string kIOTerminatedNotification = "IOServiceTerminate"; // 错误:未指定调用约定,IOKit回调用Cdecl,默认StdCall会导致调用失败 public delegate void IOServiceMatchingCallback(IntPtr context, int iterator); public enum IOReturn { Success = 0, ExclusiveAccess = -536870203, NotSupported = -536870201, Offline = -536870185, NotPermitted = -536870174 } [DllImport(CoreFoundation, EntryPoint = "CFDictionarySetValue")] public static extern void CFDictionarySetValue(IntPtr dict, IntPtr key, IntPtr value); [DllImport(CoreFoundation, CharSet = CharSet.Unicode, EntryPoint = "CFStringCreateWithCharacters")] public static extern IntPtr CFStringCreateWithCharacters(IntPtr allocator, char[] buffer, IntPtr length); public static IntPtr CFStringCreateWithCharacters(string str) { return CFStringCreateWithCharacters(IntPtr.Zero, str.ToCharArray(), (IntPtr)str.Length); } [DllImport(CoreFoundation, EntryPoint = "CFRunLoopGetCurrent")] public static extern IntPtr CFRunLoopGetCurrent(); [DllImport(CoreFoundation, EntryPoint = "CFRunLoopAddSource")] public static extern void CFRunLoopAddSource(IntPtr runLoop, IntPtr source, IntPtr mode); [DllImport(CoreFoundation, EntryPoint = "CFRelease")] public static extern void CFRelease(IntPtr obj); [DllImport(CoreFoundation, EntryPoint = "CFRetain")] public static extern void CFRetain(IntPtr obj); [DllImport(IOKit, EntryPoint = "IOMasterPort")] // 错误:masterPort需为nint,macOS是64位,int会截断句柄 public static extern IOReturn IOMasterPort(int bootstrapPort, out int masterPort); [DllImport(IOKit, EntryPoint = "IONotificationPortCreate")] // 错误:masterPort需为nint public static extern IntPtr IONotificationPortCreate(int masterPort); [DllImport(IOKit, EntryPoint = "IONotificationPortGetRunLoopSource")] public static extern IntPtr IONotificationPortGetRunLoopSource(IntPtr notifyPort); [DllImport(IOKit, EntryPoint = "IOIteratorNext")] // 错误:iterator和返回值需为nint,io_iterator_t是64位类型 public static extern int IOIteratorNext(int iterator); [DllImport(IOKit, EntryPoint = "IOObjectRelease")] // 错误:object需为nint public static extern IOReturn IOObjectRelease(int @object); [DllImport(IOKit, EntryPoint = "IOServiceAddMatchingNotification")] // 错误:notificationType需为IntPtr(CFStringRef),iterator参数类型错误 public static extern IOReturn IOServiceAddMatchingNotification(IntPtr notifyPort, string notificationType, IntPtr matching, IOServiceMatchingCallback callback, IntPtr context, out int iterator); [DllImport(IOKit, EntryPoint = "IOServiceMatching")] public static extern IntPtr IOServiceMatching(string name); }
关键修复点
回调委托调用约定:给
IOServiceMatchingCallback添加[UnmanagedFunctionPointer(CallingConvention.Cdecl)]属性,IOKit的C回调使用Cdecl调用约定,默认StdCall会导致调用栈异常,回调无法触发。64位类型匹配:macOS是64位系统,
io_iterator_t、io_object_t、mach_port_t都是64位整数,C#里要用nint替代int,避免句柄截断失效。CFString参数修正:
IOServiceAddMatchingNotification的第二个参数是CFStringRef,不能直接传C#字符串,需将常量预转为CFString的IntPtr,并在程序退出时释放:
public static readonly IntPtr kCFRunLoopDefaultMode = CFStringCreateWithCharacters("kCFRunLoopDefaultMode"); public static readonly IntPtr kIOPublishNotification = CFStringCreateWithCharacters("IOServicePublish"); public static readonly IntPtr kIOTerminatedNotification = CFStringCreateWithCharacters("IOServiceTerminate"); // 程序退出时释放资源 public static void Cleanup() { CFRelease(kCFRunLoopDefaultMode); CFRelease(kIOPublishNotification); CFRelease(kIOTerminatedNotification); // 其他CFString常量同理释放 }
- 内存泄漏修复:创建CFString后必须调用
CFRelease释放,比如原代码中CFDictionarySetValue、CFRunLoopAddSource时创建的临时CFString都需要释放。
修改后的NativeMethods核心部分
public static class NativeMethods { const string CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; const string IOKit = "/System/Library/Frameworks/IOKit.framework/IOKit"; public static readonly IntPtr kCFRunLoopDefaultMode = CFStringCreateWithCharacters("kCFRunLoopDefaultMode"); public static readonly IntPtr kIOSerialBSDServiceValue = CFStringCreateWithCharacters("IOSerialBSDClient"); public static readonly IntPtr kIOSerialBSDTypeKey = CFStringCreateWithCharacters("IOSerialBSDClientType"); public static readonly IntPtr kIOSerialBSDAllTypes = CFStringCreateWithCharacters("IOSerialStream"); public static readonly IntPtr kIOPublishNotification = CFStringCreateWithCharacters("IOServicePublish"); public static readonly IntPtr kIOTerminatedNotification = CFStringCreateWithCharacters("IOServiceTerminate"); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void IOServiceMatchingCallback(IntPtr context, nint iterator); public enum IOReturn { Success = 0, ExclusiveAccess = -536870203, NotSupported = -536870201, Offline = -536870185, NotPermitted = -536870174 } [DllImport(IOKit, EntryPoint = "IOMasterPort")] public static extern IOReturn IOMasterPort(nint bootstrapPort, out nint masterPort); [DllImport(IOKit, EntryPoint = "IONotificationPortCreate")] public static extern IntPtr IONotificationPortCreate(nint masterPort); [DllImport(IOKit, EntryPoint = "IOIteratorNext")] public static extern nint IOIteratorNext(nint iterator); [DllImport(IOKit, EntryPoint = "IOObjectRelease")] public static extern IOReturn IOObjectRelease(nint @object); [DllImport(IOKit, EntryPoint = "IOServiceAddMatchingNotification")] public static extern IOReturn IOServiceAddMatchingNotification(IntPtr notifyPort, IntPtr notificationType, IntPtr matching, IOServiceMatchingCallback callback, IntPtr context, out nint iterator); // 其他方法保持类型修正后的版本... public static void Cleanup() { CFRelease(kCFRunLoopDefaultMode); CFRelease(kIOSerialBSDServiceValue); CFRelease(kIOSerialBSDTypeKey); CFRelease(kIOSerialBSDAllTypes); CFRelease(kIOPublishNotification); CFRelease(kIOTerminatedNotification); } }
以上修改解决了类型不匹配、调用约定错误、CFString参数传递错误等核心问题,应该能触发串口插拔的回调通知。
内容的提问来源于stack exchange,提问作者sonic1015
相关产品推荐
相关产品推荐

