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/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/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/.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..ae2a611c7ad --- /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. +/// +/// +internal 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..a701fd37329 100644 --- a/src/System.Private.Windows.Core/src/Resources/SR.resx +++ b/src/System.Private.Windows.Core/src/Resources/SR.resx @@ -58,7 +58,7 @@ : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> - + 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..17a5498b071 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,8 @@ 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 (!hglobal.IsNull) { HGLOBAL freed = PInvokeCore.GlobalFree(hglobal); @@ -35,7 +37,14 @@ 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. 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/BinaryFormat/BinaryFormatWriter.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs index a1941e58b8f..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,6 +752,7 @@ static bool Write(Stream stream, object value) } } +#if OLE_JSON 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..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,9 @@ internal class CoreNrbfSerializer : INrbfSerializer public static bool TryWriteObject(Stream stream, object value) => BinaryFormatWriter.TryWriteFrameworkObject(stream, value) +#if OLE_JSON || 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..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 @@ -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 OLE_JSON +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..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,8 +10,17 @@ namespace System.Private.Windows.Ole; -internal static 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 = new TNrbfSerializer(); +#endif + /// /// Writes an object to the provided memory stream. /// @@ -28,7 +37,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 +129,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 +142,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 +163,7 @@ record = stream.DecodeNrbf(); } } +#if OLE_JSON if (type is null) { // Serializer didn't recognize the type, look for and deserialize a JSON object. @@ -147,6 +173,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..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 @@ -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; @@ -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) @@ -310,7 +319,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 +421,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..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 @@ -96,7 +96,7 @@ private static bool TryGetDataFromHGLOBAL( [NotNullWhen(true)] out T? data) { data = default; - if (hglobal == 0) + if (hglobal == (nint)0) { return false; } @@ -147,7 +147,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 +210,7 @@ private static unsafe string ReadStringFromHGLOBAL(HGLOBAL hglobal, bool unicode } chars = chars[..nullIndex]; - return new string(chars); + return chars.ToString(); } else { @@ -319,7 +325,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 +482,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 +600,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..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 @@ -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 OLE_JSON +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 OLE_JSON + 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..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 @@ -3,9 +3,10 @@ namespace System.Private.Windows.Ole; -internal static unsafe class DataObjectCore +internal static class DataObjectCore where TDataObject : IComVisibleDataObject { +#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. /// @@ -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..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,9 +20,18 @@ namespace System.Private.Windows.Ole; /// (via ) currently. /// internal static unsafe class DragDropHelper +#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 = new TOleServices(); +#endif + /// /// Sets the drop object image and accompanying text back to the default. /// @@ -204,14 +213,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 +371,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 +450,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..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,8 +29,16 @@ 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 = new TNrbfSerializer(); +#endif + private readonly Type _rootType; private readonly Func? _resolver; private readonly bool _isTypedRequest; @@ -133,8 +141,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..ba46c10bb4e 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,8 +40,7 @@ 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[*] - ) + || type.IsSZArray != typeName.IsSZArray) { return false; } 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..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 @@ -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,35 @@ 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 callable wrapper for the given IUnknown. 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 +322,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..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 @@ -1,7 +1,8 @@  - $(NetCurrent)-windows7.0 + + $(NetCurrent)-windows7.0;net481 true true + $(NoWarn);CA1822 + + + AnyCPU + + - + - + + 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/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 ac6fc600561..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 @@ -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 } } @@ -115,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(); @@ -126,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] @@ -154,6 +178,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 +199,7 @@ public void SerializableObject_InProcess_DoesNotUseBinaryFormatter() data.GetData(typeof(SerializablePerson).FullName!).Should().BeSameAs(person); } +#endif [Serializable] internal class SerializablePerson @@ -263,11 +290,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 +340,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 +450,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..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 @@ -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. @@ -156,44 +156,85 @@ public void BindToType_Resolver_CoreSerializer_Null_ReturnsSupportedType() internal sealed class AlwaysDefaultSerializer : INrbfSerializer { - private AlwaysDefaultSerializer() { } - - public static bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) + public AlwaysDefaultSerializer() { } + +#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 + + bool TryWriteObject(Stream stream, object value) => false; - public static bool IsFullySupportedType(Type type) => 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.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 @@ + + + + + + + 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/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(); + } } 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;