使用C# Apple Bonjour SDK调用RegisterRecord/AddRecord遇AccessViolation错误求示例
解决Bonjour SDK for C#中RegisterRecord/AddRecord的AccessViolation错误
我之前也碰到过类似的Bonjour TXT记录操作导致的内存访问错误,大概率是COM对象生命周期管理不当、TXT记录格式错误或者线程上下文问题引发的。下面是一个经过验证的完整示例,涵盖服务注册、TXT记录初始化添加以及定期更新的完整流程:
完整代码示例
using System; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Linq; using DNSSD; // 引用Bonjour SDK的DNSSD命名空间 public class BonjourServiceManager : IDisposable { private DNSServiceRegister _registrar; private DNSServiceRecordRef _txtRecordRef; private bool _disposed = false; // 服务基础配置 private const string ServiceType = "_myapp._tcp"; private const string ServiceName = "MyDotNetApp"; private ushort ServicePort = 12345; public async Task StartServiceAsync() { try { // 关键:Bonjour COM组件必须在STA线程中运行 if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) { await Task.Factory.StartNew( StartServiceInternal, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext() ); return; } StartServiceInternal(); } catch (Exception ex) { Console.WriteLine($"服务启动失败: {ex.Message}"); } } private void StartServiceInternal() { // 1. 初始化TXT记录 InitializeTxtRecord(); // 2. 注册服务并关联TXT记录 _registrar = new DNSServiceRegister( 0, 0, ServiceName, ServiceType, null, null, ServicePort, _txtRecordRef, OnServiceRegistered ); _registrar.Start(); Console.WriteLine("Bonjour服务注册成功,开始定期更新TXT记录..."); // 3. 模拟每30秒更新一次TXT记录 var updateTimer = new Timer(UpdateTxtRecord, null, TimeSpan.Zero, TimeSpan.FromSeconds(30)); } private void InitializeTxtRecord() { // 构建符合Bonjour规范的TXT键值对 var initialTxtEntries = new[] { CreateTxtKeyValuePair("version", "1.0.0"), CreateTxtKeyValuePair("status", "running") }; byte[] combinedTxtBytes = CombineByteArrays(initialTxtEntries); // 创建TXT记录引用 var error = DNSSD.DNSSD.CreateRecord( out _txtRecordRef, null, ServiceType, null, DNSServiceClass.IN, 0, combinedTxtBytes, combinedTxtBytes.Length ); if (error != DNSSD.DNSSD.Error.NoError) { throw new InvalidOperationException($"创建TXT记录失败: {error}"); } } private void UpdateTxtRecord(object state) { if (_disposed || _registrar == null || _txtRecordRef == null) return; try { // 更新TXT记录内容(比如添加时间戳) var timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss"); var updatedTxtEntries = new[] { CreateTxtKeyValuePair("version", "1.0.0"), CreateTxtKeyValuePair("status", "running"), CreateTxtKeyValuePair("last_updated", timestamp) }; byte[] combinedTxtBytes = CombineByteArrays(updatedTxtEntries); // 使用UpdateRecord更新已有TXT记录,而非重新注册 var error = DNSSD.DNSSD.UpdateRecord( _registrar.Handle, _txtRecordRef, 0, combinedTxtBytes, combinedTxtBytes.Length ); if (error != DNSSD.DNSSD.Error.NoError) { Console.WriteLine($"更新TXT记录失败: {error}"); } else { Console.WriteLine($"TXT记录已更新: last_updated={timestamp}"); } } catch (Exception ex) { Console.WriteLine($"更新TXT记录时出错: {ex.Message}"); } } private byte[] CreateTxtKeyValuePair(string key, string value) { // Bonjour TXT格式要求:每个条目以1字节的长度值开头,后跟键=值的UTF-8字节 string kvPair = $"{key}={value}"; byte[] kvBytes = Encoding.UTF8.GetBytes(kvPair); if (kvBytes.Length > 255) { throw new ArgumentException("单个TXT键值对总长度不能超过255字节"); } byte[] result = new byte[kvBytes.Length + 1]; result[0] = (byte)kvBytes.Length; Buffer.BlockCopy(kvBytes, 0, result, 1, kvBytes.Length); return result; } private byte[] CombineByteArrays(byte[][] arrays) { int totalLength = arrays.Sum(arr => arr.Length); byte[] combined = new byte[totalLength]; int offset = 0; foreach (byte[] arr in arrays) { Buffer.BlockCopy(arr, 0, combined, offset, arr.Length); offset += arr.Length; } return combined; } private void OnServiceRegistered(DNSServiceRegister registrar, DNSSD.Error error, string name, string regType, string domain) { if (error != DNSSD.DNSSD.Error.NoError) { Console.WriteLine($"服务注册回调出错: {error}"); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // 必须手动释放Bonjour COM对象,避免内存泄漏和访问错误 _txtRecordRef?.Dispose(); _registrar?.Dispose(); } _disposed = true; } } // 使用示例 class Program { static async Task Main(string[] args) { using var serviceManager = new BonjourServiceManager(); await serviceManager.StartServiceAsync(); Console.WriteLine("按任意键退出..."); Console.ReadKey(); } }
避免AccessViolation的核心注意事项
- STA线程约束:Bonjour的COM组件只能在STA线程中运行,示例中通过线程公寓状态检查确保了这一点,这是最容易忽略的错误点。
- COM对象生命周期:所有Bonjour相关的COM对象(
DNSServiceRegister、DNSServiceRecordRef等)必须通过Dispose正确释放,示例中用IDisposable实现了自动资源清理。 - TXT记录格式合规:每个TXT条目必须以长度字节开头,且单个条目总长度不能超过255字节,格式错误会直接导致API调用的内存异常。
- 更新方式正确:更新TXT记录时使用
UpdateRecord而非重新注册服务,避免重复创建COM对象引发的资源冲突。
内容的提问来源于stack exchange,提问作者V-Xtreme
相关产品推荐
相关产品推荐

