From d1324d5cb82a5932b752f8d25d94624bbde39473 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Wed, 11 Mar 2026 10:32:38 -0700 Subject: [PATCH 1/6] Multi-target System.Private.Windows.Core This builds our shared functionality assembly for .NET Framework 4.7.2 as Microsoft.Private.Windows.Core with the same namespaces. This is for internal scenarios where we need this shared functionality on .NET Framework. Static and default interface methods don't work on .NET Framework. For our composition usages of static interfaces we build the same interface with instance methods and constrain the consuming types to `new()` as well so they can create a static instance of the `T` that they need. On Framework we still use direct COM to look at existing COM objects. For CCWs, however, we let `Marshal` provide them on Framework. Most of the complexity around that is with unwrapping DataObject. I believe things should work as expected but it is a particular focus point for both testing and following up with issues when utilizing this code. There are some CsWin32 tweaks that are necessary, notably around `IComIId`. Added partials for the COM classes where we needed them. Framework only code is in the `Framework` subfolder and ingested through the previously committed shared polyfill project. Some tests needed to be tweaked for a few reasons. One in particular was that some validated implementation details rather than functional. Some shared test functionality (notably validators that depend on static numeric interfaces) was simply excluded on .NET Framework as it wasn't being used in the tests that we needed to multitarget. .NET Framework versions can be added if/when needed. --- Winforms.sln | 20 ++++ .../TestUtilities/AppContextSwitchScope.cs | 7 +- ...BinaryFormatterInClipboardDragDropScope.cs | 4 + .../TestUtilities/BinaryFormatterScope.cs | 4 +- .../TestUtilities/BinarySerialization.cs | 5 +- .../tests/TestUtilities/CommonTestHelper.cs | 4 +- .../tests/TestUtilities/ComparisonHelpers.cs | 2 + .../FluentAssertExtensions.cs | 6 ++ .../tests/TestUtilities/ModuleInitializer.cs | 28 +++++- .../tests/TestUtilities/NoAssertContext.cs | 9 ++ .../NrbfSerializerInClipboardDragDropScope.cs | 4 + ....Private.Windows.Core.TestUtilities.csproj | 38 +++++++- src/Common/tests/TestUtilities/TempFile.cs | 2 + .../XUnit/FloatingPointDataAttribute.cs | 2 + .../XUnit/FloatingPointToleranceComparerer.cs | 2 + .../XUnit/IntegerDataAttribute.cs | 2 + .../XUnit/InvalidEnumDataAttribute.cs | 2 + .../XUnit/PositiveNumberDataAttribute.cs | 2 + .../tests/TestUtilities/XUnit/TestData.cs | 4 + .../src/.editorconfig | 0 .../Private/Windows/Ole/DataObjectFactory.cs | 11 +++ .../src/Framework/Windows/Win32/IComIID.cs | 20 ++++ .../Windows/Win32/System/Com/IAdviseSink.cs | 9 ++ .../Windows/Win32/System/Com/IDataObject.cs | 9 ++ .../Win32/System/Com/IEnumFORMATETC.cs | 9 ++ .../Windows/Win32/System/Com/IEnumSTATDATA.cs | 9 ++ .../Win32/System/Com/IGlobalInterfaceTable.cs | 9 ++ .../Windows/Win32/System/Com/IStream.cs | 9 ++ .../Windows/Win32/System/Com/ITypeInfo.cs | 9 ++ .../Windows/Win32/System/Com/ITypeLib.cs | 9 ++ .../Windows/Win32/System/Com/IUnknown.cs | 9 ++ .../Windows/Win32/System/Ole/IDropTarget.cs | 9 ++ .../Win32/UI/Shell/IDragSourceHelper2.cs | 9 ++ .../Win32/UI/Shell/IDropTargetHelper.cs | 9 ++ .../src/Framework/Windows/Win32/__char_32.cs | 16 +++ .../src/GlobalSuppressions.cs | 2 + .../src/GlobalUsings.cs | 5 +- .../src/Microsoft.Private.Windows.Core.csproj | 89 +++++++++++++++++ .../src/Resources/SR.resx | 59 ----------- .../src/System.Private.Windows.Core.csproj | 12 +++ .../src/System/ArgumentValidation.cs | 8 ++ .../src/System/DisposeHelper.cs | 1 - .../src/System/ExceptionExtensions.cs | 6 ++ .../src/System/IO/BinaryReaderExtensions.cs | 15 ++- .../src/System/IO/StreamExtensions.cs | 12 ++- .../BinaryFormat/BinaryFormatWriter.cs | 2 + .../BinaryFormat/Serializer/IRecord.cs | 4 +- .../BinaryFormat/Serializer/MessageEnd.cs | 2 + .../NullRecord.ObjectNullMultiple.cs | 4 +- .../BinaryFormat/Serializer/NullRecord.cs | 4 +- .../BinaryFormat/Serializer/RecordMap.cs | 33 ------- .../Serializer/SerializationHeader.cs | 2 + .../Windows/Nrbf/CoreNrbfSerializer.cs | 9 ++ .../Private/Windows/Nrbf/INrbfSerializer.cs | 20 +++- .../Nrbf/SerializationRecordExtensions.cs | 15 ++- .../Windows/Ole/BinaryFormatUtilities.cs | 24 ++++- .../Private/Windows/Ole/ClipboardCore.cs | 66 ++++++++++++- .../Ole/Composition.ManagedToNativeAdapter.cs | 21 +++- .../Ole/Composition.NativeToManagedAdapter.cs | 44 ++++++++- .../Ole/Composition.RuntimeToNativeAdapter.cs | 7 +- .../System/Private/Windows/Ole/Composition.cs | 31 +++++- .../Private/Windows/Ole/DataFormatsCore.cs | 53 ++++++---- .../Private/Windows/Ole/DataObjectCore.cs | 4 +- .../Windows/Ole/DataObjectExtensions.cs | 31 +++--- .../System/Private/Windows/Ole/DataStore.cs | 25 ++++- .../Private/Windows/Ole/DragDropHelper.cs | 20 +++- .../Private/Windows/Ole/FormatEnumerator.cs | 11 ++- .../Windows/Ole/IComVisibleDataObject.cs | 8 +- .../System/Private/Windows/Ole/IDataFormat.cs | 5 +- .../Windows/Ole/IDataObjectInternal.cs | 23 ++++- .../Private/Windows/Ole/IOleServices.cs | 52 +++++++--- .../System/Private/Windows/Ole/TypeBinder.cs | 9 ++ .../src/System/SpanReader.cs | 23 +++-- .../src/System/SpanWriter.cs | 6 +- .../src/System/TypeExtensions.cs | 41 +++++++- .../src/Windows/Win32/Foundation/IHandle.cs | 4 + .../Windows/Win32/Foundation/NullHandle.cs | 2 + .../src/Windows/Win32/Graphics/Gdi/ARGB.cs | 6 +- .../Windows/Win32/Graphics/Gdi/HdcHandle.cs | 2 + .../Win32/PInvokeCore.EnumChildWindows.cs | 21 +++- .../Win32/PInvokeCore.EnumDisplayMonitors.cs | 17 +++- .../Win32/PInvokeCore.EnumThreadWindows.cs | 20 +++- .../Windows/Win32/PInvokeCore.EnumWindows.cs | 19 +++- .../Win32/System/Com/AgileComPointer.cs | 4 +- .../Windows/Win32/System/Com/ComHelpers.cs | 71 +++++++++----- .../Win32/System/Com/ComManagedStream.cs | 7 +- .../src/Windows/Win32/System/Com/ComScope.cs | 21 ++-- .../Win32/System/Com/GlobalInterfaceTable.cs | 2 + .../src/Windows/Win32/System/Com/IID.cs | 20 +++- .../Windows/Win32/System/Ole/OLE_HANDLE.cs | 10 +- .../Windows/Win32/System/Variant/VARIANT.cs | 11 +++ .../FormattedObject/RecordMapTests.cs | 33 ------- .../System.Private.Windows.Core.Tests.csproj | 16 ++- .../Ole/BinaryFormatUtilitesTestsBase.cs | 10 +- .../Windows/Ole/BinaryFormatUtilitiesTests.cs | 17 ++-- .../Private/Windows/Ole/ClipboardCoreTests.cs | 49 ++++++++-- .../Private/Windows/Ole/ClipboardScope.cs | 4 +- .../Private/Windows/Ole/DataObjectProxy.cs | 46 +++++---- .../Private/Windows/Ole/MockOleServices.cs | 58 ++++++++--- .../Windows/Ole/NativeDataObjectMock.cs | 6 +- .../Ole/NativeToManagedAdapterTests.cs | 7 +- .../Private/Windows/Ole/TestDataObject.cs | 43 +++++--- .../System/Private/Windows/Ole/TestFormat.cs | 10 +- .../Private/Windows/Ole/TypeBinderTests.cs | 97 +++++++++++++++---- .../System/TypeExtensionsTests.cs | 31 +++++- .../System/Windows/Forms/OLE/DropTarget.cs | 6 ++ .../Com2Interop/COM2FontConverterTests.cs | 2 +- 107 files changed, 1386 insertions(+), 396 deletions(-) create mode 100644 src/System.Private.Windows.Core/src/.editorconfig create mode 100644 src/System.Private.Windows.Core/src/Framework/System/Private/Windows/Ole/DataObjectFactory.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/IComIID.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IAdviseSink.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IDataObject.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IEnumFORMATETC.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IEnumSTATDATA.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IGlobalInterfaceTable.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IStream.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/ITypeInfo.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/ITypeLib.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IUnknown.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Ole/IDropTarget.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/UI/Shell/IDragSourceHelper2.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/UI/Shell/IDropTargetHelper.cs create mode 100644 src/System.Private.Windows.Core/src/Framework/Windows/Win32/__char_32.cs create mode 100644 src/System.Private.Windows.Core/src/Microsoft.Private.Windows.Core.csproj rename src/{System.Windows.Forms.Primitives => System.Private.Windows.Core}/src/System/DisposeHelper.cs (96%) delete mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/RecordMap.cs delete mode 100644 src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/RecordMapTests.cs diff --git a/Winforms.sln b/Winforms.sln index 0fc8bb25954..192a41996bb 100644 --- a/Winforms.sln +++ b/Winforms.sln @@ -214,6 +214,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Private.Windows.P EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.Private.Windows.Polyfills", "src\Microsoft.Private.Windows.Polyfills\Microsoft.Private.Windows.Polyfills.shproj", "{799CC0C2-236B-4A76-8CE3-65C346182CC1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Private.Windows.Core", "src\System.Private.Windows.Core\src\Microsoft.Private.Windows.Core.csproj", "{36A02BBB-B60B-5F23-6AF0-F41561A9275C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1082,6 +1084,22 @@ Global {4A2BD741-482C-4BF7-8A2D-5535A770DB69}.Release|x64.Build.0 = Release|Any CPU {4A2BD741-482C-4BF7-8A2D-5535A770DB69}.Release|x86.ActiveCfg = Release|Any CPU {4A2BD741-482C-4BF7-8A2D-5535A770DB69}.Release|x86.Build.0 = Release|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Debug|arm64.ActiveCfg = Debug|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Debug|arm64.Build.0 = Debug|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Debug|x64.ActiveCfg = Debug|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Debug|x64.Build.0 = Debug|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Debug|x86.ActiveCfg = Debug|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Debug|x86.Build.0 = Debug|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Release|Any CPU.Build.0 = Release|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Release|arm64.ActiveCfg = Release|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Release|arm64.Build.0 = Release|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Release|x64.ActiveCfg = Release|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Release|x64.Build.0 = Release|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Release|x86.ActiveCfg = Release|Any CPU + {36A02BBB-B60B-5F23-6AF0-F41561A9275C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1150,11 +1168,13 @@ Global {7BFA3A16-A19A-4FCE-AE1E-D1187BD92D4C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {4A2BD741-482C-4BF7-8A2D-5535A770DB69} = {583F1292-AE8D-4511-B8D8-A81FE4642DDC} {799CC0C2-236B-4A76-8CE3-65C346182CC1} = {77FEDB47-F7F6-490D-AF7C-ABB4A9E0B9D7} + {36A02BBB-B60B-5F23-6AF0-F41561A9275C} = {77FEDB47-F7F6-490D-AF7C-ABB4A9E0B9D7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7B1B0433-F612-4E5A-BE7E-FCF5B9F6E136} EndGlobalSection GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\Microsoft.Private.Windows.Polyfills\Microsoft.Private.Windows.Polyfills.projitems*{36a02bbb-b60b-5f23-6af0-f41561a9275c}*SharedItemsImports = 5 src\Microsoft.Private.Windows.Polyfills\Microsoft.Private.Windows.Polyfills.projitems*{799cc0c2-236b-4a76-8ce3-65c346182cc1}*SharedItemsImports = 13 EndGlobalSection EndGlobal diff --git a/src/Common/tests/TestUtilities/AppContextSwitchScope.cs b/src/Common/tests/TestUtilities/AppContextSwitchScope.cs index 45dff57a733..98e4aaaca32 100644 --- a/src/Common/tests/TestUtilities/AppContextSwitchScope.cs +++ b/src/Common/tests/TestUtilities/AppContextSwitchScope.cs @@ -24,12 +24,17 @@ public AppContextSwitchScope(string switchName, bool enable) public AppContextSwitchScope(string switchName, Func? getDefaultValue, bool enable) { - if (!AppContext.TryGetSwitch(AppContextSwitchNames.LocalAppContext_DisableCaching, out bool isEnabled) + bool isEnabled; + +#if NET + // .NET Framework doesn't cache switches. + if (!AppContext.TryGetSwitch(AppContextSwitchNames.LocalAppContext_DisableCaching, out isEnabled) || !isEnabled) { // It doesn't make sense to try messing with AppContext switches if they are going to be cached. throw new InvalidOperationException("LocalAppContext switch caching is not disabled."); } +#endif // AppContext won't have any switch value until it is explicitly set. if (!AppContext.TryGetSwitch(switchName, out _originalState)) diff --git a/src/Common/tests/TestUtilities/BinaryFormatterInClipboardDragDropScope.cs b/src/Common/tests/TestUtilities/BinaryFormatterInClipboardDragDropScope.cs index ebd3567cf2b..c3665e5e056 100644 --- a/src/Common/tests/TestUtilities/BinaryFormatterInClipboardDragDropScope.cs +++ b/src/Common/tests/TestUtilities/BinaryFormatterInClipboardDragDropScope.cs @@ -14,7 +14,11 @@ public BinaryFormatterInClipboardDragDropScope(bool enable) AppContextSwitchNames.ClipboardDragDropEnableUnsafeBinaryFormatterSerializationSwitchName, () => AppContextSwitchScope.GetDefaultValueForSwitchInAssembly( AppContextSwitchNames.ClipboardDragDropEnableUnsafeBinaryFormatterSerializationSwitchName, +#if NET "System.Private.Windows.Core", +#else + "Microsoft.Private.Windows.Core", +#endif "System.Private.Windows.CoreAppContextSwitches"), enable); } diff --git a/src/Common/tests/TestUtilities/BinaryFormatterScope.cs b/src/Common/tests/TestUtilities/BinaryFormatterScope.cs index b6c12b2bc50..0c7fa282003 100644 --- a/src/Common/tests/TestUtilities/BinaryFormatterScope.cs +++ b/src/Common/tests/TestUtilities/BinaryFormatterScope.cs @@ -17,7 +17,7 @@ public BinaryFormatterScope(bool enable) // Prevent multiple BinaryFormatterScopes from running simultaneously. Using Monitor to allow recursion on // the same thread. Monitor.Enter(typeof(BinaryFormatterScope)); - _switchScope = new(AppContextSwitchNames.EnableUnsafeBinaryFormatterSerialization, GetDefaultValue, enable); + _switchScope = new(AppContextSwitchNames.EnableUnsafeBinaryFormatterSerialization, () => false, enable); } public void Dispose() @@ -31,6 +31,4 @@ public void Dispose() Monitor.Exit(typeof(BinaryFormatterScope)); } } - - public static bool GetDefaultValue() => false; } diff --git a/src/Common/tests/TestUtilities/BinarySerialization.cs b/src/Common/tests/TestUtilities/BinarySerialization.cs index c51423dc51c..9d96d32a22d 100644 --- a/src/Common/tests/TestUtilities/BinarySerialization.cs +++ b/src/Common/tests/TestUtilities/BinarySerialization.cs @@ -3,11 +3,14 @@ using System.Formats.Nrbf; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters; using System.Runtime.Serialization.Formatters.Binary; +#if NET +using System.Runtime.CompilerServices; +#endif + namespace System; public static class BinarySerialization diff --git a/src/Common/tests/TestUtilities/CommonTestHelper.cs b/src/Common/tests/TestUtilities/CommonTestHelper.cs index e35380af916..6d711d38b33 100644 --- a/src/Common/tests/TestUtilities/CommonTestHelper.cs +++ b/src/Common/tests/TestUtilities/CommonTestHelper.cs @@ -51,9 +51,9 @@ public static TheoryData GetCharTheoryData() public static TheoryData GetIntPtrTheoryData() => new() { - -1, + (nint)(-1), IntPtr.Zero, - 1 + (nint)1 }; public static TheoryData GetColorTheoryData() diff --git a/src/Common/tests/TestUtilities/ComparisonHelpers.cs b/src/Common/tests/TestUtilities/ComparisonHelpers.cs index a80634fa30d..a68eb975021 100644 --- a/src/Common/tests/TestUtilities/ComparisonHelpers.cs +++ b/src/Common/tests/TestUtilities/ComparisonHelpers.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if NET using System.Numerics; namespace System; @@ -110,3 +111,4 @@ public static bool EqualsFloating(T x, T y, T variance) static unsafe bool IsPositiveZero(T value) => T.IsZero(value) && T.IsPositive(value); } } +#endif diff --git a/src/Common/tests/TestUtilities/FluentAssertions/FluentAssertExtensions.cs b/src/Common/tests/TestUtilities/FluentAssertions/FluentAssertExtensions.cs index 05f5993a8f8..b199ef0dbb8 100644 --- a/src/Common/tests/TestUtilities/FluentAssertions/FluentAssertExtensions.cs +++ b/src/Common/tests/TestUtilities/FluentAssertions/FluentAssertExtensions.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; +#if NET using FluentAssertions.Collections; +#endif using FluentAssertions.Execution; using FluentAssertions.Numeric; using FluentAssertions.Primitives; @@ -43,6 +45,7 @@ public static AndConstraint BeApproximately( return new(parent); } +#if NET /// /// Asserts that two collections contain the same items in the same order /// within the given . @@ -63,6 +66,7 @@ public static AndConstraint> BeApproxima && ComparisonHelpers.EqualsFloating(expected.Height, actual.Height, precision), because, becauseArgs); +#endif /// /// Asserts a value approximates another value as close as possible. @@ -82,6 +86,7 @@ public static AndConstraint BeApproximately( return new AndConstraint(parent); } +#if NET /// /// Asserts that two collections contain the same items in the same order /// within the given . @@ -100,6 +105,7 @@ public static AndConstraint> BeApproximately && ComparisonHelpers.EqualsFloating(expected.Y, actual.Y, precision), because, becauseArgs); +#endif /// /// Asserts a is empty. diff --git a/src/Common/tests/TestUtilities/ModuleInitializer.cs b/src/Common/tests/TestUtilities/ModuleInitializer.cs index bad5b308b0d..cd08a199412 100644 --- a/src/Common/tests/TestUtilities/ModuleInitializer.cs +++ b/src/Common/tests/TestUtilities/ModuleInitializer.cs @@ -1,17 +1,39 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.CompilerServices; - namespace System; internal static class ModuleInitializer { - [ModuleInitializer] +#if NET + [Runtime.CompilerServices.ModuleInitializer] +#endif [SuppressMessage("Usage", "CA2255:The 'ModuleInitializer' attribute should not be used in libraries", Justification = "Intentional use of module initializer to register trace listener.")] internal static void Initialize() { Trace.Listeners.Clear(); Trace.Listeners.Add(ThrowingTraceListener.Instance); } + +#if NETFRAMEWORK + private static bool s_initialized; + private static readonly object s_lock = new object(); + + internal static void EnsureInitialized() + { + if (!s_initialized) + { + lock (s_lock) + { + if (s_initialized) + { + return; + } + + Initialize(); + s_initialized = true; + } + } + } +#endif } diff --git a/src/Common/tests/TestUtilities/NoAssertContext.cs b/src/Common/tests/TestUtilities/NoAssertContext.cs index 67d29efae30..38cd300f0e4 100644 --- a/src/Common/tests/TestUtilities/NoAssertContext.cs +++ b/src/Common/tests/TestUtilities/NoAssertContext.cs @@ -18,7 +18,12 @@ public sealed class NoAssertContext : IDisposable // We do, however need to lock around hooking/unhooking our custom listener to make sure that we // are rerouting correctly if multiple threads are creating/disposing this class concurrently. +#if NET private static readonly Lock s_lock = new(); +#else + private static readonly object s_lock = new(); +#endif + private static bool s_hooked; private static bool s_hasDefaultListener; private static bool s_hasThrowingListener; @@ -31,6 +36,10 @@ public sealed class NoAssertContext : IDisposable public NoAssertContext() { +#if NETFRAMEWORK + ModuleInitializer.EnsureInitialized(); +#endif + s_suppressedThreads.AddOrUpdate(Environment.CurrentManagedThreadId, 1, (key, oldValue) => oldValue + 1); // Lock to make sure we are hooked properly if two threads come into the constructor/dispose at the same time. diff --git a/src/Common/tests/TestUtilities/NrbfSerializerInClipboardDragDropScope.cs b/src/Common/tests/TestUtilities/NrbfSerializerInClipboardDragDropScope.cs index c86be7713ec..eaec8d30ef4 100644 --- a/src/Common/tests/TestUtilities/NrbfSerializerInClipboardDragDropScope.cs +++ b/src/Common/tests/TestUtilities/NrbfSerializerInClipboardDragDropScope.cs @@ -14,7 +14,11 @@ public NrbfSerializerInClipboardDragDropScope(bool enable) AppContextSwitchNames.ClipboardDragDropEnableNrbfSerializationSwitchName, () => AppContextSwitchScope.GetDefaultValueForSwitchInAssembly( AppContextSwitchNames.ClipboardDragDropEnableNrbfSerializationSwitchName, +#if NET "System.Private.Windows.Core", +#else + "Microsoft.Private.Windows.Core", +#endif "System.Private.Windows.CoreAppContextSwitches"), enable); } diff --git a/src/Common/tests/TestUtilities/System.Private.Windows.Core.TestUtilities.csproj b/src/Common/tests/TestUtilities/System.Private.Windows.Core.TestUtilities.csproj index 892cdcb886b..d38c538efa1 100644 --- a/src/Common/tests/TestUtilities/System.Private.Windows.Core.TestUtilities.csproj +++ b/src/Common/tests/TestUtilities/System.Private.Windows.Core.TestUtilities.csproj @@ -1,7 +1,7 @@  - $(NetCurrent) + $(NetCurrent);net481 System.Private.Windows.Core.TestUtilities true System @@ -11,6 +11,14 @@ true + + + $(NoWarn);CA1822 + + @@ -21,4 +29,32 @@ + + + false + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/src/Common/tests/TestUtilities/TempFile.cs b/src/Common/tests/TestUtilities/TempFile.cs index 99785c911b0..881c6832df9 100644 --- a/src/Common/tests/TestUtilities/TempFile.cs +++ b/src/Common/tests/TestUtilities/TempFile.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if NET using System.Runtime.CompilerServices; +#endif namespace System.IO; diff --git a/src/Common/tests/TestUtilities/XUnit/FloatingPointDataAttribute.cs b/src/Common/tests/TestUtilities/XUnit/FloatingPointDataAttribute.cs index bace832f5d1..48feae54cae 100644 --- a/src/Common/tests/TestUtilities/XUnit/FloatingPointDataAttribute.cs +++ b/src/Common/tests/TestUtilities/XUnit/FloatingPointDataAttribute.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if NET using System.Numerics; namespace Xunit; @@ -15,3 +16,4 @@ public FloatingPointDataAttribute() : base(typeof(FloatingPointDataAttribute()); } +#endif diff --git a/src/Common/tests/TestUtilities/XUnit/FloatingPointToleranceComparerer.cs b/src/Common/tests/TestUtilities/XUnit/FloatingPointToleranceComparerer.cs index 8ef261daabc..ec92c6ec2c7 100644 --- a/src/Common/tests/TestUtilities/XUnit/FloatingPointToleranceComparerer.cs +++ b/src/Common/tests/TestUtilities/XUnit/FloatingPointToleranceComparerer.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if NET using System.Numerics; namespace System.XUnit; @@ -20,3 +21,4 @@ public class FloatingPointToleranceComparerer : IEqualityComparer public int GetHashCode([DisallowNull] T obj) => obj.GetHashCode(); } +#endif diff --git a/src/Common/tests/TestUtilities/XUnit/IntegerDataAttribute.cs b/src/Common/tests/TestUtilities/XUnit/IntegerDataAttribute.cs index 2c3a4d0d54b..870d2f88a14 100644 --- a/src/Common/tests/TestUtilities/XUnit/IntegerDataAttribute.cs +++ b/src/Common/tests/TestUtilities/XUnit/IntegerDataAttribute.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if NET using System.Numerics; namespace Xunit; @@ -15,3 +16,4 @@ public IntegerDataAttribute() : base(typeof(IntegerDataAttribute)) { } public static ReadOnlyTheoryData TheoryData { get; } = new(TestData.GetIntegerData()); } +#endif diff --git a/src/Common/tests/TestUtilities/XUnit/InvalidEnumDataAttribute.cs b/src/Common/tests/TestUtilities/XUnit/InvalidEnumDataAttribute.cs index 409b1bfd607..f834ddd3ebe 100644 --- a/src/Common/tests/TestUtilities/XUnit/InvalidEnumDataAttribute.cs +++ b/src/Common/tests/TestUtilities/XUnit/InvalidEnumDataAttribute.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; +#if NET using System.Runtime.CompilerServices; +#endif namespace Xunit; diff --git a/src/Common/tests/TestUtilities/XUnit/PositiveNumberDataAttribute.cs b/src/Common/tests/TestUtilities/XUnit/PositiveNumberDataAttribute.cs index 0214e957a8c..ea71d10f4ee 100644 --- a/src/Common/tests/TestUtilities/XUnit/PositiveNumberDataAttribute.cs +++ b/src/Common/tests/TestUtilities/XUnit/PositiveNumberDataAttribute.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if NET using System.Numerics; namespace Xunit; @@ -15,3 +16,4 @@ public PositiveIntegerDataAttribute() : base(typeof(PositiveIntegerDataAttribute public static ReadOnlyTheoryData TheoryData { get; } = new(TestData.GetPositiveIntegerData()); } +#endif diff --git a/src/Common/tests/TestUtilities/XUnit/TestData.cs b/src/Common/tests/TestUtilities/XUnit/TestData.cs index 2c0d31b940e..def77783769 100644 --- a/src/Common/tests/TestUtilities/XUnit/TestData.cs +++ b/src/Common/tests/TestUtilities/XUnit/TestData.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if NET + using System.Collections.Immutable; using System.Numerics; namespace Xunit; @@ -81,3 +83,5 @@ private static class PositiveIntegerData ]; } } + +#endif diff --git a/src/System.Private.Windows.Core/src/.editorconfig b/src/System.Private.Windows.Core/src/.editorconfig new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/System.Private.Windows.Core/src/Framework/System/Private/Windows/Ole/DataObjectFactory.cs b/src/System.Private.Windows.Core/src/Framework/System/Private/Windows/Ole/DataObjectFactory.cs new file mode 100644 index 00000000000..c9dae522f7a --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/System/Private/Windows/Ole/DataObjectFactory.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Private.Windows.Ole; + +internal static class DataObjectFactory + where TDataObject : class, IDataObjectInternal, TIDataObject, new() + where TIDataObject : class +{ + public static IDataObjectInternal Instance { get; } = new TDataObject(); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/IComIID.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/IComIID.cs new file mode 100644 index 00000000000..6af113c4608 --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/IComIID.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32; + +/// +/// Common interface for COM interface wrapping structs. +/// +/// +/// +/// On .NET 6 and later, this is provided by CSWin32 as an abstract static. +/// +/// +public interface IComIID +{ + /// + /// The identifier (IID) GUID for this interface. + /// + ref readonly Guid Guid { get; } +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IAdviseSink.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IAdviseSink.cs new file mode 100644 index 00000000000..e404d72924b --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IAdviseSink.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.System.Com; + +internal partial struct IAdviseSink : IComIID +{ + readonly ref readonly Guid IComIID.Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IDataObject.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IDataObject.cs new file mode 100644 index 00000000000..4e328766ee6 --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IDataObject.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.System.Com; + +internal partial struct IDataObject : IComIID +{ + readonly ref readonly Guid IComIID.Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IEnumFORMATETC.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IEnumFORMATETC.cs new file mode 100644 index 00000000000..180e6be0aa4 --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IEnumFORMATETC.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.System.Com; + +internal partial struct IEnumFORMATETC : IComIID +{ + readonly ref readonly Guid IComIID.Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IEnumSTATDATA.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IEnumSTATDATA.cs new file mode 100644 index 00000000000..403e7637467 --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IEnumSTATDATA.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.System.Com; + +internal partial struct IEnumSTATDATA : IComIID +{ + readonly ref readonly Guid IComIID.Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IGlobalInterfaceTable.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IGlobalInterfaceTable.cs new file mode 100644 index 00000000000..50c5ffcb0ff --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IGlobalInterfaceTable.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.System.Com; + +internal partial struct IGlobalInterfaceTable : IComIID +{ + readonly ref readonly Guid IComIID.Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IStream.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IStream.cs new file mode 100644 index 00000000000..596fd686acb --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IStream.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.System.Com; + +internal partial struct IStream : IComIID +{ + readonly ref readonly Guid IComIID.Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/ITypeInfo.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/ITypeInfo.cs new file mode 100644 index 00000000000..dd182c58599 --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/ITypeInfo.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.System.Com; + +internal partial struct ITypeInfo : IComIID +{ + readonly ref readonly Guid IComIID.Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/ITypeLib.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/ITypeLib.cs new file mode 100644 index 00000000000..3d46bc1a848 --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/ITypeLib.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.System.Com; + +internal partial struct ITypeLib : IComIID +{ + readonly ref readonly Guid IComIID.Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IUnknown.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IUnknown.cs new file mode 100644 index 00000000000..ce6d2f8b2d0 --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IUnknown.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.System.Com; + +internal partial struct IUnknown : IComIID +{ + readonly ref readonly Guid IComIID.Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Ole/IDropTarget.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Ole/IDropTarget.cs new file mode 100644 index 00000000000..e83ec0be325 --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Ole/IDropTarget.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.System.Ole; + +internal partial struct IDropTarget : IComIID +{ + readonly ref readonly Guid IComIID.Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/UI/Shell/IDragSourceHelper2.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/UI/Shell/IDragSourceHelper2.cs new file mode 100644 index 00000000000..f26f13366d3 --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/UI/Shell/IDragSourceHelper2.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.UI.Shell; + +internal partial struct IDragSourceHelper2 : IComIID +{ + public readonly ref readonly Guid Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/UI/Shell/IDropTargetHelper.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/UI/Shell/IDropTargetHelper.cs new file mode 100644 index 00000000000..705a0ae874c --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/UI/Shell/IDropTargetHelper.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32.UI.Shell; + +internal partial struct IDropTargetHelper : IComIID +{ + public readonly ref readonly Guid Guid => ref Unsafe.AsRef(in IID_Guid); +} diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/__char_32.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/__char_32.cs new file mode 100644 index 00000000000..edd06fd0373 --- /dev/null +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/__char_32.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Windows.Win32; + +internal partial struct __char_32 +{ + [UnscopedRef] + internal unsafe Span AsSpan() + { + fixed (char* p = &Value[0]) + { + return new(p, SpanLength); + } + } +} diff --git a/src/System.Private.Windows.Core/src/GlobalSuppressions.cs b/src/System.Private.Windows.Core/src/GlobalSuppressions.cs index 9134b588c99..839fb0b0bd6 100644 --- a/src/System.Private.Windows.Core/src/GlobalSuppressions.cs +++ b/src/System.Private.Windows.Core/src/GlobalSuppressions.cs @@ -7,6 +7,7 @@ // OLE related trimming suppressions. Suppressing here so we can find and fix in the future as we try to be trim compatible. // Not disabling globally to try and keep new code trim compatible and clean. +#if NET [assembly: SuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Trimming not supported", Scope = "member", Target = "~M:System.Private.Windows.Ole.DataStore`1.TryGetDataInternal``1(System.String,System.Boolean,``0@)~System.Boolean")] [assembly: SuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Trimming not supported", Scope = "member", Target = "~M:System.Private.Windows.Nrbf.SerializationRecordExtensions.TryGetObjectFromJson``1(System.Formats.Nrbf.SerializationRecord,System.Private.Windows.BinaryFormat.ITypeResolver,``0@)~System.ValueTuple{System.Boolean,System.Boolean}")] [assembly: SuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Trimming not supported", Scope = "member", Target = "~M:System.Private.Windows.Ole.BinaryFormatUtilities`1.WriteObjectToStream(System.IO.MemoryStream,System.Object,System.String)")] @@ -15,3 +16,4 @@ [assembly: SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "Trimming not supported", Scope = "member", Target = "~M:System.Private.Windows.Ole.TypeBinder`1.BindToType(System.Reflection.Metadata.TypeName)~System.Type")] [assembly: SuppressMessage("Trimming", "IL2057:Unrecognized value passed to the parameter of method. It's not possible to guarantee the availability of the target type.", Justification = "Trimming not supported", Scope = "member", Target = "~M:System.Private.Windows.Nrbf.SerializationRecordExtensions.TryGetObjectFromJson``1(System.Formats.Nrbf.SerializationRecord,System.Private.Windows.BinaryFormat.ITypeResolver,``0@)~System.ValueTuple{System.Boolean,System.Boolean}")] [assembly: SuppressMessage("Trimming", "IL2093:'DynamicallyAccessedMemberTypes' on the return value of method don't match overridden return value of method. All overridden members must have the same 'DynamicallyAccessedMembersAttribute' usage.", Justification = "Trimming not supported", Scope = "member", Target = "~M:System.Private.Windows.Ole.TypeBinder`1.BindToType(System.Reflection.Metadata.TypeName)~System.Type")] +#endif diff --git a/src/System.Private.Windows.Core/src/GlobalUsings.cs b/src/System.Private.Windows.Core/src/GlobalUsings.cs index e4d07dfe59b..c9a77ce784f 100644 --- a/src/System.Private.Windows.Core/src/GlobalUsings.cs +++ b/src/System.Private.Windows.Core/src/GlobalUsings.cs @@ -7,8 +7,11 @@ global using System.Private.Windows.Core.Resources; global using System.Runtime.CompilerServices; global using System.Runtime.InteropServices; -global using System.Runtime.InteropServices.Marshalling; global using Windows.Win32; global using Windows.Win32.Foundation; global using Windows.Win32.Graphics.Gdi; global using Windows.Win32.UI.WindowsAndMessaging; + +#if NET +global using System.Runtime.InteropServices.Marshalling; +#endif diff --git a/src/System.Private.Windows.Core/src/Microsoft.Private.Windows.Core.csproj b/src/System.Private.Windows.Core/src/Microsoft.Private.Windows.Core.csproj new file mode 100644 index 00000000000..155e6d193a3 --- /dev/null +++ b/src/System.Private.Windows.Core/src/Microsoft.Private.Windows.Core.csproj @@ -0,0 +1,89 @@ + + + + Microsoft.Private.Windows.Core + net472 + true + enable + + $(NoWarn);CS8981 + + + $(NoWarn);CS3016;SYSLIB5005 + + + $(NoWarn);CA1822 + true + true + false + + true + false + + + windows10.0.14393.0 + + + + + + + + + + + + + + + + + + + + + + + + false + System.Private.Windows.Core.Resources.SR + true + + + + + + false + + + System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute; + System.Diagnostics.CodeAnalysis.UnscopedRefAttribute + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/src/System.Private.Windows.Core/src/Resources/SR.resx b/src/System.Private.Windows.Core/src/Resources/SR.resx index 810f016f1fb..72290d47456 100644 --- a/src/System.Private.Windows.Core/src/Resources/SR.resx +++ b/src/System.Private.Windows.Core/src/Resources/SR.resx @@ -1,64 +1,5 @@  - - diff --git a/src/System.Private.Windows.Core/src/System.Private.Windows.Core.csproj b/src/System.Private.Windows.Core/src/System.Private.Windows.Core.csproj index 610208e48f7..a6262e7f553 100644 --- a/src/System.Private.Windows.Core/src/System.Private.Windows.Core.csproj +++ b/src/System.Private.Windows.Core/src/System.Private.Windows.Core.csproj @@ -12,17 +12,29 @@ cross that bridge when we hit it (if ever). --> $(NoWarn);CS8981 + $(NoWarn);CS3016;SYSLIB5005 + + + $(NoWarn);CA1822 + true true false true false + $(DefineConstants);OLE_JSON + + + $(DefaultItemExcludes);**/Framework/**/* diff --git a/src/System.Private.Windows.Core/src/System/ArgumentValidation.cs b/src/System.Private.Windows.Core/src/System/ArgumentValidation.cs index 66a5169b033..c259c7afa0b 100644 --- a/src/System.Private.Windows.Core/src/System/ArgumentValidation.cs +++ b/src/System.Private.Windows.Core/src/System/ArgumentValidation.cs @@ -26,6 +26,12 @@ internal static string OrThrowIfNullOrEmpty([NotNull] this string? argument, [Ca return argument; } + // .NET Framework analyzer is confused by the use of [NotNull] on the parameter of these methods, and thinks + // that the parameter must be non-null when exiting the method, which is not the case as these methods throw + // when the parameter is null. Suppress the warning for these methods. + +#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. + internal static void ThrowIfNullOrEmpty([NotNull] this string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) { if (string.IsNullOrEmpty(argument)) @@ -42,6 +48,8 @@ internal static void ThrowIfNullOrEmptyWithMessage([NotNull] this string? argume } } +#pragma warning restore CS8777 + internal static void ThrowIfNull(HDC argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) { if (argument.IsNull) diff --git a/src/System.Windows.Forms.Primitives/src/System/DisposeHelper.cs b/src/System.Private.Windows.Core/src/System/DisposeHelper.cs similarity index 96% rename from src/System.Windows.Forms.Primitives/src/System/DisposeHelper.cs rename to src/System.Private.Windows.Core/src/System/DisposeHelper.cs index e9d22a9f06b..2b4f6df4e0b 100644 --- a/src/System.Windows.Forms.Primitives/src/System/DisposeHelper.cs +++ b/src/System.Private.Windows.Core/src/System/DisposeHelper.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.CompilerServices; using Windows.Win32.System.Com; namespace System; diff --git a/src/System.Private.Windows.Core/src/System/ExceptionExtensions.cs b/src/System.Private.Windows.Core/src/System/ExceptionExtensions.cs index cf333b2a465..144e8189a5d 100644 --- a/src/System.Private.Windows.Core/src/System/ExceptionExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/ExceptionExtensions.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; +#if NET using System.Runtime.ExceptionServices; +#endif using System.Runtime.Serialization; namespace System; @@ -34,8 +36,12 @@ internal static SerializationException ConvertToSerializationException(this Exce return ex is SerializationException serializationException ? serializationException +#if NET : (SerializationException)ExceptionDispatchInfo.SetRemoteStackTrace( new SerializationException(ex.Message, ex), ex.StackTrace ?? string.Empty); +#else + : new SerializationException(ex.Message, ex); +#endif } } diff --git a/src/System.Private.Windows.Core/src/System/IO/BinaryReaderExtensions.cs b/src/System.Private.Windows.Core/src/System/IO/BinaryReaderExtensions.cs index 77a7341c7ea..7ee9ca307e6 100644 --- a/src/System.Private.Windows.Core/src/System/IO/BinaryReaderExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/IO/BinaryReaderExtensions.cs @@ -1,19 +1,21 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if NET using System.Buffers.Binary; +#endif using System.Globalization; using System.Runtime.Serialization; namespace System.IO; -internal static class BinaryReaderExtensions +internal static partial class BinaryReaderExtensions { /// /// Reads a binary formatted from the given . /// /// The data was invalid. - internal static unsafe DateTime ReadDateTime(this BinaryReader reader) + internal static DateTime ReadDateTime(this BinaryReader reader) => CreateDateTimeFromData(reader.ReadInt64()); /// @@ -124,6 +126,7 @@ internal static unsafe T[] ReadPrimitiveArray(this BinaryReader reader, int c throw new SerializationException("Not enough data to fill array."); } +#if NET if (sizeof(T) != 1 && !BitConverter.IsLittleEndian) { if (sizeof(T) == 2) @@ -146,6 +149,7 @@ internal static unsafe T[] ReadPrimitiveArray(this BinaryReader reader, int c throw new InvalidOperationException($"Cannot read primitives of {typeof(T).Name}."); } } +#endif } return array; @@ -226,6 +230,12 @@ internal static unsafe void WritePrimitives(this BinaryWriter writer, IReadOn throw new ArgumentException($"Cannot write primitives of {typeof(T).Name}.", nameof(T)); } +#if NETFRAMEWORK + // Always take the slow path on .NET Framework. This could be optimized further if necessary as the + // endianness is guaranteed to be little endian, but it's not clear if it's worth the effort without + // a demonstrated need. + WritePrimitiveCollection(writer, values); +#else ReadOnlySpan span; if (values is T[] array) { @@ -263,6 +273,7 @@ internal static unsafe void WritePrimitives(this BinaryWriter writer, IReadOn // endianness in one pass with BinaryPrimitives.ReverseEndianness (see ReadPrimitiveArray). Probably not // worth doing without measuring to see how much of a difference it actualy makes. WritePrimitiveCollection(writer, values); +#endif static void WritePrimitiveCollection(BinaryWriter writer, IReadOnlyList values) { diff --git a/src/System.Private.Windows.Core/src/System/IO/StreamExtensions.cs b/src/System.Private.Windows.Core/src/System/IO/StreamExtensions.cs index 7fce4f10306..03e2ff4cc56 100644 --- a/src/System.Private.Windows.Core/src/System/IO/StreamExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/IO/StreamExtensions.cs @@ -6,7 +6,7 @@ namespace System.IO; -internal static class StreamExtensions +internal static partial class StreamExtensions { /// /// Get a wrapper around the given . Use the return value @@ -26,6 +26,15 @@ internal static ComScope ToIStream(this Stream stream, bool makeSeekabl /// if successful. internal static unsafe HRESULT SaveStreamToHGLOBAL(this Stream stream, ref HGLOBAL hglobal) { + int size = checked((int)stream.Length); + + if (size == 0) + { + // A zero-size GMEM_MOVEABLE allocation creates a discarded handle + // that cannot be locked. Leave the existing HGLOBAL as-is. + return HRESULT.S_OK; + } + if (!hglobal.IsNull) { HGLOBAL freed = PInvokeCore.GlobalFree(hglobal); @@ -35,7 +44,6 @@ internal static unsafe HRESULT SaveStreamToHGLOBAL(this Stream stream, ref HGLOB } } - int size = checked((int)stream.Length); hglobal = PInvokeCore.GlobalAlloc(GLOBAL_ALLOC_FLAGS.GMEM_MOVEABLE, (uint)size); if (hglobal.IsNull) { diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs index a1941e58b8f..26946591e67 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs @@ -752,6 +752,7 @@ static bool Write(Stream stream, object value) } } +#if NET public static bool TryWriteJsonData(Stream stream, object value) { if (value is not IJsonData jsonData) @@ -772,6 +773,7 @@ public static bool TryWriteJsonData(Stream stream, object value) return true; } +#endif /// /// Simple wrapper to ensure the is reset to its original position if the diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/IRecord.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/IRecord.cs index 8e428d5688b..4e451bae1e5 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/IRecord.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/IRecord.cs @@ -11,9 +11,7 @@ internal interface IRecord /// /// Id for the record, or null if the record has no id. /// - Id Id => Id.Null; - - static virtual RecordType RecordType { get; } + Id Id { get; } } /// diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/MessageEnd.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/MessageEnd.cs index bec2f7d5a43..f367648d708 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/MessageEnd.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/MessageEnd.cs @@ -8,6 +8,8 @@ namespace System.Private.Windows.BinaryFormat.Serializer; /// internal sealed class MessageEnd : IRecord { + public Id Id => Id.Null; + public static MessageEnd Instance { get; } = new(); private MessageEnd() { } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/NullRecord.ObjectNullMultiple.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/NullRecord.ObjectNullMultiple.cs index a1f1cea58c0..fd1f50e5ba6 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/NullRecord.ObjectNullMultiple.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/NullRecord.ObjectNullMultiple.cs @@ -15,9 +15,7 @@ internal abstract partial class NullRecord /// /// /// - internal sealed class ObjectNullMultiple : - NullRecord, - IRecord + internal sealed class ObjectNullMultiple : NullRecord { public static RecordType RecordType => RecordType.ObjectNullMultiple; diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/NullRecord.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/NullRecord.cs index 94179add0ea..9a935d7d7f5 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/NullRecord.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/NullRecord.cs @@ -6,10 +6,12 @@ namespace System.Private.Windows.BinaryFormat.Serializer; /// /// Base class for null records. /// -internal abstract partial class NullRecord +internal abstract partial class NullRecord : IRecord { private Count _count; + public Id Id => Id.Null; + public virtual Count NullCount { get => _count; diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/RecordMap.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/RecordMap.cs deleted file mode 100644 index 166d5bd50a9..00000000000 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/RecordMap.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Private.Windows.BinaryFormat.Serializer; - -/// -/// Map of records that ensures that IDs are only entered once. -/// -internal class RecordMap : IReadOnlyRecordMap -{ - private readonly Dictionary _recordMap = []; - - public IRecord this[Id id] => _recordMap[id]; - - public void AddRecord(IRecord record) - { - Id id = record.Id; - if (id.IsNull) - { - return; - } - - if ((int)id < 0) - { - // Negative record Ids should never be referenced. Duplicate negative ids can be - // exported by the writer. The root object Id can be negative. - _recordMap.TryAdd(id, record); - return; - } - - _recordMap.Add(id, record); - } -} diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/SerializationHeader.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/SerializationHeader.cs index 3fd11976fdc..637c625fb1f 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/SerializationHeader.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Serializer/SerializationHeader.cs @@ -15,6 +15,8 @@ namespace System.Private.Windows.BinaryFormat.Serializer; /// internal sealed class SerializationHeader : IRecord { + public Id Id => Id.Null; + /// /// The id of the root object record. /// diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs index 8fa0a9c4db4..8b3aa8e4174 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs @@ -18,7 +18,9 @@ internal class CoreNrbfSerializer : INrbfSerializer public static bool TryWriteObject(Stream stream, object value) => BinaryFormatWriter.TryWriteFrameworkObject(stream, value) +#if NET || BinaryFormatWriter.TryWriteJsonData(stream, value) +#endif || BinaryFormatWriter.TryWriteDrawingPrimitivesObject(stream, value); public static bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) => @@ -158,4 +160,11 @@ public static bool IsFullySupportedType(Type type) => || type == typeof(Point) || type == typeof(Size) || type == typeof(Color); + +#if NETFRAMEWORK + bool INrbfSerializer.TryWriteObject(Stream stream, object value) => TryWriteObject(stream, value); + bool INrbfSerializer.TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) => TryGetObject(record, out value); + bool INrbfSerializer.TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) => TryBindToType(typeName, out type); + bool INrbfSerializer.IsFullySupportedType(Type type) => IsFullySupportedType(type); +#endif } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/INrbfSerializer.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/INrbfSerializer.cs index e6a0758a240..29b89286141 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/INrbfSerializer.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/INrbfSerializer.cs @@ -12,17 +12,26 @@ internal interface INrbfSerializer /// /// Tries to write supported objects to a stream. /// - static abstract bool TryWriteObject(Stream stream, object value); +#if NET + static abstract +#endif + bool TryWriteObject(Stream stream, object value); /// /// Tries to read supported objects from a . /// - static abstract bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value); +#if NET + static abstract +#endif + bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value); /// /// Tries to bind the given type name to a supported type. /// - static abstract bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type); +#if NET + static abstract +#endif + bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type); /// /// Returns if the type is fully supported by the serializer. @@ -54,5 +63,8 @@ internal interface INrbfSerializer /// Hashtable is one that we return false for, for example. While we handle ones that have nothing beyond /// primitives (such as `string` to `string`), we don't support anything else, including custom comparers. /// - static abstract bool IsFullySupportedType(Type type); +#if NET + static abstract +#endif + bool IsFullySupportedType(Type type); } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs index dfc2ba8094c..e3fea336b95 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs @@ -5,11 +5,14 @@ using System.ComponentModel; using System.Drawing; using System.Formats.Nrbf; -using System.Private.Windows.BinaryFormat; using System.Reflection; -using System.Reflection.Metadata; using System.Runtime.Serialization; + +#if NET +using System.Private.Windows.BinaryFormat; +using System.Reflection.Metadata; using System.Text.Json; +#endif namespace System.Private.Windows.Nrbf; @@ -235,7 +238,11 @@ static bool Get(SerializationRecord record, [NotNullWhen(true)] out object? valu } ConstructorInfo? ctor = typeof(Color).GetConstructor( - BindingFlags.Instance | BindingFlags.NonPublic, [typeof(long), typeof(short), typeof(string), typeof(KnownColor)]); + BindingFlags.Instance | BindingFlags.NonPublic, + binder: null, + [typeof(long), typeof(short), typeof(string), typeof(KnownColor)], + modifiers: null); + if (ctor is null) { return false; @@ -543,6 +550,7 @@ public static bool TryGetDrawingPrimitivesObject( private static bool IsPrimitiveArrayRecord(SerializationRecord serializationRecord) => serializationRecord.RecordType is SerializationRecordType.ArraySingleString or SerializationRecordType.ArraySinglePrimitive; +#if OLE_JSON /// /// Tries to deserialize this object if it was serialized as JSON. /// @@ -615,4 +623,5 @@ internal static (bool isJsonData, bool isValidType) TryGetObjectFromJson( return (isJsonData: true, isValidType: @object is not null); } +#endif } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/BinaryFormatUtilities.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/BinaryFormatUtilities.cs index 775dd120bae..1cf4022be2c 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/BinaryFormatUtilities.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/BinaryFormatUtilities.cs @@ -10,8 +10,12 @@ namespace System.Private.Windows.Ole; -internal static class BinaryFormatUtilities where TNrbfSerializer : INrbfSerializer +internal static partial class BinaryFormatUtilities where TNrbfSerializer : INrbfSerializer { +#if NETFRAMEWORK + private static readonly INrbfSerializer s_nrbfSerializer = Activator.CreateInstance(); +#endif + /// /// Writes an object to the provided memory stream. /// @@ -28,7 +32,11 @@ internal static void WriteObjectToStream(MemoryStream stream, object data, strin try { +#if NET if (TNrbfSerializer.TryWriteObject(stream, data)) +#else + if (s_nrbfSerializer.TryWriteObject(stream, data!)) +#endif { return; } @@ -116,7 +124,11 @@ record = stream.DecodeNrbf(); if (record is not null) { // Try our implicit deserialization. +#if NET if (TNrbfSerializer.TryBindToType(record.TypeName, out Type? type)) +#else + if (s_nrbfSerializer.TryBindToType(record.TypeName, out Type? type)) +#endif { if (request.TypedRequest // If we can't match the root exactly, then we fall back to the binder. @@ -125,12 +137,20 @@ record = stream.DecodeNrbf(); return false; } +#if NET if (TNrbfSerializer.TryGetObject(record, out value)) +#else + if (s_nrbfSerializer.TryGetObject(record, out value)) +#endif { @object = (T)value; return true; } +#if NET else if (TNrbfSerializer.IsFullySupportedType(type)) +#else + else if (s_nrbfSerializer.IsFullySupportedType(type)) +#endif { // The serializer fully supports this type, but can't deserialize it. // Don't let it fall through to the BinaryFormatter. @@ -138,6 +158,7 @@ record = stream.DecodeNrbf(); } } +#if NET if (type is null) { // Serializer didn't recognize the type, look for and deserialize a JSON object. @@ -147,6 +168,7 @@ record = stream.DecodeNrbf(); return isValidType; } } +#endif // JSON type info is nested, so this has to come after the JSON attempt. if (request.TypedRequest && !typeof(T).Matches(record.TypeName, TypeNameComparison.AllButAssemblyVersion)) diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/ClipboardCore.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/ClipboardCore.cs index 994bbc07597..7ec19215612 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/ClipboardCore.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/ClipboardCore.cs @@ -10,8 +10,16 @@ namespace System.Private.Windows.Ole; /// Contains platform-agnostic clipboard operations. /// internal static unsafe class ClipboardCore +#if NET where TOleServices : IOleServices +#else + where TOleServices : IOleServices, new() +#endif { +#if NETFRAMEWORK + private static readonly TOleServices s_oleServices = new(); +#endif + /// /// The number of times to retry OLE clipboard operations. /// @@ -30,12 +38,20 @@ internal static HRESULT Clear( int retryTimes = OleRetryCount, int retryDelay = OleRetryDelay) { +#if NET TOleServices.EnsureThreadState(); +#else + s_oleServices.EnsureThreadState(); +#endif HRESULT result; int retryCount = retryTimes; +#if NET while ((result = TOleServices.OleSetClipboard(null)).Failed) +#else + while ((result = s_oleServices.OleSetClipboard(null)).Failed) +#endif { if (--retryCount < 0) { @@ -56,12 +72,20 @@ internal static HRESULT Flush( int retryTimes = OleRetryCount, int retryDelay = OleRetryDelay) { +#if NET TOleServices.EnsureThreadState(); +#else + s_oleServices.EnsureThreadState(); +#endif HRESULT result; int retryCount = retryTimes; +#if NET while ((result = TOleServices.OleFlushClipboard()).Failed) +#else + while ((result = s_oleServices.OleFlushClipboard()).Failed) +#endif { if (--retryCount < 0) { @@ -88,7 +112,11 @@ internal static HRESULT SetData( int retryTimes = OleRetryCount, int retryDelay = OleRetryDelay) { +#if NET TOleServices.EnsureThreadState(); +#else + s_oleServices.EnsureThreadState(); +#endif ArgumentOutOfRangeException.ThrowIfNegative(retryTimes); ArgumentOutOfRangeException.ThrowIfNegative(retryDelay); @@ -97,7 +125,11 @@ internal static HRESULT SetData( HRESULT result; int retryCount = retryTimes; +#if NET while ((result = TOleServices.OleSetClipboard(iDataObject)).Failed) +#else + while ((result = s_oleServices.OleSetClipboard(iDataObject)).Failed) +#endif { if (--retryCount < 0) { @@ -110,7 +142,11 @@ internal static HRESULT SetData( if (copy) { retryCount = retryTimes; +#if NET while ((result = TOleServices.OleFlushClipboard()).Failed) +#else + while ((result = s_oleServices.OleFlushClipboard()).Failed) +#endif { if (--retryCount < 0) { @@ -137,7 +173,11 @@ internal static HRESULT TryGetData( int retryTimes = OleRetryCount, int retryDelay = OleRetryDelay) { +#if NET TOleServices.EnsureThreadState(); +#else + s_oleServices.EnsureThreadState(); +#endif proxyDataObject = new(null); originalObject = null; @@ -145,7 +185,11 @@ internal static HRESULT TryGetData( int retryCount = retryTimes; HRESULT result; +#if NET while ((result = TOleServices.OleGetClipboard(proxyDataObject)).Failed) +#else + while ((result = s_oleServices.OleGetClipboard(proxyDataObject)).Failed) +#endif { if (--retryCount < 0) { @@ -155,6 +199,13 @@ internal static HRESULT TryGetData( Thread.Sleep(millisecondsTimeout: retryDelay); } +#if NETFRAMEWORK + // We don't have ComWrappers on .NET Framework, use built-in COM interop to get an RCW, which will be castable + // to the original object if it came from a .NET interop generated CCW. + using var unknown = proxyDataObject.Query(); + originalObject = Marshal.GetObjectForIUnknown((nint)unknown.Value); + return result; +#else // OleGetClipboard always returns a proxy. The proxy forwards all IDataObject method calls to the real data object, // without giving out the real data object. If the data placed on the clipboard is not one of our CCWs or the clipboard // has been flushed, a wrapper around the proxy for us to use will be given. However, if the data placed on @@ -171,6 +222,7 @@ internal static HRESULT TryGetData( } return result; +#endif } /// @@ -212,7 +264,7 @@ internal static HRESULT GetDataObject( out TIDataObject? dataObject, int retryTimes = OleRetryCount, int retryDelay = OleRetryDelay) - where TDataObject : class, IDataObjectInternal, TIDataObject + where TDataObject : class, IDataObjectInternal, TIDataObject, new() where TIDataObject : class { dataObject = default; @@ -242,7 +294,11 @@ internal static HRESULT GetDataObject( // Original data given wasn't an IDataObject, give the proxy value back. // (Creating the DataObject will add a reference to the proxy.) +#if NET dataObject = TDataObject.Create(proxyDataObject.Value); +#else + dataObject = DataObjectFactory.Instance.Create(proxyDataObject.Value); +#endif } return result; @@ -286,13 +342,21 @@ or DataFormatNames.Html or DataFormatNames.FileNameAnsi or DataFormatNames.FileNameUnicode => typeof(string[]) == type, +#if NET _ => TOleServices.IsValidTypeForFormat(type, format) +#else + _ => s_oleServices.IsValidTypeForFormat(type, format) +#endif }; } internal static void SetFileDropList(StringCollection filePaths) { +#if NET IComVisibleDataObject dataObject = TOleServices.CreateDataObject(); +#else + IComVisibleDataObject dataObject = s_oleServices.CreateDataObject(); +#endif dataObject.SetFileDropList(filePaths); SetData(dataObject, copy: true); } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.ManagedToNativeAdapter.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.ManagedToNativeAdapter.cs index 59df02ddb31..63972ab4b99 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.ManagedToNativeAdapter.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.ManagedToNativeAdapter.cs @@ -13,10 +13,15 @@ namespace System.Private.Windows.Ole; internal unsafe partial class Composition { +#if NET + // Workaround SA1001 white space warnings. + private sealed partial class ManagedToNativeAdapter : IManagedWrapper { } +#endif + /// /// Maps to . /// - private sealed unsafe class ManagedToNativeAdapter : IDataObject.Interface, IManagedWrapper + private sealed unsafe partial class ManagedToNativeAdapter : IDataObject.Interface { private const int DATA_S_SAMEFORMATETC = 0x00040130; @@ -111,12 +116,12 @@ public HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) string format = DataFormatsCore.GetOrAddFormat(pformatetc->cfFormat).Name; - if (!_dataObject.GetDataPresent(format)) + if (!_dataObject.GetDataPresent(format, autoConvert: false)) { return HRESULT.DV_E_FORMATETC; } - if (_dataObject.GetData(format) is not object data) + if (_dataObject.GetData(format, autoConvert: false) is not object data) { return HRESULT.E_UNEXPECTED; } @@ -147,7 +152,11 @@ public HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) } } +#if NET return TOleServices.GetDataHere(format, data, pformatetc, pmedium); +#else + return s_oleServices.GetDataHere(format, data, pformatetc, pmedium); +#endif } public HRESULT QueryGetData(FORMATETC* pformatetc) @@ -172,7 +181,8 @@ public HRESULT QueryGetData(FORMATETC* pformatetc) return HRESULT.S_FALSE; } - if (!_dataObject.GetDataPresent(DataFormatsCore.GetOrAddFormat(pformatetc->cfFormat).Name)) + // The NativeToManagedAdapter handles the autoConvert behavior. + if (!_dataObject.GetDataPresent(DataFormatsCore.GetOrAddFormat(pformatetc->cfFormat).Name, autoConvert: false)) { return HRESULT.DV_E_FORMATETC; } @@ -310,7 +320,7 @@ static HRESULT SaveFileListToHGLOBAL(HGLOBAL hglobal, string[] files) return HRESULT.S_OK; } - if (hglobal == 0) + if (hglobal == (nint)0) { return HRESULT.E_INVALIDARG; } @@ -412,6 +422,7 @@ static HRESULT SaveUtf8ToHGLOBAL(HGLOBAL hglobal, string value) } Span span = buffer.AsSpan(); + byteCount = Encoding.UTF8.GetBytes(value, span); // Null terminate diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs index ddc62c1d14c..a96e2bd36b4 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs @@ -11,6 +11,25 @@ namespace System.Private.Windows.Ole; +internal static class ThreadOleServices +{ + [ThreadStatic] +#pragma warning disable IDE1006 // Naming Styles + public static IOleServices? OleServices; + + [ThreadStatic] + public static Nrbf.INrbfSerializer? NrbfSerializer; + + [ThreadStatic] + public static DataFormatFactory? DataFormatFactory; +#pragma warning restore IDE1006 // Naming Styles +} + +internal abstract class DataFormatFactory +{ + public abstract object CreateDataFormat(string format); +} + internal unsafe partial class Composition { /// @@ -96,7 +115,7 @@ private static bool TryGetDataFromHGLOBAL( [NotNullWhen(true)] out T? data) { data = default; - if (hglobal == 0) + if (hglobal == (nint)0) { return false; } @@ -147,7 +166,13 @@ private static unsafe MemoryStream ReadByteStreamFromHGLOBAL(HGLOBAL hglobal, ou try { int size = checked((int)PInvokeCore.GlobalSize(hglobal)); - byte[] bytes = GC.AllocateUninitializedArray(size); + byte[] bytes = +#if NET + GC.AllocateUninitializedArray(size); +#else + new byte[size]; +#endif + Marshal.Copy((nint)buffer, bytes, 0, size); int index = 0; @@ -204,7 +229,7 @@ private static unsafe string ReadStringFromHGLOBAL(HGLOBAL hglobal, bool unicode } chars = chars[..nullIndex]; - return new string(chars); + return chars.ToString(); } else { @@ -319,7 +344,11 @@ private static bool TryGetObjectFromDataObject( try { // Try to get platform specific data first. +#if NET if (TOleServices.TryGetObjectFromDataObject(dataObject, request.Format, out data)) +#else + if (s_oleServices.TryGetObjectFromDataObject(dataObject, request.Format, out data)) +#endif { return true; } @@ -472,7 +501,11 @@ private static void ThrowIfFormatAndTypeRequireResolver(string format) { // Restricted format is either read directly from the HGLOBAL or serialization record is read manually. if (!DataFormatNames.IsPredefinedFormat(format) +#if NET && !TOleServices.AllowTypeWithoutResolver() +#else + && !s_oleServices.AllowTypeWithoutResolver() +#endif // This check is a convenience for simple usages if TryGetData APIs that don't take the resolver. && IsUnboundedType()) { @@ -586,6 +619,11 @@ private bool TryGetDataInternal( public bool GetDataPresent(string format, bool autoConvert) { + if (string.IsNullOrWhiteSpace(format)) + { + return false; + } + bool dataPresent = GetDataPresentInner(format); if (dataPresent || !autoConvert) diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.RuntimeToNativeAdapter.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.RuntimeToNativeAdapter.cs index a632d4db657..a4cdab9c2c9 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.RuntimeToNativeAdapter.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.RuntimeToNativeAdapter.cs @@ -8,10 +8,15 @@ namespace System.Private.Windows.Ole; internal unsafe partial class Composition { +#if NET + // Workaround SA1001 white space warnings. + private sealed partial class RuntimeToNativeAdapter : Com.IManagedWrapper { } +#endif + /// /// Maps the runtime to the native . /// - private sealed class RuntimeToNativeAdapter : Com.IDataObject.Interface, IDataObject, Com.IManagedWrapper + private sealed partial class RuntimeToNativeAdapter : Com.IDataObject.Interface, IDataObject { private readonly IDataObject _runtimeDataObject; diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.cs index d167ae654f8..b28df416012 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.cs @@ -3,10 +3,13 @@ using System.Private.Windows.Nrbf; using System.Reflection.Metadata; -using System.Text.Json; using Windows.Win32.System.Com; using ComTypes = System.Runtime.InteropServices.ComTypes; +#if NET +using System.Text.Json; +#endif + namespace System.Private.Windows.Ole; /// @@ -15,10 +18,20 @@ namespace System.Private.Windows.Ole; /// internal sealed unsafe partial class Composition : IDataObjectInternal, IDataObject.Interface, ComTypes.IDataObject +#if NET where TDataFormat : IDataFormat where TOleServices : IOleServices where TNrbfSerializer : INrbfSerializer +#else + where TDataFormat : IDataFormat, new() + where TOleServices : IOleServices, new() + where TNrbfSerializer : INrbfSerializer, new() +#endif { +#if NETFRAMEWORK + private static readonly TOleServices s_oleServices = new(); +#endif + private const TYMED AllowedTymeds = TYMED.TYMED_HGLOBAL | TYMED.TYMED_ISTREAM | TYMED.TYMED_GDI; // We use this to identify that a stream is actually a serialized object. On read, we don't know if the contents @@ -47,7 +60,11 @@ private Composition(IDataObjectInternal managedDataObject, IDataObject.Interface internal static Composition Create() => Create(new DataStore()); internal static Composition Create(object data) +#if NET where TDataObject : class, IDataObjectInternal, TIDataObject +#else + where TDataObject : class, IDataObjectInternal, TIDataObject, new() +#endif where TIDataObject : class { if (data is IDataObjectInternal internalDataObject) @@ -56,7 +73,11 @@ internal static Composition Create.Instance.Wrap(iDataObject)); +#endif } else if (data is ComTypes.IDataObject comDataObject) { @@ -96,6 +117,7 @@ internal static Composition Create(C return new(nativeToWinForms, runtimeToNative, runtimeDataObject); } +#if NET /// /// Stores the data in the specified format using the . /// @@ -130,6 +152,7 @@ public void SetDataAsJson(T data, string? format = default) autoConvert: false, DataObjectCore.TryJsonSerialize(format, data)); } +#endif #region IDataObjectInternal public object? GetData(string format, bool autoConvert) @@ -137,7 +160,11 @@ public void SetDataAsJson(T data, string? format = default) object? result = ManagedDataObject.GetData(format, autoConvert); // Avoid exposing our internal JsonData - return result is IJsonData json ? json.Deserialize() : result; + return +#if NET + result is IJsonData json ? json.Deserialize() : +#endif + result; } public object? GetData(string format) => ManagedDataObject.GetData(format); diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataFormatsCore.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataFormatsCore.cs index e31f972f590..5bf75be93e5 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataFormatsCore.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataFormatsCore.cs @@ -6,8 +6,16 @@ namespace System.Private.Windows.Ole; -internal static partial class DataFormatsCore where T : IDataFormat +#if NET +internal static class DataFormatsCore where T : IDataFormat +#else +internal static class DataFormatsCore where T : IDataFormat, new() +#endif { +#if NETFRAMEWORK + private static readonly IDataFormat s_dataFormat = new T(); +#endif + private static List? s_formatList; private static T[]? s_predefinedFormatList; @@ -41,7 +49,7 @@ internal static T GetOrAddFormat(string format) } s_formatList ??= []; - T newFormat = T.Create(format, (int)formatId); + T newFormat = Create(format, (int)formatId); s_formatList.Add(newFormat); return newFormat; } @@ -106,7 +114,7 @@ internal static unsafe T GetOrAddFormat(int id) name ??= $"Format{shortId}"; s_formatList ??= []; - T newFormat = T.Create(name, shortId); + T newFormat = Create(name, shortId); s_formatList.Add(newFormat); return newFormat; @@ -143,22 +151,29 @@ private static void EnsurePredefined() s_predefinedFormatList ??= [ // Text name Win32 format ID - T.Create(DataFormatNames.UnicodeText, (int)CLIPBOARD_FORMAT.CF_UNICODETEXT), - T.Create(DataFormatNames.Text, (int)CLIPBOARD_FORMAT.CF_TEXT), - T.Create(DataFormatNames.Bitmap, (int)CLIPBOARD_FORMAT.CF_BITMAP), - T.Create(DataFormatNames.Wmf, (int)CLIPBOARD_FORMAT.CF_METAFILEPICT), - T.Create(DataFormatNames.Emf, (int)CLIPBOARD_FORMAT.CF_ENHMETAFILE), - T.Create(DataFormatNames.Dif, (int)CLIPBOARD_FORMAT.CF_DIF), - T.Create(DataFormatNames.Tiff, (int)CLIPBOARD_FORMAT.CF_TIFF), - T.Create(DataFormatNames.OemText, (int)CLIPBOARD_FORMAT.CF_OEMTEXT), - T.Create(DataFormatNames.Dib, (int)CLIPBOARD_FORMAT.CF_DIB), - T.Create(DataFormatNames.Palette, (int)CLIPBOARD_FORMAT.CF_PALETTE), - T.Create(DataFormatNames.PenData, (int)CLIPBOARD_FORMAT.CF_PENDATA), - T.Create(DataFormatNames.Riff, (int)CLIPBOARD_FORMAT.CF_RIFF), - T.Create(DataFormatNames.WaveAudio, (int)CLIPBOARD_FORMAT.CF_WAVE), - T.Create(DataFormatNames.SymbolicLink, (int)CLIPBOARD_FORMAT.CF_SYLK), - T.Create(DataFormatNames.FileDrop, (int)CLIPBOARD_FORMAT.CF_HDROP), - T.Create(DataFormatNames.Locale, (int)CLIPBOARD_FORMAT.CF_LOCALE) + Create(DataFormatNames.UnicodeText, (int)CLIPBOARD_FORMAT.CF_UNICODETEXT), + Create(DataFormatNames.Text, (int)CLIPBOARD_FORMAT.CF_TEXT), + Create(DataFormatNames.Bitmap, (int)CLIPBOARD_FORMAT.CF_BITMAP), + Create(DataFormatNames.Wmf, (int)CLIPBOARD_FORMAT.CF_METAFILEPICT), + Create(DataFormatNames.Emf, (int)CLIPBOARD_FORMAT.CF_ENHMETAFILE), + Create(DataFormatNames.Dif, (int)CLIPBOARD_FORMAT.CF_DIF), + Create(DataFormatNames.Tiff, (int)CLIPBOARD_FORMAT.CF_TIFF), + Create(DataFormatNames.OemText, (int)CLIPBOARD_FORMAT.CF_OEMTEXT), + Create(DataFormatNames.Dib, (int)CLIPBOARD_FORMAT.CF_DIB), + Create(DataFormatNames.Palette, (int)CLIPBOARD_FORMAT.CF_PALETTE), + Create(DataFormatNames.PenData, (int)CLIPBOARD_FORMAT.CF_PENDATA), + Create(DataFormatNames.Riff, (int)CLIPBOARD_FORMAT.CF_RIFF), + Create(DataFormatNames.WaveAudio, (int)CLIPBOARD_FORMAT.CF_WAVE), + Create(DataFormatNames.SymbolicLink, (int)CLIPBOARD_FORMAT.CF_SYLK), + Create(DataFormatNames.FileDrop, (int)CLIPBOARD_FORMAT.CF_HDROP), + Create(DataFormatNames.Locale, (int)CLIPBOARD_FORMAT.CF_LOCALE) ]; } + + private static T Create(string name, int id) => +#if NET + T.Create(name, id); +#else + s_dataFormat.Create(name, id); +#endif } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectCore.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectCore.cs index 1c93b5461ee..a360bafef22 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectCore.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectCore.cs @@ -3,9 +3,10 @@ namespace System.Private.Windows.Ole; -internal static unsafe class DataObjectCore +internal static class DataObjectCore where TDataObject : IComVisibleDataObject { +#if NET /// /// JSON serialize the data only if the format is not a restricted deserialization format and the data is not an intrinsic type. /// @@ -31,4 +32,5 @@ internal static IJsonData TryJsonSerialize(string format, T data) return IJsonData.Create(data); } +#endif } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectExtensions.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectExtensions.cs index 0626c90fd5d..1ac74cd6a9e 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectExtensions.cs @@ -7,26 +7,29 @@ namespace System.Private.Windows.Ole; internal static class DataObjectExtensions { - internal static void SetFileDropList(this IComVisibleDataObject dataObject, StringCollection filePaths) + extension(IComVisibleDataObject dataObject) { - if (filePaths.OrThrowIfNull().Count == 0) + internal void SetFileDropList(StringCollection filePaths) { - throw new ArgumentException(SR.CollectionEmptyException); - } + if (filePaths.OrThrowIfNull().Count == 0) + { + throw new ArgumentException(SR.CollectionEmptyException); + } - // Validate the paths to make sure they don't contain invalid characters - string[] filePathsArray = new string[filePaths.Count]; - filePaths.CopyTo(filePathsArray, 0); + // Validate the paths to make sure they don't contain invalid characters + string[] filePathsArray = new string[filePaths.Count]; + filePaths.CopyTo(filePathsArray, 0); - foreach (string path in filePathsArray) - { - // These are the only error states for Path.GetFullPath - if (string.IsNullOrEmpty(path) || path.Contains('\0')) + foreach (string path in filePathsArray) { - throw new ArgumentException(string.Format(SR.Clipboard_InvalidPath, path ?? "", nameof(filePaths))); + // These are the only error states for Path.GetFullPath + if (string.IsNullOrEmpty(path) || path.Contains('\0')) + { + throw new ArgumentException(string.Format(SR.Clipboard_InvalidPath, path ?? "", nameof(filePaths))); + } } - } - dataObject.SetData(DataFormatNames.FileDrop, autoConvert: true, filePathsArray); + dataObject.SetData(DataFormatNames.FileDrop, autoConvert: true, filePathsArray); + } } } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataStore.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataStore.cs index b317fc861aa..44bd9a9a58a 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataStore.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataStore.cs @@ -7,8 +7,17 @@ namespace System.Private.Windows.Ole; -internal sealed partial class DataStore : IDataObjectInternal where TOleServices : IOleServices +internal sealed partial class DataStore +#if NET + : IDataObjectInternal where TOleServices : IOleServices +#else + : IDataObjectInternal where TOleServices : IOleServices, new() +#endif { +#if NETFRAMEWORK + private static readonly TOleServices s_oleServices = new(); +#endif + private readonly Dictionary _mappedData = new(BackCompatibleStringComparer.Default); private bool TryGetDataInternal( @@ -57,11 +66,13 @@ bool TryGetData(string format, ref bool autoConvert, [NotNullWhen(true)] out T? data = value; return true; } +#if OLE_JSON else if (entry.Data is JsonData jsonData) { data = (T)jsonData.Deserialize(); return true; } +#endif } data = default; @@ -82,7 +93,11 @@ bool TryGetData(string format, ref bool autoConvert, [NotNullWhen(true)] out T? public void SetData(string format, bool autoConvert, object? data) { ArgumentException.ThrowIfNullOrWhiteSpace(format, nameof(format)); +#if NET TOleServices.ValidateDataStoreData(ref format, autoConvert, data); +#else + s_oleServices.ValidateDataStoreData(ref format, autoConvert, data); +#endif _mappedData[format] = new DataStoreEntry(data, autoConvert); } @@ -150,7 +165,13 @@ public string[] GetFormats(bool autoConvert) { // Since we are only adding elements to the HashSet, the order will be preserved. int definedCount = definedFormats.Length; - HashSet distinctFormats = new(definedCount); + HashSet distinctFormats = +#if NET + new(definedCount); +#else + new(); +#endif + for (int i = 0; i < definedCount; i++) { string current = definedFormats[i]; diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DragDropHelper.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DragDropHelper.cs index da4bd8982cf..c9fc20099d2 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DragDropHelper.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DragDropHelper.cs @@ -21,8 +21,16 @@ namespace System.Private.Windows.Ole; /// internal static unsafe class DragDropHelper where TOleServices : IOleServices +#if NET where TDataFormat : IDataFormat +#else + where TDataFormat : IDataFormat, new() +#endif { +#if NETFRAMEWORK + private static readonly TOleServices s_oleServices = Activator.CreateInstance(); +#endif + /// /// Sets the drop object image and accompanying text back to the default. /// @@ -204,14 +212,14 @@ private static unsafe void SetBooleanFormat(IComVisibleDataObject dataObject, st if (medium.hGlobal.IsNull) { - throw new Win32Exception(Marshal.GetLastSystemError(), SR.ExternalException); + throw new Win32Exception(Marshal.GetLastWin32Error(), SR.ExternalException); } void* basePtr = PInvokeCore.GlobalLock(medium.hGlobal); if (basePtr is null) { PInvokeCore.GlobalFree(medium.hGlobal); - throw new Win32Exception(Marshal.GetLastSystemError(), SR.ExternalException); + throw new Win32Exception(Marshal.GetLastWin32Error(), SR.ExternalException); } *(BOOL*)basePtr = value; @@ -362,14 +370,14 @@ public static unsafe void SetDropDescription( if (medium.hGlobal.IsNull) { - throw new Win32Exception(Marshal.GetLastSystemError(), SR.ExternalException); + throw new Win32Exception(Marshal.GetLastWin32Error(), SR.ExternalException); } void* basePtr = PInvokeCore.GlobalLock(medium.hGlobal); if (basePtr is null) { PInvokeCore.GlobalFree(medium.hGlobal); - throw new Win32Exception(Marshal.GetLastSystemError(), SR.ExternalException); + throw new Win32Exception(Marshal.GetLastWin32Error(), SR.ExternalException); } DROPDESCRIPTION* pDropDescription = (DROPDESCRIPTION*)basePtr; @@ -441,7 +449,11 @@ private static void SetUsingDefaultDragImage(IComVisibleDataObject dataObject, b private static bool TryGetDragDropHelper(TDragHelper** dragDropHelper) where TDragHelper : unmanaged, IComIID { +#if NET TOleServices.EnsureThreadState(); +#else + s_oleServices.EnsureThreadState(); +#endif HRESULT hr = PInvokeCore.CoCreateInstance( CLSID.DragDropHelper, diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/FormatEnumerator.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/FormatEnumerator.cs index 701ef5e9cde..9200423c153 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/FormatEnumerator.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/FormatEnumerator.cs @@ -6,10 +6,15 @@ namespace System.Private.Windows.Ole; +#if NET +// Workaround SA1001 white space warnings. +internal sealed partial class FormatEnumerator : IManagedWrapper { } +#endif + /// /// Part of IComDataObject, used to interop with OLE. /// -internal unsafe class FormatEnumerator : ComTypes.IEnumFORMATETC, IEnumFORMATETC.Interface, IManagedWrapper +internal sealed unsafe partial class FormatEnumerator : ComTypes.IEnumFORMATETC, IEnumFORMATETC.Interface { // Want to keep a reference to the data object to ensure it's not collected. private readonly IDataObjectInternal _dataObject; @@ -40,7 +45,7 @@ public FormatEnumerator(IDataObjectInternal dataObject, Func getFor { cfFormat = (short)(ushort)getFormatId(format), dwAspect = ComTypes.DVASPECT.DVASPECT_CONTENT, - ptd = 0, + ptd = (nint)0, lindex = -1, tymed = format == DataFormatNames.Bitmap ? ComTypes.TYMED.TYMED_GDI @@ -66,7 +71,7 @@ public int Next(int celt, ComTypes.FORMATETC[] rgelt, int[]? pceltFetched) cfFormat = current.cfFormat, tymed = current.tymed, dwAspect = ComTypes.DVASPECT.DVASPECT_CONTENT, - ptd = 0, + ptd = (nint)0, lindex = -1 }; diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IComVisibleDataObject.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IComVisibleDataObject.cs index 901c6824003..dc6a97cf0ad 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IComVisibleDataObject.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IComVisibleDataObject.cs @@ -5,9 +5,15 @@ namespace System.Private.Windows.Ole; +#if NET +internal partial interface IComVisibleDataObject : IManagedWrapper +{ +} +#endif + /// /// Used to filter to fully COM visible data objects. Notably the platform specific DataObject class. /// -internal interface IComVisibleDataObject : IDataObject.Interface, IManagedWrapper, IDataObjectInternal +internal partial interface IComVisibleDataObject : IDataObject.Interface, IDataObjectInternal { } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDataFormat.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDataFormat.cs index e62999ab805..6556fa30c1e 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDataFormat.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDataFormat.cs @@ -28,5 +28,8 @@ internal interface IDataFormat : IDataFormat /// /// Creates a new instance of the data format. /// - static abstract T Create(string name, int id); +#if NET + static abstract +#endif + T Create(string name, int id); } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDataObjectInternal.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDataObjectInternal.cs index 92218719e72..83fb60ca792 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDataObjectInternal.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDataObjectInternal.cs @@ -161,10 +161,25 @@ internal unsafe interface IDataObjectInternal : IData where TDataObject : class, TIDataObject where TIDataObject : class { - static abstract TDataObject Create(); - static abstract TDataObject Create(IDataObject* dataObject); - static abstract TDataObject Create(object data); - static abstract IDataObjectInternal Wrap(TIDataObject data); +#if NET + static abstract +#endif + TDataObject Create(); + +#if NET + static abstract +#endif + TDataObject Create(IDataObject* dataObject); + +#if NET + static abstract +#endif + TDataObject Create(object data); + +#if NET + static abstract +#endif + IDataObjectInternal Wrap(TIDataObject data); /// /// Unwraps the user IDataObject instance when applicable. diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IOleServices.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IOleServices.cs index 43f9d8dd5da..9bf5f9834e2 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IOleServices.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IOleServices.cs @@ -15,7 +15,10 @@ internal unsafe interface IOleServices /// that the current thread is a single-threaded apartment (STA). /// /// Current thread is not in the right state for OLE. - static abstract void EnsureThreadState(); +#if NET + static abstract +#endif + void EnsureThreadState(); /// /// Called after unsuccessfully performing clipboard serialization. @@ -27,7 +30,10 @@ internal unsafe interface IOleServices /// /// The data format that is being serialized. /// - static abstract HRESULT GetDataHere(string format, object data, FORMATETC* pformatetc, STGMEDIUM* pmedium); +#if NET + static abstract +#endif + HRESULT GetDataHere(string format, object data, FORMATETC* pformatetc, STGMEDIUM* pmedium); /// /// If the is a the requested format this method will attempt to extract it @@ -40,7 +46,10 @@ internal unsafe interface IOleServices /// /// /// if a bitmap was extracted. - static abstract bool TryGetObjectFromDataObject( +#if NET + static abstract +#endif + bool TryGetObjectFromDataObject( IDataObject* dataObject, string format, [NotNullWhen(true)] out T data); @@ -53,12 +62,18 @@ static abstract bool TryGetObjectFromDataObject( /// Basic predefined formats that map to are checked before this call. /// /// - static abstract bool IsValidTypeForFormat(Type type, string format); +#if NET + static abstract +#endif + bool IsValidTypeForFormat(Type type, string format); /// /// Allows the given to pass pre-validation without a resolver. /// - static abstract bool AllowTypeWithoutResolver(); +#if NET + static abstract +#endif + bool AllowTypeWithoutResolver(); /// /// Allows custom validation or adapting of data and formats. @@ -68,19 +83,34 @@ static abstract bool TryGetObjectFromDataObject( /// is true. /// /// The data to be checked against . - static abstract void ValidateDataStoreData(ref string format, bool autoConvert, object? data); +#if NET + static abstract +#endif + void ValidateDataStoreData(ref string format, bool autoConvert, object? data); /// /// Creates an instance. /// - static abstract IComVisibleDataObject CreateDataObject(); +#if NET + static abstract +#endif + IComVisibleDataObject CreateDataObject(); - /// /> - static abstract HRESULT OleGetClipboard(IDataObject** dataObject); + /// +#if NET + static abstract +#endif + HRESULT OleGetClipboard(IDataObject** dataObject); /// - static abstract HRESULT OleSetClipboard(IDataObject* dataObject); +#if NET + static abstract +#endif + HRESULT OleSetClipboard(IDataObject* dataObject); /// - static abstract HRESULT OleFlushClipboard(); +#if NET + static abstract +#endif + HRESULT OleFlushClipboard(); } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/TypeBinder.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/TypeBinder.cs index bda429f88e4..66a48d63af6 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/TypeBinder.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/TypeBinder.cs @@ -31,6 +31,10 @@ namespace System.Private.Windows.Ole; internal sealed class TypeBinder : SerializationBinder, ITypeResolver where TNrbfSerializer : INrbfSerializer { +#if NETFRAMEWORK + private static readonly INrbfSerializer s_nrbfSerializer = Activator.CreateInstance(); +#endif + private readonly Type _rootType; private readonly Func? _resolver; private readonly bool _isTypedRequest; @@ -133,8 +137,13 @@ public bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) if (type is null && _rootType != typeof(object) && !_rootType.IsInterface +#if NET && TNrbfSerializer.TryBindToType(typeName, out type) && !TNrbfSerializer.IsFullySupportedType(type)) +#else + && s_nrbfSerializer.TryBindToType(typeName, out type) + && !s_nrbfSerializer.IsFullySupportedType(type)) +#endif { // Don't allow automatic binding for open-ended root types. This is to prevent surprising behavior // with "primitive" types such as `int` binding to `IComparable`, etc. It also prevents leaking out diff --git a/src/System.Private.Windows.Core/src/System/SpanReader.cs b/src/System.Private.Windows.Core/src/System/SpanReader.cs index 7f380e38633..98e51141b7c 100644 --- a/src/System.Private.Windows.Core/src/System/SpanReader.cs +++ b/src/System.Private.Windows.Core/src/System/SpanReader.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers; - namespace System; /// @@ -15,7 +13,7 @@ namespace System; /// field. /// /// -/// Inspired by patterns. +/// Inspired by SequenceReader{T} patterns. /// /// internal unsafe ref struct SpanReader(ReadOnlySpan span) where T : unmanaged, IEquatable @@ -177,7 +175,12 @@ public bool TryRead(int count, out ReadOnlySpan value) where TVa else { success = true; +#if NETFRAMEWORK + ReadOnlySpan current = _unread[..(sizeof(TValue) / sizeof(T) * count)]; + value = MemoryMarshal.Cast(current); +#else value = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref MemoryMarshal.GetReference(_unread)), count); +#endif UnsafeAdvance((sizeof(TValue) / sizeof(T)) * count); } @@ -258,23 +261,25 @@ private void UnsafeAdvance(int count) UncheckedSlice(ref _unread, count, _unread.Length - count); } - /// - /// Slicing without bounds checking. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void UncheckedSliceTo(ref ReadOnlySpan span, int length) { Debug.Assert((uint)length <= (uint)span.Length); +#if NETFRAMEWORK + span = span[..length]; +#else span = MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(span), length); +#endif } - /// - /// Slicing without bounds checking. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void UncheckedSlice(ref ReadOnlySpan span, int start, int length) { Debug.Assert((uint)start <= (uint)span.Length && (uint)length <= (uint)(span.Length - start)); +#if NETFRAMEWORK + span = span.Slice(start, length); +#else span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(span), (nint)(uint)start), length); +#endif } } diff --git a/src/System.Private.Windows.Core/src/System/SpanWriter.cs b/src/System.Private.Windows.Core/src/System/SpanWriter.cs index 623a16fab3d..9167464768b 100644 --- a/src/System.Private.Windows.Core/src/System/SpanWriter.cs +++ b/src/System.Private.Windows.Core/src/System/SpanWriter.cs @@ -6,7 +6,7 @@ namespace System; /// /// Fast stack based writer. /// -internal unsafe ref struct SpanWriter(Span span) where T : unmanaged, IEquatable +internal ref struct SpanWriter(Span span) where T : unmanaged, IEquatable { private Span _unwritten = span; public Span Span { get; } = span; @@ -97,6 +97,10 @@ private void UnsafeAdvance(int count) private static void UncheckedSlice(ref Span span, int start, int length) { Debug.Assert((uint)start <= (uint)span.Length && (uint)length <= (uint)(span.Length - start)); +#if NETFRAMEWORK + span = span.Slice(start, length); +#else span = MemoryMarshal.CreateSpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(span), (nint)(uint)start), length); +#endif } } diff --git a/src/System.Private.Windows.Core/src/System/TypeExtensions.cs b/src/System.Private.Windows.Core/src/System/TypeExtensions.cs index 96169d73ec4..f92fcf56a87 100644 --- a/src/System.Private.Windows.Core/src/System/TypeExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/TypeExtensions.cs @@ -11,7 +11,7 @@ namespace System; /// /// Helper methods for comparing s and s. /// -internal static class TypeExtensions +internal static partial class TypeExtensions { /// /// Match type against . @@ -40,7 +40,11 @@ internal static bool Matches( || type.IsConstructedGenericType != typeName.IsConstructedGenericType || type.IsNested != typeName.IsNested || (type.IsArray && type.GetArrayRank() != typeName.GetArrayRank()) - || type.IsSZArray != typeName.IsSZArray // int[] vs int[*] +#if NET + || type.IsSZArray != typeName.IsSZArray +#else + || IsSZArray(type) != typeName.IsSZArray +#endif ) { return false; @@ -283,4 +287,37 @@ static bool TryComparePublicKeyTokenToKey(ReadOnlySpan publicKeyToken, Rea } } } + +#if NETFRAMEWORK + /// + /// Determines whether the given is a single-dimensional, zero-indexed (SZ) array, + /// equivalent to Type.IsSZArray which is not available on .NET Framework. + /// + /// + /// + /// The CLR distinguishes two kinds of array types at the metadata level: + /// + /// + /// ELEMENT_TYPE_SZARRAY the "vector" type (e.g. int[]), single-dimensional + /// and always zero-indexed. + /// ELEMENT_TYPE_ARRAY with rank 1 — a general array that happens to have one dimension + /// (e.g. int[*]), which can have a non-zero lower bound. + /// + /// + /// Both return for and 1 for + /// , so those properties alone cannot distinguish them. + /// + /// + /// The trick here exploits (parameterless), which always + /// produces the SZ array type for the element type. If the resulting type is reference-equal + /// to the original, the original is an SZ array. If it is not (as would be the case for + /// int[*], whose MakeArrayType() yields int[]), the original is a + /// general rank-1 array. The comparison resolves to a RuntimeTypeHandle identity + /// check on cached objects — no reflection metadata walking or string + /// parsing is involved. + /// + /// + private static bool IsSZArray(Type type) => + type.IsArray && type.GetElementType()!.MakeArrayType() == type; +#endif } diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/Foundation/IHandle.cs b/src/System.Private.Windows.Core/src/Windows/Win32/Foundation/IHandle.cs index e5cdb386784..dbcc6c2642a 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/Foundation/IHandle.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/Foundation/IHandle.cs @@ -42,5 +42,9 @@ internal interface IHandle where THandle : unmanaged /// is on a struct. See for a concrete usage. /// /// +#if NET object? Wrapper => this; +#else + object? Wrapper { get; } +#endif } diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/Foundation/NullHandle.cs b/src/System.Private.Windows.Core/src/Windows/Win32/Foundation/NullHandle.cs index 841fde7b75a..f3e893bd99a 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/Foundation/NullHandle.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/Foundation/NullHandle.cs @@ -10,4 +10,6 @@ private NullHandle() { } public static NullHandle Instance { get; } = new(); public T Handle => default; + + public object? Wrapper => null; } diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/ARGB.cs b/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/ARGB.cs index 2377a41cd40..6b0670bce95 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/ARGB.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/ARGB.cs @@ -53,8 +53,6 @@ public static Color[] ToColorArray(params ReadOnlySpan argbColors) return colors; } - public static Color[] ToColorArray(params ReadOnlySpan argbColors) => ToColorArray( - MemoryMarshal.CreateReadOnlySpan( - ref Unsafe.As(ref MemoryMarshal.GetReference(argbColors)), - argbColors.Length)); + public static Color[] ToColorArray(params ReadOnlySpan argbColors) => + ToColorArray(MemoryMarshal.Cast(argbColors)); } diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HdcHandle.cs b/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HdcHandle.cs index e998a1eea1f..4224ea18049 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HdcHandle.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HdcHandle.cs @@ -23,6 +23,8 @@ public HdcHandle(CreateDcScope hdc) public HDC Handle { get; private set; } + public object? Wrapper => this; + public static implicit operator HDC(in HdcHandle handle) => handle.Handle; public static implicit operator nint(in HdcHandle handle) => handle.Handle; diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumChildWindows.cs b/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumChildWindows.cs index ae78ad68005..bd96a328e23 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumChildWindows.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumChildWindows.cs @@ -3,11 +3,18 @@ namespace Windows.Win32; -internal static partial class PInvokeCore +internal static unsafe partial class PInvokeCore { public delegate BOOL EnumChildWindowsCallback(HWND hWnd); - public static unsafe BOOL EnumChildWindows(T hwndParent, EnumChildWindowsCallback callback) +#if NETFRAMEWORK + private delegate BOOL EnumChildWindowsNativeCallback(HWND hWnd, LPARAM lParam); + private static readonly EnumChildWindowsNativeCallback s_enumChildWindowsNativeCallback = HandleEnumChildWindowsNativeCallback; + private static readonly delegate* unmanaged[Stdcall] s_enumChildWindowsNativeCallbackFunctionPointer = + (delegate* unmanaged[Stdcall])Marshal.GetFunctionPointerForDelegate(s_enumChildWindowsNativeCallback); +#endif + + public static BOOL EnumChildWindows(T hwndParent, EnumChildWindowsCallback callback) where T : IHandle { // We pass a function pointer to the native function and supply the callback as @@ -16,7 +23,11 @@ public static unsafe BOOL EnumChildWindows(T hwndParent, EnumChildWindowsCall GCHandle gcHandle = GCHandle.Alloc(callback); try { - return EnumChildWindows(hwndParent.Handle, &EnumChildWindowsNativeCallback, (LPARAM)(nint)gcHandle); +#if NET + return EnumChildWindows(hwndParent.Handle, &HandleEnumChildWindowsNativeCallback, (LPARAM)(nint)gcHandle); +#else + return EnumChildWindows(hwndParent.Handle, s_enumChildWindowsNativeCallbackFunctionPointer, (LPARAM)(nint)gcHandle); +#endif } finally { @@ -25,8 +36,10 @@ public static unsafe BOOL EnumChildWindows(T hwndParent, EnumChildWindowsCall } } +#if NET [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static BOOL EnumChildWindowsNativeCallback(HWND hWnd, LPARAM lParam) +#endif + private static BOOL HandleEnumChildWindowsNativeCallback(HWND hWnd, LPARAM lParam) { return ((EnumChildWindowsCallback)((GCHandle)(nint)lParam).Target!)(hWnd); } diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumDisplayMonitors.cs b/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumDisplayMonitors.cs index 88e064da2f1..0e0a1027884 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumDisplayMonitors.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumDisplayMonitors.cs @@ -7,12 +7,23 @@ internal static unsafe partial class PInvokeCore { public delegate bool EnumDisplayMonitorsCallback(HMONITOR monitor, HDC hdc); +#if NETFRAMEWORK + private delegate BOOL EnumDisplayMonitorsNativeCallback(HMONITOR monitor, HDC hdc, RECT* lprcMonitor, LPARAM lParam); + private static readonly EnumDisplayMonitorsNativeCallback s_enumDisplayMonitorsNativeCallback = HandleEnumDisplayMonitorsNativeCallback; + private static readonly delegate* unmanaged[Stdcall] s_enumDisplayMonitorsNativeCallbackFunctionPointer = + (delegate* unmanaged[Stdcall])Marshal.GetFunctionPointerForDelegate(s_enumDisplayMonitorsNativeCallback); +#endif + public static BOOL EnumDisplayMonitors(EnumDisplayMonitorsCallback callBack) { GCHandle gcHandle = GCHandle.Alloc(callBack); try { - return EnumDisplayMonitors(default, (RECT*)null, &EnumDisplayMonitorsNativeCallback, (LPARAM)(nint)gcHandle); +#if NET + return EnumDisplayMonitors(default, (RECT*)null, &HandleEnumDisplayMonitorsNativeCallback, (LPARAM)(nint)gcHandle); +#else + return EnumDisplayMonitors(default, (RECT*)null, s_enumDisplayMonitorsNativeCallbackFunctionPointer, (LPARAM)(nint)gcHandle); +#endif } finally { @@ -20,8 +31,10 @@ public static BOOL EnumDisplayMonitors(EnumDisplayMonitorsCallback callBack) } } +#if NET [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static BOOL EnumDisplayMonitorsNativeCallback(HMONITOR monitor, HDC hdc, RECT* lprcMonitor, LPARAM lParam) +#endif + private static BOOL HandleEnumDisplayMonitorsNativeCallback(HMONITOR monitor, HDC hdc, RECT* lprcMonitor, LPARAM lParam) { return ((EnumDisplayMonitorsCallback)((GCHandle)(nint)lParam).Target!)(monitor, hdc); } diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumThreadWindows.cs b/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumThreadWindows.cs index 73c9ffd17e1..158b353ab5e 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumThreadWindows.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumThreadWindows.cs @@ -3,14 +3,21 @@ namespace Windows.Win32; -internal static partial class PInvokeCore +internal static unsafe partial class PInvokeCore { public delegate BOOL EnumThreadWindowsCallback(HWND hWnd); +#if NETFRAMEWORK + private delegate BOOL EnumThreadWindowsNativeCallback(HWND hWnd, LPARAM lParam); + private static readonly EnumThreadWindowsNativeCallback s_enumThreadWindowsNativeCallback = HandleEnumThreadWindowsNativeCallback; + private static readonly delegate* unmanaged[Stdcall] s_enumThreadWindowsNativeCallbackFunctionPointer = + (delegate* unmanaged[Stdcall])Marshal.GetFunctionPointerForDelegate(s_enumThreadWindowsNativeCallback); +#endif + /// /// Enumerates all nonchild windows in the current thread. /// - public static unsafe BOOL EnumCurrentThreadWindows(EnumThreadWindowsCallback callback) + public static BOOL EnumCurrentThreadWindows(EnumThreadWindowsCallback callback) { // We pass a function pointer to the native function and supply the callback as // reference data, so that the CLR doesn't need to generate a native code block for @@ -18,7 +25,14 @@ public static unsafe BOOL EnumCurrentThreadWindows(EnumThreadWindowsCallback cal GCHandle gcHandle = GCHandle.Alloc(callback); try { +#if NET return EnumThreadWindows(GetCurrentThreadId(), &HandleEnumThreadWindowsNativeCallback, (LPARAM)(nint)gcHandle); +#else + return EnumThreadWindows( + GetCurrentThreadId(), + s_enumThreadWindowsNativeCallbackFunctionPointer, + (LPARAM)(nint)gcHandle); +#endif } finally { @@ -26,7 +40,9 @@ public static unsafe BOOL EnumCurrentThreadWindows(EnumThreadWindowsCallback cal } } +#if NET [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] +#endif private static BOOL HandleEnumThreadWindowsNativeCallback(HWND hWnd, LPARAM lParam) { return ((EnumThreadWindowsCallback)((GCHandle)(nint)lParam).Target!)(hWnd); diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumWindows.cs b/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumWindows.cs index b11d172dc66..051e117d1b2 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumWindows.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/PInvokeCore.EnumWindows.cs @@ -3,10 +3,17 @@ namespace Windows.Win32; -internal static partial class PInvokeCore +internal static unsafe partial class PInvokeCore { public delegate BOOL EnumWindowsCallback(HWND hWnd); +#if NETFRAMEWORK + private delegate BOOL EnumWindowsNativeCallback(HWND hWnd, LPARAM lParam); + private static readonly EnumWindowsNativeCallback s_enumWindowsNativeCallback = HandleEnumWindowsNativeCallback; + private static readonly delegate* unmanaged[Stdcall] s_enumWindowsNativeCallbackFunctionPointer = + (delegate* unmanaged[Stdcall])Marshal.GetFunctionPointerForDelegate(s_enumWindowsNativeCallback); +#endif + public static unsafe BOOL EnumWindows(EnumWindowsCallback callback) { // We pass a function pointer to the native function and supply the callback as @@ -15,7 +22,11 @@ public static unsafe BOOL EnumWindows(EnumWindowsCallback callback) GCHandle gcHandle = GCHandle.Alloc(callback); try { - return EnumWindows(&EnumWindowsNativeCallback, (LPARAM)(nint)gcHandle); +#if NET + return EnumWindows(&HandleEnumWindowsNativeCallback, (LPARAM)(nint)gcHandle); +#else + return EnumWindows(s_enumWindowsNativeCallbackFunctionPointer, (LPARAM)(nint)gcHandle); +#endif } finally { @@ -23,8 +34,10 @@ public static unsafe BOOL EnumWindows(EnumWindowsCallback callback) } } +#if NET [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static BOOL EnumWindowsNativeCallback(HWND hWnd, LPARAM lParam) +#endif + private static BOOL HandleEnumWindowsNativeCallback(HWND hWnd, LPARAM lParam) { return ((EnumWindowsCallback)((GCHandle)(nint)lParam).Target!)(hWnd); } diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/AgileComPointer.cs b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/AgileComPointer.cs index 9dde5888686..03fa8dabe8d 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/AgileComPointer.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/AgileComPointer.cs @@ -17,7 +17,7 @@ namespace Windows.Win32.Foundation; /// /// Fields should be nulled out before calling . Releasing the COM pointer during disposal /// can result in callbacks to containing classes. Rather than evaluate the risk of this for every class, always -/// follow this pattern. facilitates doing this safely. +/// follow this pattern. facilitates doing this safely. /// /// internal unsafe class AgileComPointer : @@ -46,7 +46,7 @@ internal unsafe class AgileComPointer : /// /// Setting to `` will ensure that this object takes /// responsibility for releasing the COM interface when it is no longer needed. This is done by calling - /// after the GIT adds a ref to the interface. + /// IUnknown.Release after the GIT adds a ref to the interface. /// /// /// diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComHelpers.cs b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComHelpers.cs index 001dd0f15bf..9cc3f11c67a 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComHelpers.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComHelpers.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Windows.Win32.System.Com; +using ComUnknown = Windows.Win32.System.Com.IUnknown; namespace Windows.Win32; @@ -84,24 +85,28 @@ internal static bool SupportsInterface(object? @object) where T : unmanaged, return null; } - IUnknown* ccw = null; + ComUnknown* ccw = null; + + // On .NET Framework we only have the legacy COM interop. +#if NET if (@object is IManagedWrapper) { // One of our classes that we can generate a CCW for. - ccw = (IUnknown*)WinFormsComWrappers.Instance.GetOrCreateComInterfaceForObject(@object, CreateComInterfaceFlags.None); + ccw = (ComUnknown*)WinFormsComWrappers.Instance.GetOrCreateComInterfaceForObject(@object, CreateComInterfaceFlags.None); } else if (ComWrappers.TryGetComInstance(@object, out nint unknown)) { // A ComWrappers generated RCW. - ccw = (IUnknown*)unknown; + ccw = (ComUnknown*)unknown; } else +#endif { // Fall back to COM interop if possible. Note that this will use the globally registered ComWrappers // if that exists (so it won't always fall into legacy COM interop). try { - ccw = (IUnknown*)Marshal.GetIUnknownForObject(@object); + ccw = (ComUnknown*)Marshal.GetIUnknownForObject(@object); } catch (Exception ex) { @@ -115,7 +120,7 @@ internal static bool SupportsInterface(object? @object) where T : unmanaged, return null; } - if (typeof(T) == typeof(IUnknown)) + if (typeof(T) == typeof(ComUnknown)) { // No need to query if we wanted IUnknown. result = HRESULT.S_OK; @@ -128,11 +133,12 @@ internal static bool SupportsInterface(object? @object) where T : unmanaged, return (T*)ppvObject; } +#if NET /// /// Attempts to unwrap a ComWrapper CCW as a particular managed object. /// public static bool TryUnwrapComWrapperCCW( - IUnknown* unknown, + ComUnknown* unknown, [NotNullWhen(true)] out TWrapper? @interface) where TWrapper : class { if (ComWrappers.TryGetObject((nint)unknown, out object? obj)) @@ -151,15 +157,16 @@ public static bool TryUnwrapComWrapperCCW( @interface = default; return false; } +#endif - /// + /// internal static bool TryGetObjectForIUnknown( ComScope comScope, [NotNullWhen(true)] out TObject? @object) where TObject : class where TInterface : unmanaged, IComIID => TryGetObjectForIUnknown(comScope.Value, out @object); - /// + /// internal static bool TryGetObjectForIUnknown( TInterface* comPointer, [NotNullWhen(true)] out TObject? @object) @@ -172,13 +179,13 @@ internal static bool TryGetObjectForIUnknown( return false; } - IUnknown* unknown = (IUnknown*)comPointer; - if (typeof(TInterface) == typeof(IUnknown)) + ComUnknown* unknown = (ComUnknown*)comPointer; + if (typeof(TInterface) == typeof(ComUnknown)) { return TryGetObjectForIUnknown(unknown, out @object); } - HRESULT hr = unknown->QueryInterface(IID.Get(), (void**)&unknown); + HRESULT hr = unknown->QueryInterface(IID.Get(), (void**)&unknown); if (hr.Failed) { Debug.Fail("How did we fail to query for IUnknown?"); @@ -189,9 +196,9 @@ internal static bool TryGetObjectForIUnknown( return TryGetObjectForIUnknown(unknown, out @object); } - /// + /// internal static bool TryGetObjectForIUnknown( - IUnknown* unknown, + ComUnknown* unknown, [NotNullWhen(true)] out TObject? @object) where TObject : class => TryGetObjectForIUnknown(unknown, takeOwnership: false, out @object); @@ -202,7 +209,7 @@ internal static bool TryGetObjectForIUnknown( /// When , releases the original whether successful or not. /// internal static bool TryGetObjectForIUnknown( - IUnknown* unknown, + ComUnknown* unknown, bool takeOwnership, [NotNullWhen(true)] out TObject? @object) where TObject : class { @@ -232,6 +239,10 @@ internal static bool TryGetObjectForIUnknown( } } +#if NET + + // This could be checked on .NET Framework, but would require it be typed so you can cast the RCW out to compare it. + /// /// Returns if the given /// is projected as the given . @@ -244,8 +255,8 @@ internal static bool WrapsManagedObject(object @object, T* comPointer) return false; } - using ComScope unknown = new(null); - ((IUnknown*)comPointer)->QueryInterface(IID.Get(), unknown).ThrowOnFailure(); + using ComScope unknown = new(null); + ((ComUnknown*)comPointer)->QueryInterface(IID.Get(), unknown).ThrowOnFailure(); // If it is a ComWrappers object we need to simply pull out the original object to check. if (ComWrappers.TryGetObject((nint)unknown, out object? obj)) @@ -253,11 +264,12 @@ internal static bool WrapsManagedObject(object @object, T* comPointer) return @object == obj; } - using ComScope ccw = new((IUnknown*)(void*)Marshal.GetIUnknownForObject(@object)); + using ComScope ccw = new((ComUnknown*)(void*)Marshal.GetIUnknownForObject(@object)); return ccw.Value == unknown; } +#endif - /// + /// internal static object GetObjectForIUnknown(TInterface* comPointer) where TInterface : unmanaged, IComIID { @@ -266,32 +278,36 @@ internal static object GetObjectForIUnknown(TInterface* comPointer) throw new ArgumentNullException(nameof(comPointer)); } - IUnknown* unknown = (IUnknown*)comPointer; + ComUnknown* unknown = (ComUnknown*)comPointer; - if (typeof(TInterface) == typeof(IUnknown)) + if (typeof(TInterface) == typeof(ComUnknown)) { return GetObjectForIUnknown(unknown); } - unknown->QueryInterface(IID.Get(), (void**)&unknown).ThrowOnFailure(); + unknown->QueryInterface(IID.Get(), (void**)&unknown).ThrowOnFailure(); return GetObjectForIUnknown(unknown); } - /// + /// internal static object GetObjectForIUnknown(ComScope comScope) where TInterface : unmanaged, IComIID => GetObjectForIUnknown(comScope.Value); /// - /// capable wrapper for . + /// Gets a runtime capable wrapper for . + /// Will attempt to unwrap ComWrapper RCWs if available. /// /// is . - internal static object GetObjectForIUnknown(IUnknown* unknown) + internal static object GetObjectForIUnknown(ComUnknown* unknown) { if (unknown is null) { throw new ArgumentNullException(nameof(unknown)); } +#if NETFRAMEWORK + return Marshal.GetObjectForIUnknown((nint)unknown); +#else // If it is a ComWrappers object we need to simply pull out the original object. if (ComWrappers.TryGetObject((nint)unknown, out object? obj)) { @@ -307,14 +323,17 @@ internal static object GetObjectForIUnknown(IUnknown* unknown) // Analogous to ComInterfaceMarshaller.ConvertToManaged(unknown), but we need our own strategy. return WinFormsComStrategy.Instance.GetOrCreateObjectForComInstance((nint)unknown, CreateObjectFlags.Unwrap); } +#endif } +#if NET /// - /// vtable population hook for CsWin32's generated implementation. + /// vtable population hook for CsWin32's generated implementation. /// - static partial void PopulateIUnknownImpl(IUnknown.Vtbl* vtable) + static partial void PopulateIUnknownImpl(ComUnknown.Vtbl* vtable) where TComInterface : unmanaged => WinFormsComWrappers.PopulateIUnknownVTable(vtable); +#endif /// /// Find the given interface's from the specified type library. diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComManagedStream.cs b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComManagedStream.cs index 4014493bf4b..395aca540c3 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComManagedStream.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComManagedStream.cs @@ -3,7 +3,12 @@ namespace Windows.Win32.System.Com; -internal sealed unsafe class ComManagedStream : IStream.Interface, IManagedWrapper +#if NET +// Workaround SA1001 white space warnings. +internal sealed partial class ComManagedStream : IManagedWrapper { } +#endif + +internal sealed unsafe partial class ComManagedStream : IStream.Interface { private readonly Stream _dataStream; diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComScope.cs b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComScope.cs index a5e4c87036b..c4700518919 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComScope.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComScope.cs @@ -1,13 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Windows.Win32.System.Com; +// .NET Framework struggles with IUnknown in XML comments unless we alias it. +using ComUnknown = Windows.Win32.System.Com.IUnknown; namespace Windows.Win32.Foundation; /// /// Lifetime management struct for a native COM pointer. Meant to be utilized in a statement -/// to ensure is called when going out of scope with the using. +/// to ensure is called when going out of scope with the using. /// /// /// @@ -24,7 +25,7 @@ namespace Windows.Win32.Foundation; /// /// /// This should be one of the struct COM definitions as generated by CsWin32. Ideally we'd constrain to -/// or some other interface tag to enforce that this is being used around +/// or some other interface tag to enforce that this is being used around /// a struct that is actually a COM wrapper. /// internal readonly unsafe ref struct ComScope where T : unmanaged, IComIID @@ -32,7 +33,7 @@ namespace Windows.Win32.Foundation; // Keeping internal as nint allows us to use Unsafe methods to get significantly better generated code. private readonly nint _value; public T* Value => (T*)_value; - public IUnknown* AsUnknown => (IUnknown*)_value; + public ComUnknown* AsUnknown => (ComUnknown*)_value; public ComScope(T* value) => _value = (nint)value; @@ -59,7 +60,7 @@ namespace Windows.Win32.Foundation; public ComScope TryQuery(out HRESULT hr) where TTo : unmanaged, IComIID { ComScope scope = new(null); - hr = ((IUnknown*)Value)->QueryInterface(IID.Get(), scope); + hr = ((ComUnknown*)Value)->QueryInterface(IID.Get(), scope); return scope; } @@ -69,7 +70,7 @@ public ComScope TryQuery(out HRESULT hr) where TTo : unmanaged, IComII public ComScope Query() where TTo : unmanaged, IComIID { ComScope scope = new(null); - ((IUnknown*)Value)->QueryInterface(IID.Get(), scope).ThrowOnFailure(); + ((ComUnknown*)Value)->QueryInterface(IID.Get(), scope).ThrowOnFailure(); return scope; } @@ -79,7 +80,7 @@ public ComScope Query() where TTo : unmanaged, IComIID public static ComScope TryQueryFrom(TFrom* from, out HRESULT hr) where TFrom : unmanaged, IComIID { ComScope scope = new(null); - hr = from is null ? HRESULT.E_POINTER : ((IUnknown*)from)->QueryInterface(IID.Get(), scope); + hr = from is null ? HRESULT.E_POINTER : ((ComUnknown*)from)->QueryInterface(IID.Get(), scope); return scope; } @@ -94,7 +95,7 @@ public static ComScope QueryFrom(TFrom* from) where TFrom : unmanaged, } ComScope scope = new(null); - ((IUnknown*)from)->QueryInterface(IID.Get(), scope).ThrowOnFailure(); + ((ComUnknown*)from)->QueryInterface(IID.Get(), scope).ThrowOnFailure(); return scope; } @@ -109,7 +110,7 @@ public bool SupportsInterface() where TInterface : unmanaged, IComII return true; } - IUnknown* unknown; + ComUnknown* unknown; HRESULT hr = AsUnknown->QueryInterface(IID.Get(), (void**)&unknown); if (hr.Succeeded) @@ -123,7 +124,7 @@ public bool SupportsInterface() where TInterface : unmanaged, IComII public void Dispose() { - IUnknown* unknown = (IUnknown*)_value; + ComUnknown* unknown = (ComUnknown*)_value; // Really want this to be null after disposal to avoid double releases, but we also want // to maintain the readonly state of the struct to allow passing as `in` without creating implicit diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/GlobalInterfaceTable.cs b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/GlobalInterfaceTable.cs index 3766e5391a6..0fa874fa293 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/GlobalInterfaceTable.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/GlobalInterfaceTable.cs @@ -67,6 +67,7 @@ public static HRESULT RevokeInterface(uint cookie) return hr; } +#if NET /// /// Creates a new instance of an for /// that uses the Global Interface Table. @@ -77,4 +78,5 @@ public static HRESULT RevokeInterface(uint cookie) /// /// public static IIUnknownStrategy CreateUnknownStrategy() => new UnknownStrategy(); +#endif } diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/IID.cs b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/IID.cs index 5678980652f..ae2c8ca990e 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/IID.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/IID.cs @@ -12,7 +12,7 @@ private static ref readonly Guid IID_NULL { ReadOnlySpan data = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]; return ref Unsafe.As(ref MemoryMarshal.GetReference(data)); @@ -27,14 +27,26 @@ private static ref readonly Guid IID_NULL /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Guid* Get() where T : unmanaged, IComIID - => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in T.Guid)); + { +#if NET + return (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in T.Guid)); +#else + return (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in default(T).Guid)); +#endif + } /// /// Gets a reference to the IID for the given . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref Guid GetRef() where T : unmanaged, IComIID - => ref Unsafe.AsRef(in T.Guid); + public static ref readonly Guid GetRef() where T : unmanaged, IComIID + { +#if NET + return ref Unsafe.AsRef(in T.Guid); +#else + return ref default(T).Guid; +#endif + } /// /// Empty (GUID_NULL in docs). diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/System/Ole/OLE_HANDLE.cs b/src/System.Private.Windows.Core/src/Windows/Win32/System/Ole/OLE_HANDLE.cs index 2fe40ef295f..55ac2bf556c 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/System/Ole/OLE_HANDLE.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/System/Ole/OLE_HANDLE.cs @@ -7,9 +7,9 @@ internal partial struct OLE_HANDLE { // HANDLE objects are nints and need to be sign extended. - public static explicit operator HICON(OLE_HANDLE handle) => new((int)handle.Value); - public static explicit operator HBITMAP(OLE_HANDLE handle) => new((int)handle.Value); - public static explicit operator HPALETTE(OLE_HANDLE handle) => new((int)handle.Value); - public static explicit operator HMETAFILE(OLE_HANDLE handle) => new((int)handle.Value); - public static explicit operator HENHMETAFILE(OLE_HANDLE handle) => new((int)handle.Value); + public static explicit operator HICON(OLE_HANDLE handle) => new((nint)(int)handle.Value); + public static explicit operator HBITMAP(OLE_HANDLE handle) => new((nint)(int)handle.Value); + public static explicit operator HPALETTE(OLE_HANDLE handle) => new((nint)(int)handle.Value); + public static explicit operator HMETAFILE(OLE_HANDLE handle) => new((nint)(int)handle.Value); + public static explicit operator HENHMETAFILE(OLE_HANDLE handle) => new((nint)(int)handle.Value); } diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/System/Variant/VARIANT.cs b/src/System.Private.Windows.Core/src/Windows/Win32/System/Variant/VARIANT.cs index c424afa5487..e02b3acb608 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/System/Variant/VARIANT.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/System/Variant/VARIANT.cs @@ -3,8 +3,13 @@ using Windows.Win32.System.Com; using Windows.Win32.System.Com.StructuredStorage; + +#if NET using Windows.Win32.System.Ole; +#endif + using static Windows.Win32.System.Variant.VARENUM; +using FILETIME = Windows.Win32.Foundation.FILETIME; namespace Windows.Win32.System.Variant; @@ -75,6 +80,9 @@ public void Clear() fixed (VARIANT* thisVariant = &this) { +#if NETFRAMEWORK + return Marshal.GetObjectForNativeVariant((nint)thisVariant); +#else void* data = &thisVariant->Anonymous.Anonymous.Anonymous; if (Byref) { @@ -105,9 +113,11 @@ public void Clear() } return ToObject(Type, Byref, data); +#endif } } +#if NET private static object? ToObject(VARENUM type, bool byRef, void* data) { switch (type) @@ -864,6 +874,7 @@ private static object ToVector(in CA ca, VARENUM vectorType) private static Span GetSpan(Array array) => MemoryMarshal.CreateSpan(ref Unsafe.AsRef(Marshal.UnsafeAddrOfPinnedArrayElement(array, 0).ToPointer()), array.Length); +#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] public static explicit operator bool(VARIANT value) diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/RecordMapTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/RecordMapTests.cs deleted file mode 100644 index 35b54d320af..00000000000 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/RecordMapTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Private.Windows.BinaryFormat.Serializer; - -namespace FormatTests.FormattedObject; - -public class RecordMapTests -{ - private class Record : IWritableRecord - { - public Id Id => 1; - - void IWritableRecord.Write(BinaryWriter writer) { } - } - - [Fact] - public void RecordMap_CannotAddSameIndexTwice() - { - RecordMap map = new(); - Action action = () => map.AddRecord(new Record()); - action(); - action.Should().Throw(); - } - - [Fact] - public void RecordMap_GetMissingThrowsKeyNotFound() - { - RecordMap map = new(); - Func func = () => map[(Id)0]; - func.Should().Throw(); - } -} diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System.Private.Windows.Core.Tests.csproj b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System.Private.Windows.Core.Tests.csproj index 290e3926209..57464652485 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System.Private.Windows.Core.Tests.csproj +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System.Private.Windows.Core.Tests.csproj @@ -1,7 +1,8 @@  - $(NetCurrent)-windows7.0 + + $(NetCurrent)-windows7.0;net481 true true + $(NoWarn);CA1822 + + - + - + + diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/BinaryFormatUtilitesTestsBase.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/BinaryFormatUtilitesTestsBase.cs index 8da25667b69..8586de828a2 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/BinaryFormatUtilitesTestsBase.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/BinaryFormatUtilitesTestsBase.cs @@ -2,27 +2,35 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using System.Private.Windows.BinaryFormat; using System.Reflection.Metadata; +#if NET +using System.Private.Windows.BinaryFormat; +#endif + namespace System.Private.Windows.Ole.Tests; public abstract class BinaryFormatUtilitesTestsBase : IDisposable { public enum DataType { +#if NET Json, +#endif BinaryFormat } public MemoryStream CreateStream(DataType dataType, T data) where T : notnull { MemoryStream stream = new(); + +#if NET if (dataType == DataType.Json) { BinaryFormatWriter.TryWriteJsonData(stream, IJsonData.Create(data)); } else +#endif { WriteObjectToStream(stream, data, "test"); } diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/BinaryFormatUtilitiesTests.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/BinaryFormatUtilitiesTests.cs index 69961d60ea5..9f50c07173b 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/BinaryFormatUtilitiesTests.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/BinaryFormatUtilitiesTests.cs @@ -238,7 +238,7 @@ public void TryReadObjectFromStream_Primitives_BaseResolver(Array value) { Format = "test", TypedRequest = true, - Resolver = (TypeName typeName) => Type.GetType(typeName.FullName) + Resolver = typeName => Type.GetType(typeName.FullName) }; BinaryFormatUtilities.TryReadObjectFromStream(stream, in request, out Array? result).Should().BeTrue(); @@ -296,7 +296,7 @@ public void TryReadObjectFromStream_IntArray_TypedToBaseSucceeds(DataType dataTy { Format = "test", TypedRequest = true, - Resolver = (TypeName typeName) => typeof(int[]).Matches(typeName, TypeNameComparison.TypeFullName) + Resolver = typeName => typeof(int[]).Matches(typeName, TypeNameComparison.TypeFullName) ? typeof(int[]) : throw new NotSupportedException() }; @@ -343,6 +343,8 @@ public void RoundTrip_PredefinedFormat_PrimitiveHashtable(Hashtable value) result.Should().BeEquivalentTo(value); } +#if NET + // There isn't a way to actually turn off the BinaryFormatter in .NET Framework. [Theory] [MemberData(nameof(Lists_UnsupportedTestData))] public void RoundTrip_Unsupported(IList value) @@ -370,6 +372,7 @@ public void RoundTrip_Unsupported(IList value) // Binary format deserializers in Clipboard/DragDrop scenarios are not opted in. reader.Should().Throw(); } +#endif [Theory] [MemberData(nameof(Lists_UnsupportedTestData))] @@ -490,7 +493,7 @@ public void TryReadObjectFromStream_Class_DerivedAsBase(DataType dataType) { Format = "test", TypedRequest = true, - Resolver = (TypeName typeName) => typeName.FullName == typeof(DerivedClass).FullName + Resolver = typeName => typeName.FullName == typeof(DerivedClass).FullName ? typeof(DerivedClass) : throw new NotSupportedException() }; @@ -529,7 +532,7 @@ public void TryReadObjectFromStream_Struct_AsNullable(DataType dataType) { Format = "test", TypedRequest = true, - Resolver = (TypeName typeName) => typeName.FullName == typeof(MyStruct).FullName + Resolver = typeName => typeName.FullName == typeof(MyStruct).FullName ? typeof(MyStruct?) : throw new NotSupportedException() }; @@ -578,7 +581,7 @@ public void TryReadObjectFromStream_Int_AsNullableInt(DataType dataType) { Format = "test", TypedRequest = true, - Resolver = (TypeName typeName) => typeName.FullName == typeof(int).FullName + Resolver = typeName => typeName.FullName == typeof(int).FullName ? typeof(int?) : throw new NotSupportedException() }; @@ -617,7 +620,7 @@ public void TryReadObjectFromStream_Int_AsFloat(DataType dataType) { Format = "test", TypedRequest = true, - Resolver = (TypeName typeName) => typeName.FullName == typeof(int).FullName + Resolver = typeName => typeName.FullName == typeof(int).FullName ? typeof(float) : throw new NotSupportedException() }; @@ -844,7 +847,7 @@ public void TryReadObjectFromStream_MyClass(DataType dataType) { Format = "test", TypedRequest = true, - Resolver = (TypeName typeName) => + Resolver = typeName => { if (typeof(MyClass1).Matches(typeName)) { diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardCoreTests.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardCoreTests.cs index ac6fc600561..61578a6c1b8 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardCoreTests.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardCoreTests.cs @@ -35,16 +35,29 @@ public void TryGetData_ChecksThreadState() private class InvalidThreadOleServices() : IOleServices { +#if NET static bool IOleServices.AllowTypeWithoutResolver() => throw new NotImplementedException(); static IComVisibleDataObject IOleServices.CreateDataObject() => throw new NotImplementedException(); static void IOleServices.EnsureThreadState() => throw new ThreadStateException(); - static unsafe HRESULT IOleServices.GetDataHere(string format, object data, FORMATETC* pformatetc, STGMEDIUM* pmedium) => throw new NotImplementedException(); + static HRESULT IOleServices.GetDataHere(string format, object data, FORMATETC* pformatetc, STGMEDIUM* pmedium) => throw new NotImplementedException(); static bool IOleServices.IsValidTypeForFormat(Type type, string format) => throw new NotImplementedException(); static HRESULT IOleServices.OleFlushClipboard() => throw new NotImplementedException(); - static unsafe HRESULT IOleServices.OleGetClipboard(IDataObject** dataObject) => throw new NotImplementedException(); - static unsafe HRESULT IOleServices.OleSetClipboard(IDataObject* dataObject) => throw new NotImplementedException(); - static unsafe bool IOleServices.TryGetObjectFromDataObject(IDataObject* dataObject, string requestedFormat, [NotNullWhen(true)] out T data) => throw new NotImplementedException(); + static HRESULT IOleServices.OleGetClipboard(IDataObject** dataObject) => throw new NotImplementedException(); + static HRESULT IOleServices.OleSetClipboard(IDataObject* dataObject) => throw new NotImplementedException(); + static bool IOleServices.TryGetObjectFromDataObject(IDataObject* dataObject, string requestedFormat, [NotNullWhen(true)] out T data) => throw new NotImplementedException(); static void IOleServices.ValidateDataStoreData(ref string format, bool autoConvert, object? data) => throw new NotImplementedException(); +#else + bool IOleServices.AllowTypeWithoutResolver() => throw new NotImplementedException(); + IComVisibleDataObject IOleServices.CreateDataObject() => throw new NotImplementedException(); + void IOleServices.EnsureThreadState() => throw new ThreadStateException(); + HRESULT IOleServices.GetDataHere(string format, object data, FORMATETC* pformatetc, STGMEDIUM* pmedium) => throw new NotImplementedException(); + bool IOleServices.IsValidTypeForFormat(Type type, string format) => throw new NotImplementedException(); + HRESULT IOleServices.OleFlushClipboard() => throw new NotImplementedException(); + HRESULT IOleServices.OleGetClipboard(IDataObject** dataObject) => throw new NotImplementedException(); + HRESULT IOleServices.OleSetClipboard(IDataObject* dataObject) => throw new NotImplementedException(); + bool IOleServices.TryGetObjectFromDataObject(IDataObject* dataObject, string requestedFormat, [NotNullWhen(true)] out T data) => throw new NotImplementedException(); + void IOleServices.ValidateDataStoreData(ref string format, bool autoConvert, object? data) => throw new NotImplementedException(); +#endif } [Fact] @@ -73,7 +86,10 @@ public void SetData_SetsClipboard() { result.Should().Be(HRESULT.S_OK); data.IsNull.Should().BeFalse(); +#if NET + // This will only hold true on .NET, not .NET Framework for our mock implementation. original.Should().BeSameAs(dataObject); +#endif } } @@ -154,6 +170,8 @@ public override bool GetDataPresent(string format, bool autoConvert) => format == Format || base.GetDataPresent(format, autoConvert); } +#if NET + // This feature currently only works with the .NET implementation of the clipboard code. [Fact] public void SerializableObject_InProcess_DoesNotUseBinaryFormatter() { @@ -173,6 +191,7 @@ public void SerializableObject_InProcess_DoesNotUseBinaryFormatter() data.GetData(typeof(SerializablePerson).FullName!).Should().BeSameAs(person); } +#endif [Serializable] internal class SerializablePerson @@ -263,11 +282,22 @@ public void GetDataObject_InvokeMultipleTimes_Success() [Fact] public void GetFileDropList_InvokeMultipleTimes_Success() { - string testData = "FileDrop"; + string[] testData = ["FileDrop"]; string format = DataFormatNames.FileDrop; - SetAndGetClipboardDataMultipleTimes(format, testData, out ITestDataObject? outData1, out ITestDataObject? outData2); - VerifyResult(testData, format, outData1, outData2); + DataObject dataObject = new(); + dataObject.SetData(format, testData); + HRESULT result = ClipboardCore.SetData(dataObject, copy: false, retryTimes: 1, retryDelay: 0); + result.Should().Be(HRESULT.S_OK); + + ClipboardCore.GetDataObject(out var outData1, retryTimes: 1, retryDelay: 0); + ClipboardCore.GetDataObject(out var outData2, retryTimes: 1, retryDelay: 0); + + outData1.Should().NotBeNull(); + outData2.Should().NotBeNull(); + outData1.GetDataPresent(format).Should().BeTrue(); + outData1.GetData(format, autoConvert: false).Should().BeEquivalentTo(testData); + outData1.GetData(format, autoConvert: false).Should().BeEquivalentTo(outData2.GetData(format, autoConvert: false)); } [Fact] @@ -302,7 +332,7 @@ public void GetText_TextDataFormat_InvokeMultipleTimes_Success(string format) private static void SetAndGetClipboardDataMultipleTimes(string? format, string data, out ITestDataObject? outData1, out ITestDataObject? outData2) { - DataObject dataObject = string.IsNullOrEmpty(format) ? new() : new(format, data); + DataObject dataObject = string.IsNullOrEmpty(format) ? new() : new(format!, data); HRESULT result = ClipboardCore.SetData(dataObject, copy: false, retryTimes: 1, retryDelay: 0); result.Should().Be(HRESULT.S_OK); @@ -412,13 +442,12 @@ public void SetAudio_InvokeByteArray_GetReturnsExpected(byte[]? audioBytes) ClipboardCore.GetDataObject(out var outData, retryTimes: 1, retryDelay: 0); outData.Should().NotBeNull(); outData.GetDataPresent(DataFormatNames.WaveAudio).Should().BeTrue(); - outData.GetData(DataFormatNames.WaveAudio).Should().Be(audioBytes); + outData.GetData(DataFormatNames.WaveAudio).Should().BeEquivalentTo(audioBytes); } public static IEnumerable GetEmptyStreamData() { yield return new object[] { new MemoryStream([1, 2, 3]), new byte[] { 1, 2, 3 } }; - yield return new object[] { new MemoryStream([]), Array.Empty() }; } [Theory] diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardScope.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardScope.cs index f91b8ec662f..a606ab433ac 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardScope.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardScope.cs @@ -8,11 +8,9 @@ namespace System.Private.Windows.Ole; /// /// Ensures the CoreClipboard is cleared before and after a test. /// -internal ref struct ClipboardScope where TOleServices : IOleServices +internal ref struct ClipboardScope where TOleServices : IOleServices, new() { public ClipboardScope() => ClipboardCore.Clear(retryTimes: 1, retryDelay: 0).Should().Be(HRESULT.S_OK); -#pragma warning disable CA1822 // Mark members as static - must be an instance for the dispose pattern matching to work public readonly void Dispose() => ClipboardCore.Clear(retryTimes: 1, retryDelay: 0).Should().Be(HRESULT.S_OK); -#pragma warning restore CA1822 } diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/DataObjectProxy.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/DataObjectProxy.cs index 42e50173308..a4d49f40466 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/DataObjectProxy.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/DataObjectProxy.cs @@ -1,11 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using Windows.Win32.Foundation; using Windows.Win32.System.Com; + +#if NET +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Lifetime = Windows.Win32.System.Com.Lifetime; +#else +using Windows.Win32; +#endif namespace System.Private.Windows.Ole; @@ -33,7 +38,12 @@ public DataObjectProxy(IDataObject* original) #endif ); - Proxy = CCW.Create(this); + Proxy = +#if NET + CCW.Create(this); +#else + ComHelpers.GetComPointer(this); +#endif _agileProxy = new( #if DEBUG @@ -60,11 +70,12 @@ public void Dispose() _agileProxy.Dispose(); } +#if NET internal static class CCW { private static readonly IDataObject.Vtbl* s_vtable = AllocateVTable(); - private static unsafe IDataObject.Vtbl* AllocateVTable() + private static IDataObject.Vtbl* AllocateVTable() { // Allocate and create a singular VTable for this type projection. IDataObject.Vtbl* vtable = (IDataObject.Vtbl*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(CCW), sizeof(IDataObject.Vtbl)); @@ -88,11 +99,11 @@ internal static class CCW /// /// Creates a manual COM Callable Wrapper for the given . /// - public static unsafe IDataObject* Create(DataObjectProxy @object) => + public static IDataObject* Create(DataObjectProxy @object) => (IDataObject*)Lifetime.Allocate(@object, s_vtable); [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe HRESULT QueryInterface(IDataObject* @this, Guid* iid, void** ppObject) + private static HRESULT QueryInterface(IDataObject* @this, Guid* iid, void** ppObject) { if (iid is null || ppObject is null) { @@ -119,45 +130,46 @@ private static unsafe HRESULT QueryInterface(IDataObject* @this, Guid* iid, void } [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe uint AddRef(IDataObject* @this) => Lifetime.AddRef(@this); + private static uint AddRef(IDataObject* @this) => Lifetime.AddRef(@this); [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe uint Release(IDataObject* @this) => Lifetime.Release(@this); + private static uint Release(IDataObject* @this) => Lifetime.Release(@this); [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe HRESULT GetData(IDataObject* @this, FORMATETC* pFormatetc, STGMEDIUM* pMedium) => + private static HRESULT GetData(IDataObject* @this, FORMATETC* pFormatetc, STGMEDIUM* pMedium) => Lifetime.GetObject(@this)?.GetData(pFormatetc, pMedium) ?? HRESULT.COR_E_OBJECTDISPOSED; [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe HRESULT GetDataHere(IDataObject* @this, FORMATETC* pFormatetc, STGMEDIUM* pMedium) => + private static HRESULT GetDataHere(IDataObject* @this, FORMATETC* pFormatetc, STGMEDIUM* pMedium) => Lifetime.GetObject(@this)?.GetDataHere(pFormatetc, pMedium) ?? HRESULT.COR_E_OBJECTDISPOSED; [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe HRESULT QueryGetData(IDataObject* @this, FORMATETC* pFormatetc) => + private static HRESULT QueryGetData(IDataObject* @this, FORMATETC* pFormatetc) => Lifetime.GetObject(@this)?.QueryGetData(pFormatetc) ?? HRESULT.COR_E_OBJECTDISPOSED; [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe HRESULT GetCanonicalFormatEtc(IDataObject* @this, FORMATETC* pFormatetcIn, FORMATETC* pFormatetcOut) => + private static HRESULT GetCanonicalFormatEtc(IDataObject* @this, FORMATETC* pFormatetcIn, FORMATETC* pFormatetcOut) => Lifetime.GetObject(@this)?.GetCanonicalFormatEtc(pFormatetcIn, pFormatetcOut) ?? HRESULT.COR_E_OBJECTDISPOSED; [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe HRESULT SetData(IDataObject* @this, FORMATETC* pFormatetc, STGMEDIUM* pMedium, BOOL fRelease) => + private static HRESULT SetData(IDataObject* @this, FORMATETC* pFormatetc, STGMEDIUM* pMedium, BOOL fRelease) => Lifetime.GetObject(@this)?.SetData(pFormatetc, pMedium, fRelease) ?? HRESULT.COR_E_OBJECTDISPOSED; [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe HRESULT EnumFormatEtc(IDataObject* @this, uint dwDirection, IEnumFORMATETC** ppEnumFormatEtc) => + private static HRESULT EnumFormatEtc(IDataObject* @this, uint dwDirection, IEnumFORMATETC** ppEnumFormatEtc) => Lifetime.GetObject(@this)?.EnumFormatEtc(dwDirection, ppEnumFormatEtc) ?? HRESULT.COR_E_OBJECTDISPOSED; [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe HRESULT DAdvise(IDataObject* @this, FORMATETC* pFormatetc, uint advf, IAdviseSink* pAdvSink, uint* pdwConnection) => + private static HRESULT DAdvise(IDataObject* @this, FORMATETC* pFormatetc, uint advf, IAdviseSink* pAdvSink, uint* pdwConnection) => Lifetime.GetObject(@this)?.DAdvise(pFormatetc, advf, pAdvSink, pdwConnection) ?? HRESULT.COR_E_OBJECTDISPOSED; [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe HRESULT DUnadvise(IDataObject* @this, uint dwConnection) => + private static HRESULT DUnadvise(IDataObject* @this, uint dwConnection) => Lifetime.GetObject(@this)?.DUnadvise(dwConnection) ?? HRESULT.COR_E_OBJECTDISPOSED; [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] - private static unsafe HRESULT EnumDAdvise(IDataObject* @this, IEnumSTATDATA** ppEnumAdvise) => + private static HRESULT EnumDAdvise(IDataObject* @this, IEnumSTATDATA** ppEnumAdvise) => Lifetime.GetObject(@this)?.EnumDAdvise(ppEnumAdvise) ?? HRESULT.COR_E_OBJECTDISPOSED; } +#endif } diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/MockOleServices.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/MockOleServices.cs index 7dd79fbb080..f39d2edf028 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/MockOleServices.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/MockOleServices.cs @@ -11,17 +11,41 @@ namespace System.Private.Windows.Ole; /// Mock implementation of for testing purposes. /// /// Used to get an instance for each test class so test classes can run asynchronously. -internal class MockOleServices : IOleServices +internal unsafe class MockOleServices : IOleServices { private static DataObjectProxy? s_dataObjectProxy; - static bool IOleServices.AllowTypeWithoutResolver() => true; - static void IOleServices.EnsureThreadState() { } - static unsafe HRESULT IOleServices.GetDataHere(string format, object data, FORMATETC* pformatetc, STGMEDIUM* pmedium) => HRESULT.DV_E_TYMED; - static bool IOleServices.IsValidTypeForFormat(Type type, string format) => true; - static void IOleServices.ValidateDataStoreData(ref string format, bool autoConvert, object? data) { } +#if NET + static +#endif + bool IOleServices.AllowTypeWithoutResolver() => true; - static unsafe bool IOleServices.TryGetObjectFromDataObject( +#if NET + static +#endif + void IOleServices.EnsureThreadState() + { } + +#if NET + static +#endif + HRESULT IOleServices.GetDataHere(string format, object data, FORMATETC* pformatetc, STGMEDIUM* pmedium) => HRESULT.DV_E_TYMED; + +#if NET + static +#endif + bool IOleServices.IsValidTypeForFormat(Type type, string format) => true; + +#if NET + static +#endif + void IOleServices.ValidateDataStoreData(ref string format, bool autoConvert, object? data) + { } + +#if NET + static +#endif + bool IOleServices.TryGetObjectFromDataObject( IDataObject* dataObject, string requestedFormat, [NotNullWhen(true)] out T data) @@ -30,13 +54,19 @@ static unsafe bool IOleServices.TryGetObjectFromDataObject( return false; } - static HRESULT IOleServices.OleFlushClipboard() +#if NET + static +#endif + HRESULT IOleServices.OleFlushClipboard() { // Would need to implement copying the raw TYMED data into a new object to mimic the real behavior. throw new NotImplementedException(); } - static unsafe HRESULT IOleServices.OleGetClipboard(IDataObject** dataObject) +#if NET + static +#endif + HRESULT IOleServices.OleGetClipboard(IDataObject** dataObject) { if (dataObject is null) { @@ -54,7 +84,10 @@ static unsafe HRESULT IOleServices.OleGetClipboard(IDataObject** dataObject) return HRESULT.S_OK; } - static unsafe HRESULT IOleServices.OleSetClipboard(IDataObject* dataObject) +#if NET + static +#endif + HRESULT IOleServices.OleSetClipboard(IDataObject* dataObject) { if (dataObject is null) { @@ -73,5 +106,8 @@ static unsafe HRESULT IOleServices.OleSetClipboard(IDataObject* dataObject) return HRESULT.S_OK; } - static IComVisibleDataObject IOleServices.CreateDataObject() => new TestDataObject>(); +#if NET + static +#endif + IComVisibleDataObject IOleServices.CreateDataObject() => new TestDataObject>(); } diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/NativeDataObjectMock.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/NativeDataObjectMock.cs index c15e5a72850..126e511e2cf 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/NativeDataObjectMock.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/NativeDataObjectMock.cs @@ -6,7 +6,11 @@ namespace System.Private.Windows.Ole; -internal abstract unsafe class NativeDataObjectMock : DisposableBase, IDataObject.Interface, IManagedWrapper +#if NET +internal abstract unsafe partial class NativeDataObjectMock : IManagedWrapper { } +#endif + +internal abstract unsafe partial class NativeDataObjectMock : DisposableBase, IDataObject.Interface { public virtual HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) => throw new NotImplementedException(); public virtual HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) => throw new NotImplementedException(); diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/NativeToManagedAdapterTests.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/NativeToManagedAdapterTests.cs index 50221dcf593..3c38cddcd41 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/NativeToManagedAdapterTests.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/NativeToManagedAdapterTests.cs @@ -4,12 +4,15 @@ using System.ComponentModel; using System.Formats.Nrbf; using System.Private.Windows.BinaryFormat; -using System.Text.Json; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.System.Com; using Windows.Win32.System.Memory; +#if NET +using System.Text.Json; +#endif + using Composition = System.Private.Windows.Ole.Composition< System.Private.Windows.Ole.MockOleServices, System.Private.Windows.Nrbf.CoreNrbfSerializer, @@ -96,6 +99,7 @@ public void GetData_CustomType_BinaryFormattedData_AsSerializationRecord() data!.TypeName.AssemblyQualifiedName.Should().Be("System.Int32[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); } +#if NET [Fact] public void GetData_CustomType_BinaryFormattedJson_AsSerializationRecord() { @@ -111,6 +115,7 @@ public void GetData_CustomType_BinaryFormattedJson_AsSerializationRecord() composition.TryGetData(nameof(NativeToManagedAdapterTests), out SerializationRecord? data).Should().BeTrue(); data!.TypeName.AssemblyQualifiedName.Should().Be("System.Private.Windows.JsonData, System.Private.Windows.VirtualJson"); } +#endif [Theory] [BoolData] diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TestDataObject.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TestDataObject.cs index 0334a3b27df..ed27f65403e 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TestDataObject.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TestDataObject.cs @@ -13,17 +13,32 @@ internal unsafe class TestDataObject : IComVisibleDataObject, ITestDataObject, IDataObjectInternal, ITestDataObject> - where TOleServices : IOleServices + where TOleServices : IOleServices, new() { private readonly Composition _innerData; - static TestDataObject IDataObjectInternal, ITestDataObject>.Create() => +#if NET + static +#endif + TestDataObject IDataObjectInternal, ITestDataObject>.Create() => new(); - static TestDataObject IDataObjectInternal, ITestDataObject>.Create(IDataObject* dataObject) => + +#if NET + static +#endif + TestDataObject IDataObjectInternal, ITestDataObject>.Create(IDataObject* dataObject) => new(dataObject); - static TestDataObject IDataObjectInternal, ITestDataObject>.Create(object data) => + +#if NET + static +#endif + TestDataObject IDataObjectInternal, ITestDataObject>.Create(object data) => new(data); - static IDataObjectInternal IDataObjectInternal, ITestDataObject>.Wrap(ITestDataObject data) => + +#if NET + static +#endif + IDataObjectInternal IDataObjectInternal, ITestDataObject>.Wrap(ITestDataObject data) => new TestDataObjectAdapter(data); bool IDataObjectInternal, ITestDataObject>.TryUnwrapUserDataObject([NotNullWhen(true)] out ITestDataObject? dataObject) @@ -32,7 +47,7 @@ bool IDataObjectInternal, ITestDataObject>.TryUnwra internal TestDataObject(IDataObject* data) => _innerData = Composition.Create(data); - internal TestDataObject() => + public TestDataObject() => _innerData = Composition.Create(); internal TestDataObject(object data) => @@ -114,13 +129,13 @@ internal virtual bool TryUnwrapUserDataObject([NotNullWhen(true)] out ITestDataO // Native callbacks follow - public unsafe HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) => _innerData.GetData(pformatetcIn, pmedium); - public unsafe HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) => _innerData.GetDataHere(pformatetc, pmedium); - public unsafe HRESULT QueryGetData(FORMATETC* pformatetc) => _innerData.QueryGetData(pformatetc); - public unsafe HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) => _innerData.GetCanonicalFormatEtc(pformatectIn, pformatetcOut); - public unsafe HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) => _innerData.SetData(pformatetc, pmedium, fRelease); - public unsafe HRESULT EnumFormatEtc(uint dwDirection, IEnumFORMATETC** ppenumFormatEtc) => _innerData.EnumFormatEtc(dwDirection, ppenumFormatEtc); - public unsafe HRESULT DAdvise(FORMATETC* pformatetc, uint advf, IAdviseSink* pAdvSink, uint* pdwConnection) => _innerData.DAdvise(pformatetc, advf, pAdvSink, pdwConnection); + public HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) => _innerData.GetData(pformatetcIn, pmedium); + public HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) => _innerData.GetDataHere(pformatetc, pmedium); + public HRESULT QueryGetData(FORMATETC* pformatetc) => _innerData.QueryGetData(pformatetc); + public HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) => _innerData.GetCanonicalFormatEtc(pformatectIn, pformatetcOut); + public HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) => _innerData.SetData(pformatetc, pmedium, fRelease); + public HRESULT EnumFormatEtc(uint dwDirection, IEnumFORMATETC** ppenumFormatEtc) => _innerData.EnumFormatEtc(dwDirection, ppenumFormatEtc); + public HRESULT DAdvise(FORMATETC* pformatetc, uint advf, IAdviseSink* pAdvSink, uint* pdwConnection) => _innerData.DAdvise(pformatetc, advf, pAdvSink, pdwConnection); public HRESULT DUnadvise(uint dwConnection) => _innerData.DUnadvise(dwConnection); - public unsafe HRESULT EnumDAdvise(IEnumSTATDATA** ppenumAdvise) => _innerData.EnumDAdvise(ppenumAdvise); + public HRESULT EnumDAdvise(IEnumSTATDATA** ppenumAdvise) => _innerData.EnumDAdvise(ppenumAdvise); } diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TestFormat.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TestFormat.cs index 55a4114ee76..4a926d26991 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TestFormat.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TestFormat.cs @@ -5,6 +5,10 @@ namespace System.Private.Windows.Ole; internal class TestFormat : IDataFormat { + public TestFormat() : this(string.Empty, 0) + { + } + public TestFormat(string name, int id) { Name = name; @@ -13,5 +17,9 @@ public TestFormat(string name, int id) public string Name { get; } public int Id { get; } - static TestFormat IDataFormat.Create(string name, int id) => new(name, id); + +#if NET + static +#endif + TestFormat IDataFormat.Create(string name, int id) => new(name, id); } diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TypeBinderTests.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TypeBinderTests.cs index da0d2796070..ec353f80219 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TypeBinderTests.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TypeBinderTests.cs @@ -77,7 +77,7 @@ public void BindToType_Resolver_TypedRequest_CoreSerializer_SupportedTypesSuccee { Format = "test", TypedRequest = true, - Resolver = (TypeName typeName) => typeof(MyClass).Matches(typeName) ? typeof(MyClass) : null + Resolver = typeName => typeof(MyClass).Matches(typeName) ? typeof(MyClass) : null }; TypeBinder binder = new(typeof(MyClass), in request); @@ -115,7 +115,7 @@ public void BindToType_Resolver_CoreSerializer_Matches_DoesNotReturnSupportedTyp DataRequest request = new("test") { TypedRequest = true, - Resolver = (TypeName typeName) => typeof(MyClass) + Resolver = typeName => typeof(MyClass) }; // Shouldn't fall back to the core serializer. @@ -130,7 +130,7 @@ public void BindToType_Resolver_CoreSerializer_Throws_DoesNotReturnSupportedType DataRequest request = new("test") { TypedRequest = true, - Resolver = (TypeName typeName) => throw new NotSupportedException() + Resolver = typeName => throw new NotSupportedException() }; // Shouldn't fall back to the core serializer. @@ -145,7 +145,7 @@ public void BindToType_Resolver_CoreSerializer_Null_ReturnsSupportedType() DataRequest request = new("test") { TypedRequest = true, - Resolver = (TypeName typeName) => null! + Resolver = typeName => null! }; // Shouldn't fall back to the core serializer. @@ -158,42 +158,83 @@ internal sealed class AlwaysDefaultSerializer : INrbfSerializer { private AlwaysDefaultSerializer() { } - public static bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) +#if NET + public static +#else + public +#endif + bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) { type = null; return false; } - public static bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) +#if NET + public static +#else + public +#endif + bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) { value = null; return false; } - public static bool TryWriteObject(Stream stream, object value) => false; +#if NET + public static +#else + public +#endif - public static bool IsFullySupportedType(Type type) => false; + bool TryWriteObject(Stream stream, object value) => false; + +#if NET + public static +#else + public +#endif + bool IsFullySupportedType(Type type) => false; } internal sealed class BindingAlwaysSucceedsSerializer : INrbfSerializer { private BindingAlwaysSucceedsSerializer() { } - public static bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) +#if NET + public static +#else + public +#endif + bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) { type = Type.GetType(typeName.AssemblyQualifiedName); return type is not null; } - public static bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) +#if NET + public static +#else + public +#endif + bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) { value = null; return false; } - public static bool TryWriteObject(Stream stream, object value) => false; - - public static bool IsFullySupportedType(Type type) => true; +#if NET + public static +#else + public +#endif + bool TryWriteObject(Stream stream, object value) => false; + +#if NET + public static +#else + public +#endif + bool IsFullySupportedType(Type type) => true; } internal sealed class AlwaysReturnsTypeSerializer : INrbfSerializer @@ -201,21 +242,41 @@ internal sealed class AlwaysReturnsTypeSerializer : INrbfSerializer { private AlwaysReturnsTypeSerializer() { } - public static bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) +#if NET + public static +#else + public +#endif + bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) { type = typeof(T); return true; } - public static bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) +#if NET + public static +#else + public +#endif + bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) { value = null; return false; } - public static bool TryWriteObject(Stream stream, object value) => false - ; - public static bool IsFullySupportedType(Type type) => true; +#if NET + public static +#else + public +#endif + bool TryWriteObject(Stream stream, object value) => false; + +#if NET + public static +#else + public +#endif + bool IsFullySupportedType(Type type) => true; } private class MyClass { } diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/TypeExtensionsTests.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/TypeExtensionsTests.cs index cb621b664aa..a0539743f1d 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/TypeExtensionsTests.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/TypeExtensionsTests.cs @@ -18,12 +18,26 @@ public class TypeExtensionsTests { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.AssemblyCultureName, true }, { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.AssemblyName, true }, { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.AssemblyPublicKeyToken, true }, - { typeof(int), TypeName.Parse($"System.Int32, {Assemblies.Mscorlib}"), (int)TypeNameComparison.AllButAssemblyVersion, false }, + { + typeof(int), TypeName.Parse($"System.Int32, {Assemblies.Mscorlib}"), (int)TypeNameComparison.AllButAssemblyVersion, +#if NET + false +#else + true +#endif + }, { typeof(int), TypeName.Parse($"System.Int32, {Assemblies.Mscorlib}"), (int)TypeNameComparison.TypeFullName, true }, { typeof(int), TypeName.Parse($"Int32, {Assemblies.Mscorlib}"), (int)TypeNameComparison.TypeFullName, false }, { typeof(int?), TypeName.Parse(typeof(int).AssemblyQualifiedName), (int)TypeNameComparison.AllButAssemblyVersion, false }, { typeof(int?), TypeName.Parse(typeof(int?).AssemblyQualifiedName), (int)TypeNameComparison.All, true }, - { typeof(int?[]), TypeName.Parse($"System.Nullable`1[[System.Int32, {Assemblies.Mscorlib}]][], {Assemblies.Mscorlib}"), (int)TypeNameComparison.AllButAssemblyVersion, false}, + { + typeof(int?[]), TypeName.Parse($"System.Nullable`1[[System.Int32, {Assemblies.Mscorlib}]][], {Assemblies.Mscorlib}"), (int)TypeNameComparison.AllButAssemblyVersion, +#if NET + false +#else + true +#endif + }, { typeof(DayOfWeek), TypeName.Parse($"System.Nullable`1[[System.DayOfWeek, {Assemblies.Mscorlib}]], {Assemblies.Mscorlib}"), (int)TypeNameComparison.AllButAssemblyVersion, false }, // Culture is incorrect. { typeof(int), TypeName.Parse(typeof(int).AssemblyQualifiedName!.Replace("neutral", "en-US")), (int)TypeNameComparison.AssemblyCultureName, false }, @@ -50,8 +64,17 @@ public void Matches_Type_InvalidKey() { // We assert here as we're not expecting to see this exception in normal usage. using NoAssertContext noAsserts = new(); + + // On .NET Framework: + // System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 bool success = typeof(int).Matches( - TypeName.Parse(typeof(int).AssemblyQualifiedName!.Replace("7cec85d7bea7798e", "7cec00000ea7798e")), + TypeName.Parse(typeof(int).AssemblyQualifiedName!.Replace( +#if NET + "7cec85d7bea7798e", +#else + "b77a5c561934e089", +#endif + "7cec00000ea7798e")), TypeNameComparison.AllButAssemblyVersion); success.Should().BeFalse(); } @@ -111,7 +134,7 @@ public void BinaryFormatter_BinderTypes(object value, string[] expected) _ = BinarySerialization.DeserializeFromStream( stream, binder: new BindToTypeBinder( - (string assembly, string type) => + (assembly, type) => { bindings.Add($"{type}, {assembly}"); return null; diff --git a/src/System.Windows.Forms/System/Windows/Forms/OLE/DropTarget.cs b/src/System.Windows.Forms/System/Windows/Forms/OLE/DropTarget.cs index 8bb8623ceb1..00b8e62f88b 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/OLE/DropTarget.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/OLE/DropTarget.cs @@ -172,6 +172,11 @@ HRESULT OleIDropTarget.Interface.DragLeave() return HRESULT.S_OK; } +#pragma warning disable CA1416 // Validate platform compatibility + // IDataObjectAsyncCapability is only available on Windows 8 and later. Our baseline is beyond that and this + // will fail gracefully in any case as we won't be able to query it successfully, which is always a possibility + // as most objects will not support it anyway. + HRESULT OleIDropTarget.Interface.Drop(Com.IDataObject* pDataObj, MODIFIERKEYS_FLAGS grfKeyState, POINTL pt, DROPEFFECT* pdwEffect) { if (pdwEffect is null) @@ -298,6 +303,7 @@ private HRESULT HandleOnDragDrop(DragEventArgs e, IDataObjectAsyncCapability* as return HRESULT.S_OK; } +#pragma warning restore CA1416 // Validate platform compatibility private void UpdateDropDescription(DragEventArgs e) { diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ComponentModel/Com2Interop/COM2FontConverterTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ComponentModel/Com2Interop/COM2FontConverterTests.cs index 9b22a7415c2..cd6c3c496e2 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ComponentModel/Com2Interop/COM2FontConverterTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ComponentModel/Com2Interop/COM2FontConverterTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; From b4762f3e159fecd43e9c9999bccd8c6a0bf283b8 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Wed, 11 Mar 2026 14:34:55 -0700 Subject: [PATCH 2/6] Attempt to address the NETSDK1047 CI error --- .../System.Private.Windows.Core.Tests.csproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System.Private.Windows.Core.Tests.csproj b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System.Private.Windows.Core.Tests.csproj index 57464652485..1de64263e11 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System.Private.Windows.Core.Tests.csproj +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System.Private.Windows.Core.Tests.csproj @@ -20,6 +20,14 @@ doesn't properly recognize this on .NET Framework. --> $(NoWarn);CA1822 + + + AnyCPU From 0171260bf9cd6396ce039901fcd2a4a1f88eceee Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Thu, 12 Mar 2026 10:26:00 -0700 Subject: [PATCH 3/6] Address review comments --- .../System/TypeExtensions.cs | 31 ++++++++++++ .../src/Framework/Windows/Win32/IComIID.cs | 2 +- .../Windows/Ole/BinaryFormatUtilities.cs | 11 +++-- .../Ole/Composition.NativeToManagedAdapter.cs | 19 -------- .../System/Private/Windows/Ole/Composition.cs | 4 +- .../Private/Windows/Ole/DragDropHelper.cs | 5 +- .../src/System/TypeExtensions.cs | 40 +--------------- .../Windows/Win32/System/Com/ComHelpers.cs | 3 +- .../System/TypeExtensionsTests.cs | 47 ++++++++++++++++++- 9 files changed, 93 insertions(+), 69 deletions(-) diff --git a/src/Microsoft.Private.Windows.Polyfills/System/TypeExtensions.cs b/src/Microsoft.Private.Windows.Polyfills/System/TypeExtensions.cs index c104fbf32bc..ae3129e7294 100644 --- a/src/Microsoft.Private.Windows.Polyfills/System/TypeExtensions.cs +++ b/src/Microsoft.Private.Windows.Polyfills/System/TypeExtensions.cs @@ -25,5 +25,36 @@ internal static partial class TypeExtensions && !type.IsPointer && !type.IsConstructedGenericType && !type.IsGenericParameter; + + /// + /// Determines whether the given is a single-dimensional, zero-indexed (SZ) array, + /// equivalent to Type.IsSZArray which is not available on .NET Framework. + /// + /// + /// + /// The CLR distinguishes two kinds of array types at the metadata level: + /// + /// + /// ELEMENT_TYPE_SZARRAY the "vector" type (e.g. int[]), single-dimensional + /// and always zero-indexed. + /// ELEMENT_TYPE_ARRAY with rank 1 — a general array that happens to have one dimension + /// (e.g. int[*]), which can have a non-zero lower bound. + /// + /// + /// Both return for and 1 for + /// , so those properties alone cannot distinguish them. + /// + /// + /// The trick here exploits (parameterless), which always + /// produces the SZ array type for the element type. If the resulting type is reference-equal + /// to the original, the original is an SZ array. If it is not (as would be the case for + /// int[*], whose MakeArrayType() yields int[]), the original is a + /// general rank-1 array. The comparison resolves to a RuntimeTypeHandle identity + /// check on cached objects — no reflection metadata walking or string + /// parsing is involved. + /// + /// + public bool IsSZArray => + type.IsArray && type.GetElementType()!.MakeArrayType() == type; } } diff --git a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/IComIID.cs b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/IComIID.cs index 6af113c4608..ae2a611c7ad 100644 --- a/src/System.Private.Windows.Core/src/Framework/Windows/Win32/IComIID.cs +++ b/src/System.Private.Windows.Core/src/Framework/Windows/Win32/IComIID.cs @@ -11,7 +11,7 @@ namespace Windows.Win32; /// On .NET 6 and later, this is provided by CSWin32 as an abstract static. /// /// -public interface IComIID +internal interface IComIID { /// /// The identifier (IID) GUID for this interface. diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/BinaryFormatUtilities.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/BinaryFormatUtilities.cs index 1cf4022be2c..9328971520a 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/BinaryFormatUtilities.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/BinaryFormatUtilities.cs @@ -10,10 +10,15 @@ namespace System.Private.Windows.Ole; -internal static partial class BinaryFormatUtilities where TNrbfSerializer : INrbfSerializer +internal static partial class BinaryFormatUtilities +#if NET + where TNrbfSerializer : INrbfSerializer +#else + where TNrbfSerializer : INrbfSerializer, new() +#endif { #if NETFRAMEWORK - private static readonly INrbfSerializer s_nrbfSerializer = Activator.CreateInstance(); + private static readonly INrbfSerializer s_nrbfSerializer = new TNrbfSerializer(); #endif /// @@ -158,7 +163,7 @@ record = stream.DecodeNrbf(); } } -#if NET +#if OLE_JSON if (type is null) { // Serializer didn't recognize the type, look for and deserialize a JSON object. diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs index a96e2bd36b4..dabbd25b768 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs @@ -11,25 +11,6 @@ namespace System.Private.Windows.Ole; -internal static class ThreadOleServices -{ - [ThreadStatic] -#pragma warning disable IDE1006 // Naming Styles - public static IOleServices? OleServices; - - [ThreadStatic] - public static Nrbf.INrbfSerializer? NrbfSerializer; - - [ThreadStatic] - public static DataFormatFactory? DataFormatFactory; -#pragma warning restore IDE1006 // Naming Styles -} - -internal abstract class DataFormatFactory -{ - public abstract object CreateDataFormat(string format); -} - internal unsafe partial class Composition { /// diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.cs index b28df416012..a43965eaa8a 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.cs @@ -6,7 +6,7 @@ using Windows.Win32.System.Com; using ComTypes = System.Runtime.InteropServices.ComTypes; -#if NET +#if OLE_JSON using System.Text.Json; #endif @@ -161,7 +161,7 @@ public void SetDataAsJson(T data, string? format = default) // Avoid exposing our internal JsonData return -#if NET +#if OLE_JSON result is IJsonData json ? json.Deserialize() : #endif result; diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DragDropHelper.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DragDropHelper.cs index c9fc20099d2..698de42f078 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DragDropHelper.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DragDropHelper.cs @@ -20,15 +20,16 @@ namespace System.Private.Windows.Ole; /// (via ) currently. /// internal static unsafe class DragDropHelper - where TOleServices : IOleServices #if NET + where TOleServices : IOleServices where TDataFormat : IDataFormat #else + where TOleServices : IOleServices, new() where TDataFormat : IDataFormat, new() #endif { #if NETFRAMEWORK - private static readonly TOleServices s_oleServices = Activator.CreateInstance(); + private static readonly TOleServices s_oleServices = new TOleServices(); #endif /// diff --git a/src/System.Private.Windows.Core/src/System/TypeExtensions.cs b/src/System.Private.Windows.Core/src/System/TypeExtensions.cs index f92fcf56a87..ba46c10bb4e 100644 --- a/src/System.Private.Windows.Core/src/System/TypeExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/TypeExtensions.cs @@ -40,12 +40,7 @@ internal static bool Matches( || type.IsConstructedGenericType != typeName.IsConstructedGenericType || type.IsNested != typeName.IsNested || (type.IsArray && type.GetArrayRank() != typeName.GetArrayRank()) -#if NET - || type.IsSZArray != typeName.IsSZArray -#else - || IsSZArray(type) != typeName.IsSZArray -#endif - ) + || type.IsSZArray != typeName.IsSZArray) { return false; } @@ -287,37 +282,4 @@ static bool TryComparePublicKeyTokenToKey(ReadOnlySpan publicKeyToken, Rea } } } - -#if NETFRAMEWORK - /// - /// Determines whether the given is a single-dimensional, zero-indexed (SZ) array, - /// equivalent to Type.IsSZArray which is not available on .NET Framework. - /// - /// - /// - /// The CLR distinguishes two kinds of array types at the metadata level: - /// - /// - /// ELEMENT_TYPE_SZARRAY the "vector" type (e.g. int[]), single-dimensional - /// and always zero-indexed. - /// ELEMENT_TYPE_ARRAY with rank 1 — a general array that happens to have one dimension - /// (e.g. int[*]), which can have a non-zero lower bound. - /// - /// - /// Both return for and 1 for - /// , so those properties alone cannot distinguish them. - /// - /// - /// The trick here exploits (parameterless), which always - /// produces the SZ array type for the element type. If the resulting type is reference-equal - /// to the original, the original is an SZ array. If it is not (as would be the case for - /// int[*], whose MakeArrayType() yields int[]), the original is a - /// general rank-1 array. The comparison resolves to a RuntimeTypeHandle identity - /// check on cached objects — no reflection metadata walking or string - /// parsing is involved. - /// - /// - private static bool IsSZArray(Type type) => - type.IsArray && type.GetElementType()!.MakeArrayType() == type; -#endif } diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComHelpers.cs b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComHelpers.cs index 9cc3f11c67a..b53975ec19a 100644 --- a/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComHelpers.cs +++ b/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComHelpers.cs @@ -294,8 +294,7 @@ internal static object GetObjectForIUnknown(ComScope com where TInterface : unmanaged, IComIID => GetObjectForIUnknown(comScope.Value); /// - /// Gets a runtime capable wrapper for . - /// Will attempt to unwrap ComWrapper RCWs if available. + /// Gets a runtime callable wrapper for the given IUnknown. Will attempt to unwrap ComWrapper RCWs if available. /// /// is . internal static object GetObjectForIUnknown(ComUnknown* unknown) diff --git a/src/test/unit/Microsoft.Private.Windows.Polyfills/System/TypeExtensionsTests.cs b/src/test/unit/Microsoft.Private.Windows.Polyfills/System/TypeExtensionsTests.cs index dad47acc408..d2a45a5e971 100644 --- a/src/test/unit/Microsoft.Private.Windows.Polyfills/System/TypeExtensionsTests.cs +++ b/src/test/unit/Microsoft.Private.Windows.Polyfills/System/TypeExtensionsTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace System.Tests; @@ -88,4 +88,49 @@ public void IsTypeDefinition_Interface_ReturnsTrue() { typeof(IDisposable).IsTypeDefinition.Should().BeTrue(); } + + [Fact] + public void IsSZArray_SingleDimensionalArray_ReturnsTrue() + { + typeof(int[]).IsSZArray.Should().BeTrue(); + } + + [Fact] + public void IsSZArray_ReferenceTypeArray_ReturnsTrue() + { + typeof(string[]).IsSZArray.Should().BeTrue(); + } + + [Fact] + public void IsSZArray_MultiDimensionalArray_ReturnsFalse() + { + typeof(int[,]).IsSZArray.Should().BeFalse(); + } + + [Fact] + public void IsSZArray_JaggedArray_ReturnsTrue() + { + typeof(int[][]).IsSZArray.Should().BeTrue(); + } + + [Fact] + public void IsSZArray_NonArrayType_ReturnsFalse() + { + typeof(int).IsSZArray.Should().BeFalse(); + } + + [Fact] + public void IsSZArray_NonArrayClassType_ReturnsFalse() + { + typeof(string).IsSZArray.Should().BeFalse(); + } + + [Fact] + public void IsSZArray_Rank1GeneralArray_ReturnsFalse() + { + // Array.CreateInstance with a lower bound creates a general rank-1 array (int[*]), + // which is distinct from an SZ array (int[]). + Type rank1General = Array.CreateInstance(typeof(int), [1], [1]).GetType(); + rank1General.IsSZArray.Should().BeFalse(); + } } From 95753c57f8eed06d127dff060ae6448609bfb667 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Thu, 12 Mar 2026 12:28:24 -0700 Subject: [PATCH 4/6] Address some test issues --- .../src/System/IO/StreamExtensions.cs | 15 ++++++++------- .../Ole/Composition.ManagedToNativeAdapter.cs | 7 +++---- .../ClipboardBinaryFormatterFullCompatScope.cs | 18 +++++++++++++++--- .../Private/Windows/Ole/ClipboardCoreTests.cs | 8 ++++++++ 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/System.Private.Windows.Core/src/System/IO/StreamExtensions.cs b/src/System.Private.Windows.Core/src/System/IO/StreamExtensions.cs index 03e2ff4cc56..17a5498b071 100644 --- a/src/System.Private.Windows.Core/src/System/IO/StreamExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/IO/StreamExtensions.cs @@ -28,13 +28,6 @@ internal static unsafe HRESULT SaveStreamToHGLOBAL(this Stream stream, ref HGLOB { int size = checked((int)stream.Length); - if (size == 0) - { - // A zero-size GMEM_MOVEABLE allocation creates a discarded handle - // that cannot be locked. Leave the existing HGLOBAL as-is. - return HRESULT.S_OK; - } - if (!hglobal.IsNull) { HGLOBAL freed = PInvokeCore.GlobalFree(hglobal); @@ -44,6 +37,14 @@ internal static unsafe HRESULT SaveStreamToHGLOBAL(this Stream stream, ref HGLOB } } + if (size == 0) + { + // A zero-size GMEM_MOVEABLE allocation creates a discarded handle + // that cannot be locked. Set to null so the caller knows there's no data. + hglobal = HGLOBAL.Null; + return HRESULT.S_OK; + } + hglobal = PInvokeCore.GlobalAlloc(GLOBAL_ALLOC_FLAGS.GMEM_MOVEABLE, (uint)size); if (hglobal.IsNull) { diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.ManagedToNativeAdapter.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.ManagedToNativeAdapter.cs index 63972ab4b99..b2dd42b8fb0 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.ManagedToNativeAdapter.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.ManagedToNativeAdapter.cs @@ -116,12 +116,12 @@ public HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) string format = DataFormatsCore.GetOrAddFormat(pformatetc->cfFormat).Name; - if (!_dataObject.GetDataPresent(format, autoConvert: false)) + if (!_dataObject.GetDataPresent(format)) { return HRESULT.DV_E_FORMATETC; } - if (_dataObject.GetData(format, autoConvert: false) is not object data) + if (_dataObject.GetData(format) is not object data) { return HRESULT.E_UNEXPECTED; } @@ -181,8 +181,7 @@ public HRESULT QueryGetData(FORMATETC* pformatetc) return HRESULT.S_FALSE; } - // The NativeToManagedAdapter handles the autoConvert behavior. - if (!_dataObject.GetDataPresent(DataFormatsCore.GetOrAddFormat(pformatetc->cfFormat).Name, autoConvert: false)) + if (!_dataObject.GetDataPresent(DataFormatsCore.GetOrAddFormat(pformatetc->cfFormat).Name)) { return HRESULT.DV_E_FORMATETC; } diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardBinaryFormatterFullCompatScope.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardBinaryFormatterFullCompatScope.cs index 47b77634bfc..25bc2e10c4b 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardBinaryFormatterFullCompatScope.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardBinaryFormatterFullCompatScope.cs @@ -18,8 +18,20 @@ public ClipboardBinaryFormatterFullCompatScope() public void Dispose() { - _binaryFormatterScope.Dispose(); - _binaryFormatterInClipboardDragDropScope.Dispose(); - _nrbfSerializerInClipboardDragDropScope.Dispose(); + try + { + _nrbfSerializerInClipboardDragDropScope.Dispose(); + } + finally + { + try + { + _binaryFormatterInClipboardDragDropScope.Dispose(); + } + finally + { + _binaryFormatterScope.Dispose(); + } + } } } diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardCoreTests.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardCoreTests.cs index 61578a6c1b8..418dc782691 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardCoreTests.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/ClipboardCoreTests.cs @@ -131,8 +131,14 @@ public void RoundTrip_Text() data.GetDataPresent(DataFormatNames.UnicodeText).Should().BeTrue(); data.GetData(DataFormatNames.UnicodeText).Should().Be("Hello, World!"); + +#if NET + // On .NET the original DataObject is unwrapped, so the DataStore distinguishes between + // explicitly stored and auto-converted formats. On .NET Framework, a NativeToManagedAdapter + // wraps the COM pointer, and the COM round-trip does not preserve that distinction. data.GetDataPresent(DataFormatNames.UnicodeText, autoConvert: false).Should().BeFalse(); data.GetData(DataFormatNames.UnicodeText, autoConvert: false).Should().BeNull(); +#endif IDataObjectInternal iDataObject = data.Should().BeAssignableTo().Subject; iDataObject.TryGetData(out string? text).Should().BeTrue(); @@ -142,8 +148,10 @@ public void RoundTrip_Text() text.Should().Be("Hello, World!"); iDataObject.TryGetData(DataFormatNames.UnicodeText, out text).Should().BeTrue(); text.Should().Be("Hello, World!"); +#if NET iDataObject.TryGetData(DataFormatNames.UnicodeText, autoConvert: false, out text).Should().BeFalse(); text.Should().BeNull(); +#endif } [Fact] From fcad58f32b91d5f3311bac7439149cc6a865f2af Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Thu, 12 Mar 2026 13:11:10 -0700 Subject: [PATCH 5/6] Attempt to address the context caching --- .../tests/TestUtilities/AppContextSwitchScope.cs | 3 --- .../tests/System.Private.Windows.Core.Tests/app.config | 10 ++++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/app.config diff --git a/src/Common/tests/TestUtilities/AppContextSwitchScope.cs b/src/Common/tests/TestUtilities/AppContextSwitchScope.cs index 98e4aaaca32..76211adf928 100644 --- a/src/Common/tests/TestUtilities/AppContextSwitchScope.cs +++ b/src/Common/tests/TestUtilities/AppContextSwitchScope.cs @@ -26,15 +26,12 @@ public AppContextSwitchScope(string switchName, Func? getDefaultValue, boo { bool isEnabled; -#if NET - // .NET Framework doesn't cache switches. if (!AppContext.TryGetSwitch(AppContextSwitchNames.LocalAppContext_DisableCaching, out isEnabled) || !isEnabled) { // It doesn't make sense to try messing with AppContext switches if they are going to be cached. throw new InvalidOperationException("LocalAppContext switch caching is not disabled."); } -#endif // AppContext won't have any switch value until it is explicitly set. if (!AppContext.TryGetSwitch(switchName, out _originalState)) diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/app.config b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/app.config new file mode 100644 index 00000000000..5646f87340f --- /dev/null +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/app.config @@ -0,0 +1,10 @@ + + + + + + + From d682300bd00de9c91f1aae9554437b2309a87998 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Thu, 12 Mar 2026 16:04:40 -0700 Subject: [PATCH 6/6] More cleanup --- .../TestUtilities/AppContextSwitchScope.cs | 4 +- .../src/Resources/SR.resx | 59 +++++++++++++++++++ .../BinaryFormat/BinaryFormatWriter.cs | 2 +- .../Windows/Nrbf/CoreNrbfSerializer.cs | 2 +- .../Nrbf/SerializationRecordExtensions.cs | 2 +- .../Private/Windows/Ole/DataObjectCore.cs | 2 +- .../System/Private/Windows/Ole/TypeBinder.cs | 6 +- .../Private/Windows/Ole/TypeBinderTests.cs | 2 +- 8 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/Common/tests/TestUtilities/AppContextSwitchScope.cs b/src/Common/tests/TestUtilities/AppContextSwitchScope.cs index 76211adf928..45dff57a733 100644 --- a/src/Common/tests/TestUtilities/AppContextSwitchScope.cs +++ b/src/Common/tests/TestUtilities/AppContextSwitchScope.cs @@ -24,9 +24,7 @@ public AppContextSwitchScope(string switchName, bool enable) public AppContextSwitchScope(string switchName, Func? getDefaultValue, bool enable) { - bool isEnabled; - - if (!AppContext.TryGetSwitch(AppContextSwitchNames.LocalAppContext_DisableCaching, out isEnabled) + if (!AppContext.TryGetSwitch(AppContextSwitchNames.LocalAppContext_DisableCaching, out bool isEnabled) || !isEnabled) { // It doesn't make sense to try messing with AppContext switches if they are going to be cached. diff --git a/src/System.Private.Windows.Core/src/Resources/SR.resx b/src/System.Private.Windows.Core/src/Resources/SR.resx index 72290d47456..a701fd37329 100644 --- a/src/System.Private.Windows.Core/src/Resources/SR.resx +++ b/src/System.Private.Windows.Core/src/Resources/SR.resx @@ -1,5 +1,64 @@  + + diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs index 26946591e67..589f19af7dc 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs @@ -752,7 +752,7 @@ static bool Write(Stream stream, object value) } } -#if NET +#if OLE_JSON public static bool TryWriteJsonData(Stream stream, object value) { if (value is not IJsonData jsonData) diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs index 8b3aa8e4174..55e61bd514c 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs @@ -18,7 +18,7 @@ internal class CoreNrbfSerializer : INrbfSerializer public static bool TryWriteObject(Stream stream, object value) => BinaryFormatWriter.TryWriteFrameworkObject(stream, value) -#if NET +#if OLE_JSON || BinaryFormatWriter.TryWriteJsonData(stream, value) #endif || BinaryFormatWriter.TryWriteDrawingPrimitivesObject(stream, value); diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs index e3fea336b95..88c76d83e45 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs @@ -8,7 +8,7 @@ using System.Reflection; using System.Runtime.Serialization; -#if NET +#if OLE_JSON using System.Private.Windows.BinaryFormat; using System.Reflection.Metadata; using System.Text.Json; diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectCore.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectCore.cs index a360bafef22..52c4570b467 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectCore.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataObjectCore.cs @@ -6,7 +6,7 @@ namespace System.Private.Windows.Ole; internal static class DataObjectCore where TDataObject : IComVisibleDataObject { -#if NET +#if OLE_JSON /// /// JSON serialize the data only if the format is not a restricted deserialization format and the data is not an intrinsic type. /// diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/TypeBinder.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/TypeBinder.cs index 66a48d63af6..66ccfd3b860 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/TypeBinder.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/TypeBinder.cs @@ -29,10 +29,14 @@ namespace System.Private.Windows.Ole; /// /// internal sealed class TypeBinder : SerializationBinder, ITypeResolver +#if NET where TNrbfSerializer : INrbfSerializer +#else + where TNrbfSerializer : INrbfSerializer, new() +#endif { #if NETFRAMEWORK - private static readonly INrbfSerializer s_nrbfSerializer = Activator.CreateInstance(); + private static readonly INrbfSerializer s_nrbfSerializer = new TNrbfSerializer(); #endif private readonly Type _rootType; diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TypeBinderTests.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TypeBinderTests.cs index ec353f80219..8a952456f9e 100644 --- a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TypeBinderTests.cs +++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/TypeBinderTests.cs @@ -156,7 +156,7 @@ public void BindToType_Resolver_CoreSerializer_Null_ReturnsSupportedType() internal sealed class AlwaysDefaultSerializer : INrbfSerializer { - private AlwaysDefaultSerializer() { } + public AlwaysDefaultSerializer() { } #if NET public static