diff --git a/Google.Api.Gax.Grpc/GrpcAdapter.cs b/Google.Api.Gax.Grpc/GrpcAdapter.cs
index 089f5c59..582cf75d 100644
--- a/Google.Api.Gax.Grpc/GrpcAdapter.cs
+++ b/Google.Api.Gax.Grpc/GrpcAdapter.cs
@@ -128,29 +128,12 @@ private static GrpcAdapter DetectDefaultGrpcTransportAdapterPreferringGrpcNetCli
}
// Test 2, only for .NET Framework: check what version of Windows we're using.
#if NET462_OR_GREATER
- if (!PlatformFullySupportsGrpc())
+ if (!GrpcWindowsDetection.PlatformFullySupportsGrpc())
{
return GrpcCoreAdapter.Instance;
}
#endif
return GrpcNetClientAdapter.Default;
-
-
-#if NET462_OR_GREATER
- // gRPC support on .NET Framework is complex.
- // - Some Windows platforms don't support it at all, in which case an exception is thrown
- // in GrpcChannel.ForAddress, which is handled above.
- // - Other platforms support it partially - unary and server-streaming, but not
- // bidi- or client-streaming.
- // - Other platforms support it fully.
- //
- // While in theory we could tailor the default instance for a given service to whether
- // streaming is required or not, that would lead to a very inconsistent experience.
- // At some point, we wish to check the Windows version details and use that to determine
- // which adapter to use, but for now we'll always use Grpc.Core.
- // Once https://github.com/grpc/grpc-dotnet/issues/2242 is fixed, we can use that code.
- static bool PlatformFullySupportsGrpc() => false;
-#endif
}
}
}
diff --git a/Google.Api.Gax.Grpc/GrpcWindowsDetection.cs b/Google.Api.Gax.Grpc/GrpcWindowsDetection.cs
new file mode 100644
index 00000000..1a9e1033
--- /dev/null
+++ b/Google.Api.Gax.Grpc/GrpcWindowsDetection.cs
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2024 Google LLC
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://developers.google.com/open-source/licenses/bsd
+ */
+
+#if NET462_OR_GREATER
+
+using System.Runtime.InteropServices;
+using System;
+
+internal static class GrpcWindowsDetection
+{
+ // gRPC support on .NET Framework is complex.
+ // - Some Windows platforms don't support it at all, in which case an exception is thrown
+ // in GrpcChannel.ForAddress, which is handled above.
+ // - Other platforms support it partially - unary and server-streaming, but not
+ // bidi- or client-streaming.
+ // - Other platforms support it fully.
+ //
+ // While in theory we could tailor the default instance for a given service to whether
+ // streaming is required or not, that would lead to a very inconsistent experience.
+ // We attempt to detect which version of Windows we're on, erring on the side of caution.
+ // Code based on https://gist.github.com/tonydnewell/6a663b7258ce3599e6bdae5c4291b2a6
+ internal static bool PlatformFullySupportsGrpc()
+ {
+ // If we're running on Mono or similar, all bets are off.
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return false;
+ }
+ // Older versions of .NET report an OSVersion.Version based on Windows compatibility settings.
+ // For example, if an app running on Windows 11 is configured to be "compatible" with Windows 10
+ // then the version returned is always Windows 10.
+ //
+ // Get correct Windows version directly from Windows by calling RtlGetVersion.
+ // https://www.pinvoke.net/default.aspx/ntdll/RtlGetVersion.html
+
+ DetectWindowsVersion(out var windowsVersion, out var windowsServer);
+
+ const int Windows11BuildVersion = 22000;
+
+ // Windows 11 has full support.
+ // Currently, Windows Server (even 2022) doesn't fully support gRPC under .NET Framework.
+ // When there's a version that does, we can update this check.
+ return windowsVersion.Build >= Windows11BuildVersion;
+ }
+
+ ///
+ /// Types for calling RtlGetVersion. See https://www.pinvoke.net/default.aspx/ntdll/RtlGetVersion.html
+ ///
+#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)]
+ private static extern NTSTATUS RtlGetVersion(ref OSVERSIONINFOEX versionInfo);
+#pragma warning restore SYSLIB1054 // Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time
+
+ private static void DetectWindowsVersion(out Version version, out bool isWindowsServer)
+ {
+ // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexa
+ const byte VER_NT_SERVER = 3;
+
+ var osVersionInfo = new OSVERSIONINFOEX { OSVersionInfoSize = Marshal.SizeOf() };
+
+ if (RtlGetVersion(ref osVersionInfo) != NTSTATUS.STATUS_SUCCESS)
+ {
+ throw new InvalidOperationException($"Failed to call internal {nameof(RtlGetVersion)}.");
+ }
+
+ version = new Version(osVersionInfo.MajorVersion, osVersionInfo.MinorVersion, osVersionInfo.BuildNumber, 0);
+ isWindowsServer = osVersionInfo.ProductType == VER_NT_SERVER;
+ }
+
+ private enum NTSTATUS : uint
+ {
+ ///
+ /// The operation completed successfully.
+ ///
+ STATUS_SUCCESS = 0x00000000
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ private struct OSVERSIONINFOEX
+ {
+ // The OSVersionInfoSize field must be set to Marshal.SizeOf(typeof(OSVERSIONINFOEX))
+ public int OSVersionInfoSize;
+ public int MajorVersion;
+ public int MinorVersion;
+ public int BuildNumber;
+ public int PlatformId;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
+ public string CSDVersion;
+ public ushort ServicePackMajor;
+ public ushort ServicePackMinor;
+ public short SuiteMask;
+ public byte ProductType;
+ public byte Reserved;
+ }
+}
+#endif