You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:36:07