1 #region Copyright notice and license 2 3 // Copyright 2015 gRPC authors. 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 #endregion 18 19 using System; 20 using System.Collections.Concurrent; 21 using System.Diagnostics; 22 using System.IO; 23 using System.Linq; 24 using System.Reflection; 25 using System.Runtime.InteropServices; 26 using System.Threading; 27 using Grpc.Core.Utils; 28 29 namespace Grpc.Core.Internal 30 { 31 /// <summary> 32 /// Utility methods for detecting platform and architecture. 33 /// </summary> 34 internal static class PlatformApis 35 { 36 const string UnityEngineAssemblyName = "UnityEngine"; 37 38 const string UnityEngineApplicationClassName = "UnityEngine.Application"; 39 40 const string UnityIPhonePlayer = "IPhonePlayer"; 41 const string XamarinAndroidObjectClassName = "Java.Lang.Object, Mono.Android"; 42 const string XamarinIOSObjectClassName = "Foundation.NSObject, Xamarin.iOS"; 43 44 static readonly bool isLinux; 45 static readonly bool isMacOSX; 46 static readonly bool isWindows; 47 static readonly bool isMono; 48 static readonly bool isNet5OrHigher; 49 static readonly bool isNetCore; 50 static readonly string unityApplicationPlatform; 51 static readonly bool isXamarin; 52 static readonly bool isXamarinIOS; 53 static readonly bool isXamarinAndroid; 54 PlatformApis()55 static PlatformApis() 56 { 57 #if NETSTANDARD 58 isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); 59 isMacOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); 60 isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 61 #if NETSTANDARD2_0 62 isNet5OrHigher = Environment.Version.Major >= 5; 63 #else 64 // assume that on .NET 5+, the netstandard2.0 TFM is going to be selected. 65 isNet5OrHigher = false; 66 #endif 67 isNetCore = isNet5OrHigher || RuntimeInformation.FrameworkDescription.StartsWith(".NET Core"); 68 #else 69 var platform = Environment.OSVersion.Platform; 70 71 // PlatformID.MacOSX is never returned, commonly used trick is to identify Mac is by using uname. 72 isMacOSX = (platform == PlatformID.Unix && GetUname() == "Darwin"); 73 isLinux = (platform == PlatformID.Unix && !isMacOSX); 74 isWindows = (platform == PlatformID.Win32NT || platform == PlatformID.Win32S || platform == PlatformID.Win32Windows); 75 isNet5OrHigher = false; 76 isNetCore = false; 77 #endif 78 isMono = Type.GetType("Mono.Runtime") != null; 79 80 // Unity 81 unityApplicationPlatform = TryGetUnityApplicationPlatform(); 82 83 // Xamarin 84 isXamarinIOS = Type.GetType(XamarinIOSObjectClassName) != null; 85 isXamarinAndroid = Type.GetType(XamarinAndroidObjectClassName) != null; 86 isXamarin = isXamarinIOS || isXamarinAndroid; 87 } 88 89 public static bool IsLinux => isLinux; 90 91 public static bool IsMacOSX => isMacOSX; 92 93 public static bool IsWindows => isWindows; 94 95 public static bool IsMono => isMono; 96 97 /// <summary> 98 /// true if running on Unity platform. 99 /// </summary> 100 public static bool IsUnity => unityApplicationPlatform != null; 101 102 /// <summary> 103 /// true if running on Unity iOS, false otherwise. 104 /// </summary> 105 public static bool IsUnityIOS => unityApplicationPlatform == UnityIPhonePlayer; 106 107 /// <summary> 108 /// true if running on a Xamarin platform (either Xamarin.Android or Xamarin.iOS), 109 /// false otherwise. 110 /// </summary> 111 public static bool IsXamarin => isXamarin; 112 113 /// <summary> 114 /// true if running on Xamarin.iOS, false otherwise. 115 /// </summary> 116 public static bool IsXamarinIOS => isXamarinIOS; 117 118 /// <summary> 119 /// true if running on Xamarin.Android, false otherwise. 120 /// </summary> 121 public static bool IsXamarinAndroid => isXamarinAndroid; 122 123 /// <summary> 124 /// true if running on .NET 5+, false otherwise. 125 /// </summary> 126 public static bool IsNet5OrHigher => isNet5OrHigher; 127 128 /// <summary> 129 /// true if running on .NET Core (CoreCLR) or NET 5+, false otherwise. 130 /// </summary> 131 public static bool IsNetCore => isNetCore; 132 133 public static bool Is64Bit => IntPtr.Size == 8; 134 135 /// <summary> 136 /// Returns <c>UnityEngine.Application.platform</c> as a string. 137 /// See https://docs.unity3d.com/ScriptReference/Application-platform.html for possible values. 138 /// Value is obtained via reflection to avoid compile-time dependency on Unity. 139 /// This method should only be called if <c>IsUnity</c> is <c>true</c>. 140 /// </summary> GetUnityApplicationPlatform()141 public static string GetUnityApplicationPlatform() 142 { 143 GrpcPreconditions.CheckState(IsUnity, "Not running on Unity."); 144 return unityApplicationPlatform; 145 } 146 147 /// <summary> 148 /// Returns <c>UnityEngine.Application.platform</c> as a string or <c>null</c> 149 /// if not running on Unity. 150 /// Value is obtained via reflection to avoid compile-time dependency on Unity. 151 /// </summary> TryGetUnityApplicationPlatform()152 static string TryGetUnityApplicationPlatform() 153 { 154 Assembly unityAssembly = null; 155 #if !NETSTANDARD1_5 156 // On netstandard1.5, AppDomain is not available and we just short-circuit the logic there. 157 // This is fine because only the net45 or netstandard2.0 version Grpc.Core assembly is going to used in Unity. 158 // NOTE: Instead of trying to load the UnityEngine.Application class via <c>Type.GetType()</c> 159 // we are using a more sneaky approach to avoid inadvertently loading the UnityEngine 160 // assembly (that might be available even when we are not actually on Unity, resulting 161 // in false positive). See https://github.com/grpc/grpc/issues/18801 162 unityAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(assembly => assembly.GetName().Name == UnityEngineAssemblyName); 163 #endif 164 var applicationClass = unityAssembly?.GetType(UnityEngineApplicationClassName); 165 var platformProperty = applicationClass?.GetTypeInfo().GetProperty("platform", BindingFlags.Static | BindingFlags.Public); 166 try 167 { 168 // Consult value of Application.platform via reflection 169 // https://docs.unity3d.com/ScriptReference/Application-platform.html 170 return platformProperty?.GetValue(null)?.ToString(); 171 } 172 catch (TargetInvocationException) 173 { 174 // The getter for Application.platform is defined as "extern", so if UnityEngine assembly is loaded outside of a Unity application, 175 // the definition for the getter will be missing - note that this is a sneaky trick that helps us tell a real Unity application from a non-unity 176 // application which just happens to have loaded the UnityEngine.dll assembly. 177 // https://github.com/Unity-Technologies/UnityCsReference/blob/61f92bd79ae862c4465d35270f9d1d57befd1761/Runtime/Export/Application/Application.bindings.cs#L375 178 // See https://github.com/grpc/grpc/issues/23334 179 180 // If TargetInvocationException was thrown, it most likely means that the method definition for the extern method is missing, 181 // and we are going to interpret this as "not running on Unity". 182 return null; 183 } 184 } 185 186 [DllImport("libc")] uname(IntPtr buf)187 static extern int uname(IntPtr buf); 188 GetUname()189 static string GetUname() 190 { 191 var buffer = Marshal.AllocHGlobal(8192); 192 try 193 { 194 if (uname(buffer) == 0) 195 { 196 return Marshal.PtrToStringAnsi(buffer); 197 } 198 return string.Empty; 199 } 200 catch 201 { 202 return string.Empty; 203 } 204 finally 205 { 206 if (buffer != IntPtr.Zero) 207 { 208 Marshal.FreeHGlobal(buffer); 209 } 210 } 211 } 212 } 213 } 214