// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; #if !CORECLR namespace Microsoft.CodeAnalysis { internal sealed class FusionAssemblyIdentity { [Flags] internal enum ASM_DISPLAYF { VERSION = 0x01, CULTURE = 0x02, PUBLIC_KEY_TOKEN = 0x04, PUBLIC_KEY = 0x08, CUSTOM = 0x10, PROCESSORARCHITECTURE = 0x20, LANGUAGEID = 0x40, RETARGET = 0x80, CONFIG_MASK = 0x100, MVID = 0x200, CONTENT_TYPE = 0x400, FULL = VERSION | CULTURE | PUBLIC_KEY_TOKEN | RETARGET | PROCESSORARCHITECTURE | CONTENT_TYPE } internal enum PropertyId { PUBLIC_KEY = 0, // 0 PUBLIC_KEY_TOKEN, // 1 HASH_VALUE, // 2 NAME, // 3 MAJOR_VERSION, // 4 MINOR_VERSION, // 5 BUILD_NUMBER, // 6 REVISION_NUMBER, // 7 CULTURE, // 8 PROCESSOR_ID_ARRAY, // 9 OSINFO_ARRAY, // 10 HASH_ALGID, // 11 ALIAS, // 12 CODEBASE_URL, // 13 CODEBASE_LASTMOD, // 14 NULL_PUBLIC_KEY, // 15 NULL_PUBLIC_KEY_TOKEN, // 16 CUSTOM, // 17 NULL_CUSTOM, // 18 MVID, // 19 FILE_MAJOR_VERSION, // 20 FILE_MINOR_VERSION, // 21 FILE_BUILD_NUMBER, // 22 FILE_REVISION_NUMBER, // 23 RETARGET, // 24 SIGNATURE_BLOB, // 25 CONFIG_MASK, // 26 ARCHITECTURE, // 27 CONTENT_TYPE, // 28 MAX_PARAMS // 29 } private static class CANOF { public const uint PARSE_DISPLAY_NAME = 0x1; public const uint SET_DEFAULT_VALUES = 0x2; } [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("CD193BC0-B4BC-11d2-9833-00C04FC31D2E")] internal unsafe interface IAssemblyName { void SetProperty(PropertyId id, void* data, uint size); [PreserveSig] int GetProperty(PropertyId id, void* data, ref uint size); [PreserveSig] int Finalize(); [PreserveSig] int GetDisplayName(byte* buffer, ref uint characterCount, ASM_DISPLAYF dwDisplayFlags); [PreserveSig] int __BindToObject(/*...*/); [PreserveSig] int __GetName(/*...*/); [PreserveSig] int GetVersion(out uint versionHi, out uint versionLow); [PreserveSig] int IsEqual(IAssemblyName pName, uint dwCmpFlags); [PreserveSig] int Clone(out IAssemblyName pName); } [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("7c23ff90-33af-11d3-95da-00a024a85b51")] internal interface IApplicationContext { } // NOTE: The CLR caches assembly identities, but doesn't do so in a threadsafe manner. // Wrap all calls to this with a lock. private static object s_assemblyIdentityGate = new object(); private static int CreateAssemblyNameObject(out IAssemblyName ppEnum, string szAssemblyName, uint dwFlags, IntPtr pvReserved) { lock (s_assemblyIdentityGate) { return RealCreateAssemblyNameObject(out ppEnum, szAssemblyName, dwFlags, pvReserved); } } [DllImport("clr", EntryPoint = "CreateAssemblyNameObject", CharSet = CharSet.Unicode, PreserveSig = true)] private static extern int RealCreateAssemblyNameObject(out IAssemblyName ppEnum, [MarshalAs(UnmanagedType.LPWStr)]string szAssemblyName, uint dwFlags, IntPtr pvReserved); private const int ERROR_INSUFFICIENT_BUFFER = unchecked((int)0x8007007A); private const int FUSION_E_INVALID_NAME = unchecked((int)0x80131047); internal static unsafe string GetDisplayName(IAssemblyName nameObject, ASM_DISPLAYF displayFlags) { int hr; uint characterCountIncludingTerminator = 0; hr = nameObject.GetDisplayName(null, ref characterCountIncludingTerminator, displayFlags); if (hr == 0) { return String.Empty; } if (hr != ERROR_INSUFFICIENT_BUFFER) { throw Marshal.GetExceptionForHR(hr); } byte[] data = new byte[(int)characterCountIncludingTerminator * 2]; fixed (byte* p = data) { hr = nameObject.GetDisplayName(p, ref characterCountIncludingTerminator, displayFlags); if (hr != 0) { throw Marshal.GetExceptionForHR(hr); } return Marshal.PtrToStringUni((IntPtr)p, (int)characterCountIncludingTerminator - 1); } } internal static unsafe byte[] GetPropertyBytes(IAssemblyName nameObject, PropertyId propertyId) { int hr; uint size = 0; hr = nameObject.GetProperty(propertyId, null, ref size); if (hr == 0) { return null; } if (hr != ERROR_INSUFFICIENT_BUFFER) { throw Marshal.GetExceptionForHR(hr); } byte[] data = new byte[(int)size]; fixed (byte* p = data) { hr = nameObject.GetProperty(propertyId, p, ref size); if (hr != 0) { throw Marshal.GetExceptionForHR(hr); } } return data; } internal static unsafe string GetPropertyString(IAssemblyName nameObject, PropertyId propertyId) { byte[] data = GetPropertyBytes(nameObject, propertyId); if (data == null) { return null; } fixed (byte* p = data) { return Marshal.PtrToStringUni((IntPtr)p, (data.Length / 2) - 1); } } internal static unsafe Version GetVersion(IAssemblyName nameObject) { uint hi, lo; int hr = nameObject.GetVersion(out hi, out lo); if (hr != 0) { Debug.Assert(hr == FUSION_E_INVALID_NAME); return null; } return new Version((int)(hi >> 16), (int)(hi & 0xffff), (int)(lo >> 16), (int)(lo & 0xffff)); } internal static unsafe uint? GetPropertyWord(IAssemblyName nameObject, PropertyId propertyId) { uint result; uint size = sizeof(uint); int hr = nameObject.GetProperty(propertyId, &result, ref size); if (hr != 0) { throw Marshal.GetExceptionForHR(hr); } if (size == 0) { return null; } return result; } internal static string GetCulture(IAssemblyName nameObject) { return GetPropertyString(nameObject, PropertyId.CULTURE); } internal static ProcessorArchitecture GetProcessorArchitecture(IAssemblyName nameObject) { return (ProcessorArchitecture)(GetPropertyWord(nameObject, PropertyId.ARCHITECTURE) ?? 0); } /// /// Creates object by parsing given display name. /// internal static IAssemblyName ToAssemblyNameObject(string displayName) { // CLR doesn't handle \0 in the display name well: if (displayName.IndexOf('\0') >= 0) { return null; } Debug.Assert(displayName != null); IAssemblyName result; int hr = CreateAssemblyNameObject(out result, displayName, CANOF.PARSE_DISPLAY_NAME, IntPtr.Zero); if (hr != 0) { return null; } Debug.Assert(result != null); return result; } /// /// Selects the candidate assembly with the largest version number. Uses culture as a tie-breaker if it is provided. /// All candidates are assumed to have the same name and must include versions and cultures. /// internal static IAssemblyName GetBestMatch(IEnumerable candidates, string preferredCultureOpt) { IAssemblyName bestCandidate = null; Version bestVersion = null; string bestCulture = null; foreach (var candidate in candidates) { if (bestCandidate != null) { Version candidateVersion = GetVersion(candidate); Debug.Assert(candidateVersion != null); if (bestVersion == null) { bestVersion = GetVersion(bestCandidate); Debug.Assert(bestVersion != null); } int cmp = bestVersion.CompareTo(candidateVersion); if (cmp == 0) { if (preferredCultureOpt != null) { string candidateCulture = GetCulture(candidate); Debug.Assert(candidateCulture != null); if (bestCulture == null) { bestCulture = GetCulture(candidate); Debug.Assert(bestCulture != null); } // we have exactly the preferred culture or // we have neutral culture and the best candidate's culture isn't the preferred one: if (StringComparer.OrdinalIgnoreCase.Equals(candidateCulture, preferredCultureOpt) || candidateCulture.Length == 0 && !StringComparer.OrdinalIgnoreCase.Equals(bestCulture, preferredCultureOpt)) { bestCandidate = candidate; bestVersion = candidateVersion; bestCulture = candidateCulture; } } } else if (cmp < 0) { bestCandidate = candidate; bestVersion = candidateVersion; } } else { bestCandidate = candidate; } } return bestCandidate; } } } #endif // !CORECLR