From 80be97f8db8f5b3f6aa65125e6127a2a5f409d2f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 26 Feb 2024 15:31:17 +0800 Subject: [PATCH 1/5] Fix OS detection error in UWP --- .../Internal/{NtDll.cs => Native.cs} | 33 ++++++++++++++++++- .../Internal/OperatingSystem.cs | 8 ++--- .../OperatingSystemTests.cs | 4 +-- 3 files changed, 38 insertions(+), 7 deletions(-) rename src/Grpc.Net.Client/Internal/{NtDll.cs => Native.cs} (67%) diff --git a/src/Grpc.Net.Client/Internal/NtDll.cs b/src/Grpc.Net.Client/Internal/Native.cs similarity index 67% rename from src/Grpc.Net.Client/Internal/NtDll.cs rename to src/Grpc.Net.Client/Internal/Native.cs index 3f5c6a1df..2145fd304 100644 --- a/src/Grpc.Net.Client/Internal/NtDll.cs +++ b/src/Grpc.Net.Client/Internal/Native.cs @@ -23,11 +23,14 @@ namespace Grpc.Net.Client.Internal; /// /// Types for calling RtlGetVersion. See https://www.pinvoke.net/default.aspx/ntdll/RtlGetVersion.html /// -internal static class NtDll +internal static class Native { #pragma warning disable SYSLIB1054 // Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time [DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern NTSTATUS RtlGetVersion(ref OSVERSIONINFOEX versionInfo); + + [DllImport("kernel32.dll", ExactSpelling = true)] + private static extern int GetCurrentApplicationUserModelId(ref uint applicationUserModelIdLength, byte[] applicationUserModelId); #pragma warning restore SYSLIB1054 // Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time internal static void DetectWindowsVersion(out Version version, out bool isWindowsServer) @@ -46,6 +49,34 @@ internal static void DetectWindowsVersion(out Version version, out bool isWindow isWindowsServer = osVersionInfo.ProductType == VER_NT_SERVER; } + internal static bool IsUwp(Version version) + { + const int Windows8Build = 9200; + if (version.Build < Windows8Build) + { + return false; + } + else + { + var bufferSize = 0U; + var result = GetCurrentApplicationUserModelId(ref bufferSize, Array.Empty()); + switch (result) + { + case 15703: // APPMODEL_ERROR_NO_APPLICATION + return false; + case 0: // ERROR_SUCCESS + case 122: // ERROR_INSUFFICIENT_BUFFER + // Success is actually insufficient buffer as we're really only looking for + // not NO_APPLICATION and we're not actually giving a buffer here. The + // API will always return NO_APPLICATION if we're not running under a + // WinRT process, no matter what size the buffer is. + return true; + default: + throw new InvalidOperationException($"Failed to get AppId, result was {result}."); + } + } + } + internal enum NTSTATUS : uint { /// diff --git a/src/Grpc.Net.Client/Internal/OperatingSystem.cs b/src/Grpc.Net.Client/Internal/OperatingSystem.cs index 1ce1c6a5a..b3fe21904 100644 --- a/src/Grpc.Net.Client/Internal/OperatingSystem.cs +++ b/src/Grpc.Net.Client/Internal/OperatingSystem.cs @@ -53,9 +53,9 @@ private OperatingSystem() // Get the value lazily so that it is only called if needed. _isWindowsServer = new Lazy(() => { - if (IsWindows) + if (IsWindows && !Native.IsUwp(Environment.OSVersion.Version)) { - NtDll.DetectWindowsVersion(out _, out var isWindowsServer); + Native.DetectWindowsVersion(out _, out var isWindowsServer); return isWindowsServer; } @@ -72,9 +72,9 @@ private OperatingSystem() // // Get correct Windows version directly from Windows by calling RtlGetVersion. // https://www.pinvoke.net/default.aspx/ntdll/RtlGetVersion.html - if (IsWindows) + if (IsWindows && !Native.IsUwp(Environment.OSVersion.Version)) { - NtDll.DetectWindowsVersion(out var windowsVersion, out var windowsServer); + Native.DetectWindowsVersion(out var windowsVersion, out var windowsServer); OSVersion = windowsVersion; _isWindowsServer = new Lazy(() => windowsServer, LazyThreadSafetyMode.ExecutionAndPublication); } diff --git a/test/Grpc.Net.Client.Tests/OperatingSystemTests.cs b/test/Grpc.Net.Client.Tests/OperatingSystemTests.cs index cc2425dd5..8e50438a7 100644 --- a/test/Grpc.Net.Client.Tests/OperatingSystemTests.cs +++ b/test/Grpc.Net.Client.Tests/OperatingSystemTests.cs @@ -29,7 +29,7 @@ public class OperatingSystemTests [Platform("Win", Reason = "Only runs on Windows where ntdll.dll is present.")] public void DetectWindowsVersion_Windows_MatchesEnvironment() { - NtDll.DetectWindowsVersion(out var version, out _); + Native.DetectWindowsVersion(out var version, out _); // It is safe to compare Environment.OSVersion.Version on netfx because tests have no compatibility setting. Assert.AreEqual(Environment.OSVersion.Version, version); @@ -39,7 +39,7 @@ public void DetectWindowsVersion_Windows_MatchesEnvironment() [Platform("Win", Reason = "Only runs on Windows where ntdll.dll is present.")] public void InstanceAndIsWindowsServer_Windows_MatchesEnvironment() { - NtDll.DetectWindowsVersion(out var version, out var isWindowsServer); + Native.DetectWindowsVersion(out var version, out var isWindowsServer); Assert.AreEqual(true, OperatingSystem.Instance.IsWindows); Assert.AreEqual(version, OperatingSystem.Instance.OSVersion); From 78eff417934795d66c552d2a5053481c539970e1 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 26 Feb 2024 15:35:12 +0800 Subject: [PATCH 2/5] Fix build --- global.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/global.json b/global.json index 391ba3c2a..338c55541 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,5 @@ { "sdk": { - "version": "8.0.100", - "rollForward": "latestFeature" + "version": "8.0.200" } } From 5b9b10ca46d52b6fb053e52872bf77fb16f84a7a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 26 Feb 2024 15:48:06 +0800 Subject: [PATCH 3/5] Detect .NET Native --- src/Grpc.Net.Client/Internal/Native.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Grpc.Net.Client/Internal/Native.cs b/src/Grpc.Net.Client/Internal/Native.cs index 2145fd304..123292b0c 100644 --- a/src/Grpc.Net.Client/Internal/Native.cs +++ b/src/Grpc.Net.Client/Internal/Native.cs @@ -51,6 +51,12 @@ internal static void DetectWindowsVersion(out Version version, out bool isWindow internal static bool IsUwp(Version version) { + if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Native", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // From https://github.com/dotnet/runtime/blob/d752f9a19f2d4bc4559e0e303e9374e4042a916e/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.Windows.cs#L190 const int Windows8Build = 9200; if (version.Build < Windows8Build) { From 0e13e79921c0716a219b3a613e39d5725572a569 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 27 Feb 2024 08:09:03 +0800 Subject: [PATCH 4/5] Update src/Grpc.Net.Client/Internal/Native.cs Co-authored-by: Andrew Casey --- src/Grpc.Net.Client/Internal/Native.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grpc.Net.Client/Internal/Native.cs b/src/Grpc.Net.Client/Internal/Native.cs index 123292b0c..34782329b 100644 --- a/src/Grpc.Net.Client/Internal/Native.cs +++ b/src/Grpc.Net.Client/Internal/Native.cs @@ -78,7 +78,7 @@ internal static bool IsUwp(Version version) // WinRT process, no matter what size the buffer is. return true; default: - throw new InvalidOperationException($"Failed to get AppId, result was {result}."); + throw new InvalidOperationException($"Failed to get AppModelId, result was {result}."); } } } From 76fba67bb47cffb62bfdfd1c6ecfdec15e1a6ab0 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 27 Feb 2024 08:16:30 +0800 Subject: [PATCH 5/5] PR feedback --- src/Grpc.Net.Client/Internal/Native.cs | 4 ++-- src/Grpc.Net.Client/Internal/OperatingSystem.cs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Grpc.Net.Client/Internal/Native.cs b/src/Grpc.Net.Client/Internal/Native.cs index 34782329b..348d4086f 100644 --- a/src/Grpc.Net.Client/Internal/Native.cs +++ b/src/Grpc.Net.Client/Internal/Native.cs @@ -49,9 +49,9 @@ internal static void DetectWindowsVersion(out Version version, out bool isWindow isWindowsServer = osVersionInfo.ProductType == VER_NT_SERVER; } - internal static bool IsUwp(Version version) + internal static bool IsUwp(string frameworkDescription, Version version) { - if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Native", StringComparison.OrdinalIgnoreCase)) + if (frameworkDescription.StartsWith(".NET Native", StringComparison.OrdinalIgnoreCase)) { return true; } diff --git a/src/Grpc.Net.Client/Internal/OperatingSystem.cs b/src/Grpc.Net.Client/Internal/OperatingSystem.cs index b3fe21904..d0ea27308 100644 --- a/src/Grpc.Net.Client/Internal/OperatingSystem.cs +++ b/src/Grpc.Net.Client/Internal/OperatingSystem.cs @@ -53,7 +53,8 @@ private OperatingSystem() // Get the value lazily so that it is only called if needed. _isWindowsServer = new Lazy(() => { - if (IsWindows && !Native.IsUwp(Environment.OSVersion.Version)) + // RtlGetVersion is not available on UWP. Check it first. + if (IsWindows && !Native.IsUwp(RuntimeInformation.FrameworkDescription, Environment.OSVersion.Version)) { Native.DetectWindowsVersion(out _, out var isWindowsServer); return isWindowsServer; @@ -72,7 +73,9 @@ private OperatingSystem() // // Get correct Windows version directly from Windows by calling RtlGetVersion. // https://www.pinvoke.net/default.aspx/ntdll/RtlGetVersion.html - if (IsWindows && !Native.IsUwp(Environment.OSVersion.Version)) + // + // RtlGetVersion is not available on UWP. Check it first. + if (IsWindows && !Native.IsUwp(RuntimeInformation.FrameworkDescription, Environment.OSVersion.Version)) { Native.DetectWindowsVersion(out var windowsVersion, out var windowsServer); OSVersion = windowsVersion;