Skip to content
40 changes: 40 additions & 0 deletions src/WinRT.Runtime2/InteropServices/ComObjectHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Runtime.CompilerServices;

namespace WindowsRuntime.InteropServices;

Expand Down Expand Up @@ -82,4 +83,43 @@ public static HRESULT IsFreeThreadedUnsafe(void* thisPtr)

return WellKnownErrorCodes.S_FALSE;
}

/// <summary>
/// Validates the provided marshaling type for a given object.
/// </summary>
/// <param name="thisPtr">The target COM object.</param>
/// <param name="marshalingType">The <see cref="CreateObjectReferenceMarshalingType"/> value available in metadata for the type being marshalled.</param>
/// <remarks>
/// This method will fail-fast if the validation fails.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateMarshalingType(void* thisPtr, CreateObjectReferenceMarshalingType marshalingType)
{
if (!WindowsRuntimeFeatureSwitches.EnableMarshalingTypeValidation)
{
return;
}

// Move the logic into a non-inlineable method to help the containing one get inlined correctly
[MethodImpl(MethodImplOptions.NoInlining)]
static void PerformValidation(void* thisPtr, CreateObjectReferenceMarshalingType marshalingType)
{
// If no static type information is available, we can avoid the validation overhead
if (marshalingType == CreateObjectReferenceMarshalingType.Unknown)
{
return;
}

HRESULT hresult = IsFreeThreadedUnsafe(thisPtr);

// If the object declared a marshalling type that doesn't match what we can compute at runtime, fail immediately
if ((marshalingType == CreateObjectReferenceMarshalingType.Agile && hresult == WellKnownErrorCodes.S_FALSE) ||
(marshalingType == CreateObjectReferenceMarshalingType.Standard && hresult == WellKnownErrorCodes.S_OK))
{
Environment.FailFast($"The input object ('{(nint)thisPtr:X16}') declared the incorrect marshalling type '{marshalingType}'.");
}
}

PerformValidation(thisPtr, marshalingType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.ComponentModel;

namespace WindowsRuntime.InteropServices;

/// <summary>
/// Specifies the marshaling type to use to marshal a given Windows Runtime object, specifically when creating a <see cref="WindowsRuntimeObjectReference"/> instance.
/// </summary>
/// <seealso href="https://learn.microsoft.com/uwp/api/windows.foundation.metadata.marshalingtype"/>
[Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage,
DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId,
UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)]
[EditorBrowsable(EditorBrowsableState.Never)]
public enum CreateObjectReferenceMarshalingType
{
/// <summary>
/// No static type information is available to know in advance the marshaling type for the class.
/// </summary>
Unknown,

/// <summary>
/// The class marshals and unmarshals to the same pointer value on all interfaces.
/// </summary>
Agile,

/// <summary>
/// The class does not implement <see href="https://learn.microsoft.com/windows/win32/api/objidl/nn-objidl-imarshal"><c>IMarshal</c></see> or forwards to
/// <see href="https://learn.microsoft.com/windows/win32/api/combaseapi/nf-combaseapi-cogetstandardmarshal"><c>CoGetStandardMarshal</c></see> on all interfaces.
/// </summary>
Standard
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using WindowsRuntime.InteropServices.Marshalling;

#pragma warning disable CS1573

namespace WindowsRuntime.InteropServices;

/// <summary>
Expand Down Expand Up @@ -37,7 +40,29 @@ public static unsafe class WindowsRuntimeComWrappersMarshal
/// </remarks>
public static WindowsRuntimeObjectReference CreateObjectReference(void* externalComObject, in Guid iid, out CreatedWrapperFlags wrapperFlags)
{
WindowsRuntimeObjectReference objectReference = WindowsRuntimeObjectReference.InitializeObjectReference(externalComObject, in iid);
WindowsRuntimeObjectReference objectReference = WindowsRuntimeObjectReference.InitializeObjectReference(
externalComObject: externalComObject,
iid: in iid,
marshalingType: CreateObjectReferenceMarshalingType.Unknown);

wrapperFlags = objectReference.GetReferenceTrackerPtrUnsafe() is null ? CreatedWrapperFlags.None : CreatedWrapperFlags.TrackerObject;

return objectReference;
}

/// <inheritdoc cref="CreateObjectReference(void*, in Guid, out CreatedWrapperFlags)"/>
/// <param name="marshalingType">The <see cref="CreateObjectReferenceMarshalingType"/> value available in metadata for the type being marshalled.</param>
[SupportedOSPlatform("Windows10.0.10240.0")]
public static WindowsRuntimeObjectReference CreateObjectReference(
void* externalComObject,
in Guid iid,
CreateObjectReferenceMarshalingType marshalingType,
out CreatedWrapperFlags wrapperFlags)
{
WindowsRuntimeObjectReference objectReference = WindowsRuntimeObjectReference.InitializeObjectReference(
externalComObject: externalComObject,
iid: in iid,
marshalingType: marshalingType);

wrapperFlags = objectReference.GetReferenceTrackerPtrUnsafe() is null ? CreatedWrapperFlags.None : CreatedWrapperFlags.TrackerObject;

Expand All @@ -58,16 +83,38 @@ public static WindowsRuntimeObjectReference CreateObjectReference(void* external
/// be used when dealing with Windows Runtime types instantiated from C# (which includes COM aggregation scenarios too).
/// </para>
/// <para>
/// Unlike <see cref="CreateObjectReference"/>, this method assumes <paramref name="externalComObject"/> is exactly the
/// right interface pointer for <paramref name="iid"/>, and will therefore skip doing a <c>QueryInterface</c> call on it.
/// Unlike <see cref="CreateObjectReferenceValue(void*, in Guid)"/>, this method assumes <paramref name="externalComObject"/> is exactly
/// the right interface pointer for <paramref name="iid"/>, and will therefore skip doing a <c>QueryInterface</c> call on it.
/// </para>
/// <para>
/// This method should only be used to create <see cref="WindowsRuntimeObjectReference"/> in projection scenarios.
/// </para>
/// </remarks>
public static WindowsRuntimeObjectReference CreateObjectReferenceUnsafe(void* externalComObject, in Guid iid, out CreatedWrapperFlags wrapperFlags)
{
WindowsRuntimeObjectReference objectReference = WindowsRuntimeObjectReference.InitializeObjectReferenceUnsafe(externalComObject, in iid);
WindowsRuntimeObjectReference objectReference = WindowsRuntimeObjectReference.InitializeObjectReferenceUnsafe(
externalComObject: externalComObject,
iid: in iid,
marshalingType: CreateObjectReferenceMarshalingType.Unknown);

wrapperFlags = objectReference.GetReferenceTrackerPtrUnsafe() is null ? CreatedWrapperFlags.None : CreatedWrapperFlags.TrackerObject;

return objectReference;
}

/// <inheritdoc cref="CreateObjectReferenceUnsafe(void*, in Guid, out CreatedWrapperFlags)"/>
/// <param name="marshalingType">The <see cref="CreateObjectReferenceMarshalingType"/> value available in metadata for the type being marshalled.</param>
[SupportedOSPlatform("Windows10.0.10240.0")]
public static WindowsRuntimeObjectReference CreateObjectReferenceUnsafe(
void* externalComObject,
in Guid iid,
CreateObjectReferenceMarshalingType marshalingType,
out CreatedWrapperFlags wrapperFlags)
{
WindowsRuntimeObjectReference objectReference = WindowsRuntimeObjectReference.InitializeObjectReferenceUnsafe(
externalComObject: externalComObject,
iid: in iid,
marshalingType: marshalingType);

wrapperFlags = objectReference.GetReferenceTrackerPtrUnsafe() is null ? CreatedWrapperFlags.None : CreatedWrapperFlags.TrackerObject;

Expand Down
11 changes: 11 additions & 0 deletions src/WinRT.Runtime2/Properties/WindowsRuntimeFeatureSwitches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ internal static class WindowsRuntimeFeatureSwitches
/// </summary>
private const string EnableIDynamicInterfaceCastableSupportPropertyName = "CSWINRT_ENABLE_IDYNAMICINTERFACECASTABLE_SUPPORT";

/// <summary>
/// The configuration property name for <see cref="EnableMarshalingTypeValidation"/>.
/// </summary>
private const string EnableMarshalingTypeValidationPropertyName = "CSWINRT_ENABLE_MARSHALING_TYPE_VALIDATION";

/// <summary>
/// Gets a value indicating whether or not manifest free WinRT activation is supported (defaults to <see langword="true"/>).
/// </summary>
Expand Down Expand Up @@ -73,6 +78,12 @@ internal static class WindowsRuntimeFeatureSwitches
[FeatureSwitchDefinition(EnableIDynamicInterfaceCastableSupportPropertyName)]
public static bool EnableIDynamicInterfaceCastableSupport { get; } = GetConfigurationValue(EnableIDynamicInterfaceCastableSupportPropertyName, defaultValue: true);

/// <summary>
/// Gets a value indicating whether or not validation for <see cref="InteropServices.CreateObjectReferenceMarshalingType"/> values should be enabled (defaults to <see langword="false"/>).
/// </summary>
[FeatureSwitchDefinition(EnableMarshalingTypeValidationPropertyName)]
public static bool EnableMarshalingTypeValidation { get; } = GetConfigurationValue(EnableMarshalingTypeValidationPropertyName, defaultValue: false);

/// <summary>
/// Gets a configuration value for a specified property.
/// </summary>
Expand Down
32 changes: 22 additions & 10 deletions src/WinRT.Runtime2/WindowsRuntimeObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ protected WindowsRuntimeObject(WindowsRuntimeObjectReference nativeObjectReferen
/// <param name="_">Marker parameter used to select this constructor for sealed types (unused).</param>
/// <param name="activationFactoryObjectReference">The <see cref="WindowsRuntimeObjectReference"/> for the <c>IActivationFactory</c> instance.</param>
/// <param name="iid">The IID of the default interface for the Windows Runtime class being constructed.</param>
/// <param name="marshalingType">The <see cref="CreateObjectReferenceMarshalingType"/> value available in metadata for the type being marshalled.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="activationFactoryObjectReference"/> is <see langword="null"/>.</exception>
/// <exception cref="ObjectDisposedException">Thrown if <paramref name="activationFactoryObjectReference"/> has been disposed.</exception>
/// <exception cref="Exception">Thrown if there's any errors when activating the underlying native object.</exception>
Expand All @@ -80,7 +81,8 @@ protected WindowsRuntimeObject(WindowsRuntimeObjectReference nativeObjectReferen
protected WindowsRuntimeObject(
WindowsRuntimeActivationTypes.DerivedSealed _,
WindowsRuntimeObjectReference activationFactoryObjectReference,
in Guid iid)
in Guid iid,
CreateObjectReferenceMarshalingType marshalingType)
{
ArgumentNullException.ThrowIfNull(activationFactoryObjectReference);

Expand All @@ -101,7 +103,8 @@ protected WindowsRuntimeObject(
thisInstance: this,
newInstanceUnknown: ref defaultInterface,
innerInstanceUnknown: ref innerInterface,
newInstanceIid: in iid);
newInstanceIid: in iid,
marshalingType: marshalingType);
}

/// <summary>
Expand All @@ -110,6 +113,7 @@ protected WindowsRuntimeObject(
/// <param name="_">Marker parameter used to select this constructor for composed types (unused).</param>
/// <param name="activationFactoryObjectReference">The <see cref="WindowsRuntimeObjectReference"/> for the <c>IActivationFactory</c> instance.</param>
/// <param name="iid">The IID of the default interface for the Windows Runtime class being constructed.</param>
/// <param name="marshalingType">The <see cref="CreateObjectReferenceMarshalingType"/> value available in metadata for the type being marshalled.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="activationFactoryObjectReference"/> is <see langword="null"/>.</exception>
/// <exception cref="ObjectDisposedException">Thrown if <paramref name="activationFactoryObjectReference"/> has been disposed.</exception>
/// <exception cref="Exception">Thrown if there's any errors when activating the underlying native object.</exception>
Expand All @@ -123,7 +127,8 @@ protected WindowsRuntimeObject(
protected WindowsRuntimeObject(
WindowsRuntimeActivationTypes.DerivedComposed _,
WindowsRuntimeObjectReference activationFactoryObjectReference,
in Guid iid)
in Guid iid,
CreateObjectReferenceMarshalingType marshalingType)
{
ArgumentNullException.ThrowIfNull(activationFactoryObjectReference);

Expand Down Expand Up @@ -157,7 +162,8 @@ protected WindowsRuntimeObject(
thisInstance: this,
newInstanceUnknown: ref defaultInterface,
innerInstanceUnknown: ref innerInterface,
newInstanceIid: in iid);
newInstanceIid: in iid,
marshalingType: marshalingType);

// Optimization: if we are activating the current type for composition, then the returned object reference
// will wrap the 'IInspectable' pointer for the controlling instance (ie. 'innerInterface'). In this case,
Expand All @@ -183,6 +189,7 @@ protected WindowsRuntimeObject(
/// </summary>
/// <param name="activationFactoryCallback">The <see cref="WindowsRuntimeActivationFactoryCallback"/> instance to delegate activation to.</param>
/// <param name="iid">The IID of the default interface for the Windows Runtime class being constructed.</param>
/// <param name="marshalingType">The <see cref="CreateObjectReferenceMarshalingType"/> value available in metadata for the type being marshalled.</param>
/// <param name="additionalParameters">The additional parameters to provide to <paramref name="activationFactoryCallback"/>.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="activationFactoryCallback"/> is <see langword="null"/>.</exception>
/// <exception cref="Exception">Thrown if there's any errors when activating the underlying native object.</exception>
Expand All @@ -192,8 +199,8 @@ protected WindowsRuntimeObject(
/// </para>
/// <para>
/// Additionally, this constructor is only meant to be used when additional custom parameters are required to invoke the target factory method. If no additional
/// parameters are needed, the <see cref="WindowsRuntimeObject(WindowsRuntimeActivationTypes.DerivedSealed, WindowsRuntimeObjectReference, in Guid)"/> overload
/// should be used instead, as that is more efficient in case the default signature is sufficient.
/// parameters are needed, the <see cref="WindowsRuntimeObject(WindowsRuntimeActivationTypes.DerivedSealed, WindowsRuntimeObjectReference, in Guid, CreateObjectReferenceMarshalingType)"/>
/// overload should be used instead, as that is more efficient in case the default signature is sufficient.
/// </para>
/// </remarks>
[Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage,
Expand All @@ -203,6 +210,7 @@ protected WindowsRuntimeObject(
protected WindowsRuntimeObject(
WindowsRuntimeActivationFactoryCallback.DerivedSealed activationFactoryCallback,
in Guid iid,
CreateObjectReferenceMarshalingType marshalingType,
params ReadOnlySpan<object?> additionalParameters)
{
ArgumentNullException.ThrowIfNull(activationFactoryCallback);
Expand All @@ -221,14 +229,16 @@ protected WindowsRuntimeObject(
thisInstance: this,
newInstanceUnknown: ref defaultInterface,
innerInstanceUnknown: ref innerInterface,
newInstanceIid: in iid);
newInstanceIid: in iid,
marshalingType: marshalingType);
}

/// <summary>
/// Creates a <see cref="WindowsRuntimeObject"/> instance with the specified parameters for composed scenarios.
/// </summary>
/// <param name="activationFactoryCallback">The <see cref="WindowsRuntimeActivationFactoryCallback"/> instance to delegate activation to.</param>
/// <param name="iid">The IID of the default interface for the Windows Runtime class being constructed.</param>
/// <param name="marshalingType">The <see cref="CreateObjectReferenceMarshalingType"/> value available in metadata for the type being marshalled.</param>
/// <param name="additionalParameters">The additional parameters to provide to <paramref name="activationFactoryCallback"/>.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="activationFactoryCallback"/> is <see langword="null"/>.</exception>
/// <exception cref="Exception">Thrown if there's any errors when activating the underlying native object.</exception>
Expand All @@ -238,8 +248,8 @@ protected WindowsRuntimeObject(
/// </para>
/// <para>
/// Additionally, this constructor is only meant to be used when additional custom parameters are required to invoke the target factory method. If no additional
/// parameters are needed, the <see cref="WindowsRuntimeObject(WindowsRuntimeActivationTypes.DerivedComposed, WindowsRuntimeObjectReference, in Guid)"/> overload
/// should be used instead, as that is more efficient in case the default signature is sufficient.
/// parameters are needed, the <see cref="WindowsRuntimeObject(WindowsRuntimeActivationTypes.DerivedComposed, WindowsRuntimeObjectReference, in Guid, CreateObjectReferenceMarshalingType)"/>
/// overload should be used instead, as that is more efficient in case the default signature is sufficient.
/// </para>
/// </remarks>
[Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage,
Expand All @@ -249,6 +259,7 @@ protected WindowsRuntimeObject(
protected WindowsRuntimeObject(
WindowsRuntimeActivationFactoryCallback.DerivedComposed activationFactoryCallback,
in Guid iid,
CreateObjectReferenceMarshalingType marshalingType,
params ReadOnlySpan<object?> additionalParameters)
{
ArgumentNullException.ThrowIfNull(activationFactoryCallback);
Expand All @@ -268,7 +279,8 @@ protected WindowsRuntimeObject(
thisInstance: this,
newInstanceUnknown: ref defaultInterface,
innerInstanceUnknown: ref innerInterface,
newInstanceIid: in iid);
newInstanceIid: in iid,
marshalingType: marshalingType);

// Optimization: pre-cache the inspectable object reference if possible (see detailed explanation above)
if (!hasUnwrappableNativeObjectReference)
Expand Down
Loading
Loading