File: Microsoft\CSharp\RuntimeBinder\ComInterop\ExcepInfo.cs
Web Access
Project: src\src\runtime\src\libraries\Microsoft.CSharp\src\Microsoft.CSharp.csproj (Microsoft.CSharp)
// 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.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using ComTypes = System.Runtime.InteropServices.ComTypes;

namespace Microsoft.CSharp.RuntimeBinder.ComInterop
{
    /// <summary>
    /// This is similar to ComTypes.EXCEPINFO, but lets us do our own custom marshaling
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    internal struct ExcepInfo
    {
        private short wCode;
        private short wReserved;
        private IntPtr bstrSource;
        private IntPtr bstrDescription;
        private IntPtr bstrHelpFile;
        private int dwHelpContext;
        private IntPtr pvReserved;
        private IntPtr pfnDeferredFillIn;
        private int scode;

#if DEBUG
        static ExcepInfo()
        {
            Debug.Assert(Marshal.SizeOf<ExcepInfo>() == Marshal.SizeOf<ComTypes.EXCEPINFO>());
        }
#endif

        private static string? ConvertAndFreeBstr(ref IntPtr bstr)
        {
            if (bstr == IntPtr.Zero)
            {
                return null;
            }

            string result = Marshal.PtrToStringBSTR(bstr);
            Marshal.FreeBSTR(bstr);
            bstr = IntPtr.Zero;
            return result;
        }

        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        internal Exception GetException()
        {
            Debug.Assert(pfnDeferredFillIn == IntPtr.Zero);
#if DEBUG
            System.Diagnostics.Debug.Assert(wReserved != -1);
            wReserved = -1; // to ensure that the method gets called only once
#endif

            // If the scode is zero, we use the wCode. The wCode is a legacy of
            // 16-bit Windows error codes. This means it will never be an error
            // scode (HRESULT, < 0) and we will get a null exception.
            int errorCode = (scode != 0) ? scode : wCode;

            // If the error code doesn't resolve to an exception, we create a
            // generic COMException with the error code and no message.
            Exception exception = Marshal.GetExceptionForHR(errorCode) ?? new COMException(null, errorCode);

            string? message = ConvertAndFreeBstr(ref bstrDescription);
            if (message is not null)
            {
                // If we have a custom message, create a new Exception object with the message set correctly.
                // We need to create a new object because "exception.Message" is a read-only property.
                if (exception is COMException)
                {
                    exception = new COMException(message, errorCode);
                }
                else
                {
                    Type exceptionType = exception.GetType();
                    ConstructorInfo ctor = exceptionType.GetConstructor(new Type[] { typeof(string) });
                    if (ctor is not null)
                    {
                        exception = (Exception)ctor.Invoke(new object[] { message });
                    }
                }
            }

            exception.Source = ConvertAndFreeBstr(ref bstrSource);

            string? helpLink = ConvertAndFreeBstr(ref bstrHelpFile);
            if (helpLink is not null && dwHelpContext != 0)
            {
                helpLink += "#" + dwHelpContext;
            }
            exception.HelpLink = helpLink;

            return exception;
        }
    }
}