File: DiaSymReader\SymUnmanagedFactory.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.DiaSymReader
{
    internal static class SymUnmanagedFactory
    {
        private const string AlternateLoadPathEnvironmentVariableName = "MICROSOFT_DIASYMREADER_NATIVE_ALT_LOAD_PATH";
 
        private const string LegacyDiaSymReaderModuleName = "diasymreader.dll";
        private const string DiaSymReaderModuleName32 = "Microsoft.DiaSymReader.Native.x86.dll";
        private const string DiaSymReaderModuleNameAmd64 = "Microsoft.DiaSymReader.Native.amd64.dll";
        private const string DiaSymReaderModuleNameArm64 = "Microsoft.DiaSymReader.Native.arm64.dll";
 
        private const string CreateSymReaderFactoryName = "CreateSymReader";
        private const string CreateSymWriterFactoryName = "CreateSymWriter";
 
        // CorSymWriter_SxS from corsym.idl
        private const string SymWriterClsid = "0AE2DEB0-F901-478b-BB9F-881EE8066788";
 
        // CorSymReader_SxS from corsym.idl
        private const string SymReaderClsid = "0A3976C5-4529-4ef8-B0B0-42EED37082CD";
 
        private static Type s_lazySymReaderComType, s_lazySymWriterComType;
 
        internal static string DiaSymReaderModuleName
            => RuntimeInformation.ProcessArchitecture switch
            {
                Architecture.X86 => DiaSymReaderModuleName32,
                Architecture.X64 => DiaSymReaderModuleNameAmd64,
                Architecture.Arm64 => DiaSymReaderModuleNameArm64,
                _ => throw new NotSupportedException()
            };
 
        [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
        [DllImport(DiaSymReaderModuleName32, EntryPoint = CreateSymReaderFactoryName)]
        private static extern void CreateSymReader32(ref Guid id, [MarshalAs(UnmanagedType.IUnknown)] out object symReader);
 
        [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
        [DllImport(DiaSymReaderModuleNameAmd64, EntryPoint = CreateSymReaderFactoryName)]
        private static extern void CreateSymReaderAmd64(ref Guid id, [MarshalAs(UnmanagedType.IUnknown)] out object symReader);
 
        [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
        [DllImport(DiaSymReaderModuleNameArm64, EntryPoint = CreateSymReaderFactoryName)]
        private static extern void CreateSymReaderArm64(ref Guid id, [MarshalAs(UnmanagedType.IUnknown)] out object symReader);
 
        [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
        [DllImport(DiaSymReaderModuleName32, EntryPoint = CreateSymWriterFactoryName)]
        private static extern void CreateSymWriter32(ref Guid id, [MarshalAs(UnmanagedType.IUnknown)] out object symWriter);
 
        [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
        [DllImport(DiaSymReaderModuleNameAmd64, EntryPoint = CreateSymWriterFactoryName)]
        private static extern void CreateSymWriterAmd64(ref Guid id, [MarshalAs(UnmanagedType.IUnknown)] out object symWriter);
 
        [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
        [DllImport(DiaSymReaderModuleNameArm64, EntryPoint = CreateSymWriterFactoryName)]
        private static extern void CreateSymWriterArm64(ref Guid id, [MarshalAs(UnmanagedType.IUnknown)] out object symWriter);
 
        [DllImport("kernel32")]
        private static extern IntPtr LoadLibrary(string path);
 
        [DllImport("kernel32")]
        private static extern bool FreeLibrary(IntPtr hModule);
 
        [DllImport("kernel32")]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
 
        private delegate void NativeFactory(ref Guid id, [MarshalAs(UnmanagedType.IUnknown)] out object instance);
 
        private static readonly Lazy<Func<string, string>> s_lazyGetEnvironmentVariable = new Lazy<Func<string, string>>(() =>
        {
            try
            {
                foreach (var method in typeof(Environment).GetTypeInfo().GetDeclaredMethods("GetEnvironmentVariable"))
                {
                    var parameters = method.GetParameters();
                    if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string))
                    {
                        return (Func<string, string>)method.CreateDelegate(typeof(Func<string, string>));
                    }
                }
            }
            catch
            {
            }
 
            return null;
        });
 
        // internal for testing
        internal static string GetEnvironmentVariable(string name)
        {
            try
            {
                return s_lazyGetEnvironmentVariable.Value?.Invoke(name);
            }
            catch
            {
                return null;
            }
        }
 
        private static object TryLoadFromAlternativePath(Guid clsid, string factoryName)
        {
            var dir = GetEnvironmentVariable(AlternateLoadPathEnvironmentVariableName);
            if (string.IsNullOrEmpty(dir))
            {
                return null;
            }
 
            var moduleHandle = LoadLibrary(Path.Combine(dir, DiaSymReaderModuleName));
            if (moduleHandle == IntPtr.Zero)
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
 
            object instance = null;
            try
            {
                var createAddress = GetProcAddress(moduleHandle, factoryName);
                if (createAddress == IntPtr.Zero)
                {
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                }
 
#if NETSTANDARD1_1
                var creator = (NativeFactory)Marshal.GetDelegateForFunctionPointer(createAddress, typeof(NativeFactory));
#else
                var creator = Marshal.GetDelegateForFunctionPointer<NativeFactory>(createAddress);
#endif
                creator(ref clsid, out instance);
            }
            finally
            {
                if (instance == null && !FreeLibrary(moduleHandle))
                {
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                }
            }
 
            return instance;
        }
 
        private static Type GetComTypeType(ref Type lazyType, Guid clsid)
        {
            if (lazyType == null)
            {
#if NET6_0_OR_GREATER
                System.Diagnostics.Debug.Assert(OperatingSystem.IsWindows());
#endif
                lazyType = Marshal.GetTypeFromCLSID(clsid);
            }
 
            return lazyType;
        }
 
        internal static object CreateObject(bool createReader, bool useAlternativeLoadPath, bool useComRegistry, out string moduleName, out Exception loadException)
        {
            object instance = null;
            loadException = null;
            moduleName = null;
 
            var clsid = new Guid(createReader ? SymReaderClsid : SymWriterClsid);
 
            try
            {
                try
                {
                    switch (RuntimeInformation.ProcessArchitecture, createReader)
                    {
                        case (Architecture.X86, true):
                            CreateSymReader32(ref clsid, out instance);
                            break;
                        case (Architecture.X86, false):
                            CreateSymWriter32(ref clsid, out instance);
                            break;
                        case (Architecture.X64, true):
                            CreateSymReaderAmd64(ref clsid, out instance);
                            break;
                        case (Architecture.X64, false):
                            CreateSymWriterAmd64(ref clsid, out instance);
                            break;
                        case (Architecture.Arm64, true):
                            CreateSymReaderArm64(ref clsid, out instance);
                            break;
                        case (Architecture.Arm64, false):
                            CreateSymWriterArm64(ref clsid, out instance);
                            break;
                        default:
                            throw new NotSupportedException();
                    }
                }
                catch (DllNotFoundException e) when (useAlternativeLoadPath)
                {
                    instance = TryLoadFromAlternativePath(clsid, createReader ? CreateSymReaderFactoryName : CreateSymWriterFactoryName);
                    if (instance == null)
                    {
                        loadException = e;
                    }
                }
            }
            catch (Exception e)
            {
                loadException = e;
                instance = null;
            }
 
            if (instance != null)
            {
                moduleName = DiaSymReaderModuleName;
            }
            else if (useComRegistry)
            {
                // Try to find a registered CLR implementation
                try
                {
                    var comType = createReader ?
                        GetComTypeType(ref s_lazySymReaderComType, clsid) :
                        GetComTypeType(ref s_lazySymWriterComType, clsid);
 
                    instance = Activator.CreateInstance(comType);
                    moduleName = LegacyDiaSymReaderModuleName;
                }
                catch (Exception e)
                {
                    loadException = e;
                    instance = null;
                }
            }
 
            return instance;
        }
 
    }
}