File: ClrDataAppDomain.cs
Web Access
Project: src\src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Legacy\Microsoft.Diagnostics.DataContractReader.Legacy.csproj (Microsoft.Diagnostics.DataContractReader.Legacy)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using Microsoft.Diagnostics.DataContractReader.Contracts;

namespace Microsoft.Diagnostics.DataContractReader.Legacy;

[GeneratedComClass]
public sealed unsafe partial class ClrDataAppDomain : IXCLRDataAppDomain
{
    private readonly Target _target;
    private readonly TargetPointer _appDomain;
    private readonly IXCLRDataAppDomain? _legacyImpl;

    public TargetPointer Address => _appDomain;

    public ClrDataAppDomain(Target target, TargetPointer appDomain, IXCLRDataAppDomain? legacyImpl)
    {
        _target = target;
        _appDomain = appDomain;
        _legacyImpl = legacyImpl;
    }

    int IXCLRDataAppDomain.GetProcess(DacComNullableByRef<IXCLRDataProcess> process)
        => LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetProcess(process) : HResults.E_NOTIMPL;

    int IXCLRDataAppDomain.GetName(uint bufLen, uint* nameLen, char* name)
    {
        int hr = HResults.S_OK;
        string friendlyName;
        try
        {
            ILoader loader = _target.Contracts.Loader;
            friendlyName = loader.GetAppDomainFriendlyName();
        }
        catch (VirtualReadException)
        {
            // Match native DAC / SOSDacImpl behavior: fall back to empty string
            // when the FriendlyName pointer targets unreadable memory.
            friendlyName = string.Empty;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
            friendlyName = string.Empty;
        }

        if (hr >= 0)
        {
            OutputBufferHelpers.CopyStringToBuffer(name, bufLen, nameLen, friendlyName);

            // Match native DAC behavior: return S_FALSE when output is truncated.
            uint requiredLen = (uint)friendlyName.Length + 1;
            if (name is not null && bufLen > 0 && bufLen < requiredLen)
                hr = HResults.S_FALSE;
        }

#if DEBUG
        if (_legacyImpl is not null)
        {
            uint nameLenLocal;
            char[] legacyNameBuf = new char[bufLen > 0 ? bufLen : 1];
            int hrLocal;
            fixed (char* pLegacyName = legacyNameBuf)
            {
                hrLocal = _legacyImpl.GetName(bufLen, &nameLenLocal, name is not null ? pLegacyName : null);
            }

            Debug.ValidateHResult(hr, hrLocal);
            if (hr >= 0)
            {
                if (nameLen is not null)
                    Debug.Assert(*nameLen == nameLenLocal, $"cDAC: {*nameLen}, DAC: {nameLenLocal}");

                if (name is not null && bufLen > 0)
                {
                    // On truncation (S_FALSE), nameLenLocal is the full required length
                    // which may exceed bufLen. Cap to the actual buffer size.
                    int compareLen = (int)Math.Min(nameLenLocal, bufLen) - 1;
                    if (compareLen > 0)
                    {
                        string dacName = new string(legacyNameBuf, 0, compareLen);
                        string cdacName = new string(name, 0, compareLen);
                        Debug.Assert(dacName == cdacName, $"cDAC: {cdacName}, DAC: {dacName}");
                    }
                }
            }
        }
#endif

        return hr;
    }

    int IXCLRDataAppDomain.GetUniqueID(ulong* id)
    {
        int hr = HResults.S_OK;
        try
        {
            if (id is null)
                throw new ArgumentNullException(nameof(id));

            *id = _target.ReadGlobal<uint>(Constants.Globals.DefaultADID);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacyImpl is not null && hr >= 0)
        {
            ulong idLocal;
            int hrLocal = _legacyImpl.GetUniqueID(&idLocal);
            Debug.ValidateHResult(hr, hrLocal);
            Debug.Assert(*id == idLocal, $"cDAC: {*id}, DAC: {idLocal}");
        }
#endif

        return hr;
    }

    int IXCLRDataAppDomain.GetFlags(uint* flags)
    {
        int hr = HResults.S_OK;
        try
        {
            if (flags is null)
                throw new ArgumentNullException(nameof(flags));

            // CLRDATA_DOMAIN_DEFAULT = 0
            *flags = 0;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacyImpl is not null && hr >= 0)
        {
            uint flagsLocal;
            int hrLocal = _legacyImpl.GetFlags(&flagsLocal);
            Debug.ValidateHResult(hr, hrLocal);
            Debug.Assert(*flags == flagsLocal, $"cDAC: {*flags}, DAC: {flagsLocal}");
        }
#endif

        return hr;
    }

    int IXCLRDataAppDomain.IsSameObject(IXCLRDataAppDomain* appDomain)
    {
        int hr = HResults.S_FALSE;
        try
        {
            if (System.Runtime.InteropServices.ComWrappers.TryGetObject((nint)appDomain, out object? obj)
                && obj is ClrDataAppDomain other)
            {
                hr = _appDomain == other._appDomain ? HResults.S_OK : HResults.S_FALSE;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

#if DEBUG
        if (_legacyImpl is not null)
        {
            int hrLocal = _legacyImpl.IsSameObject(appDomain);
            Debug.Assert(hrLocal == hr, $"cDAC: {hr}, DAC: {hrLocal}");
        }
#endif

        return hr;
    }

    int IXCLRDataAppDomain.GetManagedObject(DacComNullableByRef<IXCLRDataValue> value)
        => LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetManagedObject(value) : HResults.E_NOTIMPL;

    int IXCLRDataAppDomain.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer)
        => LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer) : HResults.E_NOTIMPL;
}