Skip to content

Commit

Permalink
Refactor OLE code to be shareable (#12857)
Browse files Browse the repository at this point in the history
Refactor OLE code to be shareable

To do this, this change introduces platform agnostic static interfaces.

- `INrbfSerializer` (`CoreNrbfSerializer` implements common functionality, `WinFormsNrbfSerializer` composes and adds WinForms specific functionality)
- `IOleServices` (`WinFormsOleServices`, no common class)
- `IRuntime` to encapsulate everything

`DragDropHelper` was made explicit to `DataObject` (via `IComVisibleDataObject`) as we wouldn't really handle arbitrary `IDataObjects`.

Also encapsulates data requests into a `DataRequest` struct to simplify call sites and documentation of arguments.

Namespaces were changed to facilitate this. Files will be moved and renamed in follow-up PRs to maintain history.

Also:

- Add `IBitmap` for abstract bitmap usage
- Add `IDragEvent` and `IGiveFeedbackEvent`to abstract drag event info
- Change `ITypeResolver.GetType` to `BindToType` to match Binder terminology
- Move `TryWriteJsonData` and `TryGetObjectFromJson` to Core
- Move `RestrictedTypeDeserializationException` to Core
- Move more format definitions to DataFormatNames as well as "restricted" helpers
- Add `FullyQualifiedTypeName` struct for caching `TypeName` generation
- Add `IComVisibleDataObject` to represent a type that implements both our managed interface and COM methods
- Move `ConvertToSerializationException` helper to ExceptionExtensions
- Use same NRBF deserialization logic for Resx as OLE
- Use CsWin32 definitions to define enums
  • Loading branch information
JeremyKuhne authored Jan 30, 2025
1 parent bc6d075 commit 3c3acc1
Show file tree
Hide file tree
Showing 84 changed files with 1,416 additions and 1,012 deletions.
5 changes: 4 additions & 1 deletion src/System.Drawing.Common/src/System/Drawing/Bitmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Drawing.Imaging.Effects;
#endif
using System.IO;
using System.Private.Windows.Graphics;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;

Expand All @@ -16,7 +17,7 @@ namespace System.Drawing;
$"System.Drawing.Design.UITypeEditor, {AssemblyRef.SystemDrawing}")]
[Serializable]
[TypeForwardedFrom(AssemblyRef.SystemDrawing)]
public sealed unsafe class Bitmap : Image, IPointer<GpBitmap>
public sealed unsafe class Bitmap : Image, IPointer<GpBitmap>, IBitmap
{
private static readonly Color s_defaultTransparentColor = Color.LightGray;

Expand Down Expand Up @@ -152,6 +153,8 @@ public static Bitmap FromResource(IntPtr hinstance, string bitmapName)
[EditorBrowsable(EditorBrowsableState.Advanced)]
public IntPtr GetHbitmap() => GetHbitmap(Color.LightGray);

HBITMAP IBitmap.GetHbitmap() => (HBITMAP)GetHbitmap();

[EditorBrowsable(EditorBrowsableState.Advanced)]
public IntPtr GetHbitmap(Color background)
{
Expand Down
3 changes: 3 additions & 0 deletions src/System.Private.Windows.Core/src/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ BI_COMPRESSION
BitBlt
BOOL
CallWindowProc
CFSTR_FILENAME
CFSTR_FILENAMEA
CFSTR_DROPDESCRIPTION
CFSTR_INDRAGLOOP
CLIPBOARD_FORMAT
Expand Down Expand Up @@ -140,6 +142,7 @@ IGlobalInterfaceTable
ImageLockMode
INPLACE_E_NOTOOLSPACE
IntersectClipRect
INK_SERIALIZED_FORMAT
IPicture
IPictureDisp
IStream
Expand Down
3 changes: 3 additions & 0 deletions src/System.Private.Windows.Core/src/Resources/SR.resx
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,7 @@
<data name="RegisterCFFailed" xml:space="preserve">
<value>Clipboard format registration did not succeed.</value>
</data>
<data name="ClipboardOrDragDrop_JsonDeserializationFailed" xml:space="preserve">
<value>Failed to deserialize JSON data.</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Runtime.Serialization;

namespace System;

internal static class ExceptionExtensions
Expand All @@ -9,11 +13,29 @@ internal static class ExceptionExtensions
/// Returns <see langword="true"/> if the exception is an exception that isn't recoverable and/or a likely
/// bug in our implementation.
/// </summary>
public static bool IsCriticalException(this Exception ex)
internal static bool IsCriticalException(this Exception ex)
=> ex is NullReferenceException
or StackOverflowException
or OutOfMemoryException
or ThreadAbortException
or IndexOutOfRangeException
or AccessViolationException;

/// <summary>
/// Converts the given exception to a <see cref="SerializationException"/> if needed, nesting the original exception
/// and assigning the original stack trace.
/// </summary>
internal static SerializationException ConvertToSerializationException(this Exception ex)
{
if (ex is TargetInvocationException targetException)
{
ex = targetException.InnerException ?? ex;
}

return ex is SerializationException serializationException
? serializationException
: (SerializationException)ExceptionDispatchInfo.SetRemoteStackTrace(
new SerializationException(ex.Message, ex),
ex.StackTrace ?? string.Empty);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,27 @@ static bool Write(Stream stream, object value)
}
}

public static bool TryWriteJsonData(Stream stream, object value)
{
if (value is not IJsonData jsonData)
{
return false;
}

using BinaryFormatWriterScope writer = new(stream);
new BinaryLibrary(libraryId: 2, IJsonData.CustomAssemblyName).Write(writer);
new ClassWithMembersAndTypes(
new ClassInfo(1, $"{typeof(IJsonData).Namespace}.JsonData", [$"<{nameof(jsonData.JsonBytes)}>k__BackingField", $"<{nameof(jsonData.InnerTypeAssemblyQualifiedName)}>k__BackingField"]),
libraryId: 2,
new MemberTypeInfo[] { new(BinaryType.PrimitiveArray, PrimitiveType.Byte), new(BinaryType.String, null) },
new MemberReference(idRef: 3),
new BinaryObjectString(objectId: 4, jsonData.InnerTypeAssemblyQualifiedName)).Write(writer);

new ArraySinglePrimitive<byte>(objectId: 3, jsonData.JsonBytes).Write(writer);

return true;
}

/// <summary>
/// Simple wrapper to ensure the <paramref name="stream"/> is reset to its original position if the
/// <paramref name="action"/> throws.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserial
Debug.Assert(arrayRecord.RecordType is not (SerializationRecordType.ArraySingleString or SerializationRecordType.ArraySinglePrimitive));

_arrayRecord = arrayRecord;
_elementType = deserializer.TypeResolver.GetType(arrayRecord.TypeName.GetElementType());
_elementType = deserializer.TypeResolver.BindToType(arrayRecord.TypeName.GetElementType());
Type expectedArrayType = arrayRecord.Rank switch
{
1 => _elementType.MakeArrayType(),
Expand Down Expand Up @@ -120,7 +120,7 @@ internal override SerializationRecordId Continue()
[RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")]
internal static Array? GetSimpleBinaryArray(ArrayRecord arrayRecord, ITypeResolver typeResolver)
{
Type arrayRecordElementType = typeResolver.GetType(arrayRecord.TypeName.GetElementType());
Type arrayRecordElementType = typeResolver.BindToType(arrayRecord.TypeName.GetElementType());
Type elementType = arrayRecordElementType;
while (elementType.IsArray)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private protected ClassRecordDeserializer(ClassRecord classRecord, object @objec
[RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")]
internal static ObjectRecordDeserializer Create(ClassRecord classRecord, IDeserializer deserializer)
{
Type type = deserializer.TypeResolver.GetType(classRecord.TypeName);
Type type = deserializer.TypeResolver.BindToType(classRecord.TypeName);
SerializationRecordId id = classRecord.Id;

ISerializationSurrogate? surrogate = deserializer.GetSurrogate(type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ internal DefaultTypeResolver(DeserializationOptions options)
/// </summary>
[RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String)")]
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
Type ITypeResolver.GetType(TypeName typeName)
Type ITypeResolver.BindToType(TypeName typeName)
{
Debug.Assert(typeName.AssemblyName is not null);

Expand All @@ -35,7 +35,7 @@ Type ITypeResolver.GetType(TypeName typeName)
return cachedType;
}

if (_binder?.GetType(typeName) is Type binderType)
if (_binder?.BindToType(typeName) is Type binderType)
{
// BinaryFormatter is inconsistent about what caching behavior you get with binders.
// It would always cache the last item from the binder, but wouldn't put the result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ void IDeserializer.CompleteObject(SerializationRecordId id)
// There are no remaining dependencies. Hook any finished events for this object.
// Doing at the end of deserialization for simplicity.

Type type = _typeResolver.GetType(classRecord.TypeName);
Type type = _typeResolver.BindToType(classRecord.TypeName);
object @object = _deserializedObjects[completedId];

OnDeserialized += SerializationEvents.GetOnDeserializedForType(type, @object);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ internal interface ITypeResolver
/// </summary>
[RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String)")]
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
Type GetType(TypeName typeName);
Type BindToType(TypeName typeName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Drawing;

namespace System.Private.Windows.Graphics;

/// <summary>
/// Platform agnostic interface for a bitmap.
/// </summary>
internal interface IBitmap
{
/// <summary>
/// Gets a Win32 HBITMAP handle for this bitmap.
/// </summary>
HBITMAP GetHbitmap();

/// <summary>
/// Size of the bitmap in pixels.
/// </summary>
Size Size { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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.Nrbf;
using System.Private.Windows.Ole;

namespace System.Private.Windows;

/// <summary>
/// Unifying interface for runtime specific services and types.
/// </summary>
/// <typeparam name="TDataFormat">The platform specific DataFormat struct type.</typeparam>
internal interface IRuntime<TDataFormat>
: INrbfSerializer, IOleServices
where TDataFormat : IDataFormat<TDataFormat>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Drawing;
using System.Formats.Nrbf;
using System.Private.Windows.BinaryFormat;
using System.Reflection.Metadata;

namespace System.Private.Windows.Nrbf;

/// <summary>
/// Core NRBF serializer. Supports common .NET types.
/// </summary>
internal class CoreNrbfSerializer : INrbfSerializer
{
private static Dictionary<TypeName, Type>? s_knownTypes;

// These types are read from and written to serialized stream manually, accessing record field by field.
// Thus they are re-hydrated with no formatters and are safe. The default resolver should recognize them
// to resolve primitive types or fields of the specified type T.
private static readonly Type[] s_intrinsicTypes =
[
// Primitive types.
typeof(byte),
typeof(sbyte),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(double),
typeof(float),
typeof(char),
typeof(bool),
typeof(string),
typeof(decimal),
typeof(DateTime),
typeof(TimeSpan),
typeof(IntPtr),
typeof(UIntPtr),
// Special type we use to report that binary formatting is disabled.
typeof(NotSupportedException),
// Lists of primitive types
typeof(List<byte>),
typeof(List<sbyte>),
typeof(List<short>),
typeof(List<ushort>),
typeof(List<int>),
typeof(List<uint>),
typeof(List<long>),
typeof(List<ulong>),
typeof(List<float>),
typeof(List<double>),
typeof(List<char>),
typeof(List<bool>),
typeof(List<string>),
typeof(List<decimal>),
typeof(List<DateTime>),
typeof(List<TimeSpan>),
// Arrays of primitive types.
typeof(byte[]),
typeof(sbyte[]),
typeof(short[]),
typeof(ushort[]),
typeof(int[]),
typeof(uint[]),
typeof(long[]),
typeof(ulong[]),
typeof(float[]),
typeof(double[]),
typeof(char[]),
typeof(bool[]),
typeof(string[]),
typeof(decimal[]),
typeof(DateTime[]),
typeof(TimeSpan[]),

// Exchange types, they are serialized with the .NET Framework assembly name.
// In .NET they are located in System.Drawing.Primitives.
typeof(RectangleF),
typeof(PointF),
typeof(SizeF),
typeof(Rectangle),
typeof(Point),
typeof(Size),
typeof(Color)
];

public static bool TryWriteObject(Stream stream, object value) =>
BinaryFormatWriter.TryWriteFrameworkObject(stream, value)
|| BinaryFormatWriter.TryWriteJsonData(stream, value)
|| BinaryFormatWriter.TryWriteDrawingPrimitivesObject(stream, value);

public static bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) =>
record.TryGetFrameworkObject(out value)
// While these shouldn't normally be in a ResX file, it doesn't hurt to read them and simplifies the code.
|| record.TryGetDrawingPrimitivesObject(out value);

public static bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type)
{
if (s_knownTypes is null)
{
s_knownTypes = new(s_intrinsicTypes.Length, TypeNameComparer.Default);
foreach (Type intrinsic in s_intrinsicTypes)
{
s_knownTypes.Add(intrinsic.ToTypeName(), intrinsic);
}
}

return s_knownTypes.TryGetValue(typeName, out type);
}

public static bool IsSupportedType<T>() =>
typeof(T) == typeof(byte)
|| typeof(T) == typeof(sbyte)
|| typeof(T) == typeof(short)
|| typeof(T) == typeof(ushort)
|| typeof(T) == typeof(int)
|| typeof(T) == typeof(uint)
|| typeof(T) == typeof(long)
|| typeof(T) == typeof(ulong)
|| typeof(T) == typeof(double)
|| typeof(T) == typeof(float)
|| typeof(T) == typeof(char)
|| typeof(T) == typeof(bool)
|| typeof(T) == typeof(string)
|| typeof(T) == typeof(decimal)
|| typeof(T) == typeof(DateTime)
|| typeof(T) == typeof(TimeSpan)
|| typeof(T) == typeof(IntPtr)
|| typeof(T) == typeof(UIntPtr)
|| typeof(T) == typeof(NotSupportedException)
|| typeof(T) == typeof(List<byte>)
|| typeof(T) == typeof(List<sbyte>)
|| typeof(T) == typeof(List<short>)
|| typeof(T) == typeof(List<ushort>)
|| typeof(T) == typeof(List<int>)
|| typeof(T) == typeof(List<uint>)
|| typeof(T) == typeof(List<long>)
|| typeof(T) == typeof(List<ulong>)
|| typeof(T) == typeof(List<float>)
|| typeof(T) == typeof(List<double>)
|| typeof(T) == typeof(List<char>)
|| typeof(T) == typeof(List<bool>)
|| typeof(T) == typeof(List<string>)
|| typeof(T) == typeof(List<decimal>)
|| typeof(T) == typeof(List<DateTime>)
|| typeof(T) == typeof(List<TimeSpan>)
|| typeof(T) == typeof(byte[])
|| typeof(T) == typeof(sbyte[])
|| typeof(T) == typeof(short[])
|| typeof(T) == typeof(ushort[])
|| typeof(T) == typeof(int[])
|| typeof(T) == typeof(uint[])
|| typeof(T) == typeof(long[])
|| typeof(T) == typeof(ulong[])
|| typeof(T) == typeof(float[])
|| typeof(T) == typeof(double[])
|| typeof(T) == typeof(char[])
|| typeof(T) == typeof(bool[])
|| typeof(T) == typeof(string[])
|| typeof(T) == typeof(decimal[])
|| typeof(T) == typeof(DateTime[])
|| typeof(T) == typeof(TimeSpan[])
|| typeof(T) == typeof(RectangleF)
|| typeof(T) == typeof(PointF)
|| typeof(T) == typeof(SizeF)
|| typeof(T) == typeof(Rectangle)
|| typeof(T) == typeof(Point)
|| typeof(T) == typeof(Size)
|| typeof(T) == typeof(Color);
}
Loading

0 comments on commit 3c3acc1

Please sign in to comment.