Skip to content

Implement new ComWrappers.CreateObject API #115437

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4358,4 +4358,7 @@
<data name="ComVariant_VariantWrapper_In_Variant" xml:space="preserve">
<value>VariantWrappers cannot be stored in Variants.</value>
</data>
<data name="NotImplemented_CreateObjectWithUserState" xml:space="preserve">
<value>Creating an object wrapper for a COM instance with user state is not implemented.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ComVisibleAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CreateObjectFlags.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CreateComInterfaceFlags.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CreatedWrapperFlags.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CriticalHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CULong.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CurrencyWrapper.cs" />
Expand Down Expand Up @@ -2774,7 +2775,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolCallbackWrapper.cs" Condition="'$(FeatureNativeAot)' != 'true'" />
</ItemGroup>
<ItemGroup Condition="('$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true') and '$(FeatureWasmManagedThreads)' != 'true'">
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.Browser.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.Browser.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.Browser.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsBrowser)' == 'true' and '$(FeatureWasmManagedThreads)' != 'true'">
Expand Down Expand Up @@ -2849,4 +2850,4 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPollWorld.wit.imports.wasi.io.v0_2_0.IPoll.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPollWorld.wit.imports.wasi.io.v0_2_0.PollInterop.cs" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public struct ComInterfaceEntry

protected abstract object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags);

protected virtual object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags, object? userState, out CreatedWrapperFlags wrapperFlags)
{
throw new PlatformNotSupportedException();
}

protected internal abstract void ReleaseObjects(IEnumerable objects);

public static unsafe bool TryGetComInstance(object obj, out IntPtr unknown)
Expand Down Expand Up @@ -58,6 +63,11 @@ public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateOb
throw new PlatformNotSupportedException();
}

public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object? userState)
{
throw new PlatformNotSupportedException();
}

public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper)
{
throw new PlatformNotSupportedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,28 @@ private static nuint AlignUp(nuint value, nuint alignment)
public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags)
{
object? obj;
if (!TryGetOrCreateObjectForComInstanceInternal(externalComObject, IntPtr.Zero, flags, null, out obj))
if (!TryGetOrCreateObjectForComInstanceInternal(externalComObject, IntPtr.Zero, flags, wrapperMaybe: null, userState: NoUserState.Instance, out obj))
throw new ArgumentNullException(nameof(externalComObject));

return obj;
}

/// <summary>
/// Get the currently registered managed object or creates a new managed object and registers it.
/// </summary>
/// <param name="externalComObject">Object to import for usage into the .NET runtime.</param>
/// <param name="flags">Flags used to describe the external object.</param>
/// <param name="userState">A state object to use to help create the wrapping .NET object.</param>
/// <returns>Returns a managed object associated with the supplied external COM object.</returns>
/// <remarks>
/// If a managed object was previously created for the specified <paramref name="externalComObject" />
/// using this <see cref="ComWrappers" /> instance, the previously created object will be returned.
/// If not, a new one will be created.
/// </remarks>
public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object? userState)
{
object? obj;
if (!TryGetOrCreateObjectForComInstanceInternal(externalComObject, IntPtr.Zero, flags, wrapperMaybe: null, userState, out obj))
throw new ArgumentNullException(nameof(externalComObject));

return obj;
Expand Down Expand Up @@ -943,7 +964,7 @@ public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, Create
ArgumentNullException.ThrowIfNull(wrapper);

object? obj;
if (!TryGetOrCreateObjectForComInstanceInternal(externalComObject, inner, flags, wrapper, out obj))
if (!TryGetOrCreateObjectForComInstanceInternal(externalComObject, inner, flags, wrapper, userState: NoUserState.Instance, out obj))
throw new ArgumentNullException(nameof(externalComObject));

return obj;
Expand Down Expand Up @@ -1025,20 +1046,31 @@ private static void DetermineIdentityAndInner(
}
}

private sealed class NoUserState
{
public static readonly NoUserState Instance = new NoUserState();

private NoUserState()
{
}
}

/// <summary>
/// Get the currently registered managed object or creates a new managed object and registers it.
/// </summary>
/// <param name="externalComObject">Object to import for usage into the .NET runtime.</param>
/// <param name="innerMaybe">The inner instance if aggregation is involved</param>
/// <param name="flags">Flags used to describe the external object.</param>
/// <param name="wrapperMaybe">The <see cref="object"/> to be used as the wrapper for the external object.</param>
/// <param name="retValue">The managed object associated with the supplied external COM object or <c>null</c> if it could not be created.</param>
/// <param name="userState">A state object provided by the user for creating the object, otherwise <see cref="NoUserState.Instance" />.</param>
/// <returns>Returns <c>true</c> if a managed object could be retrieved/created, <c>false</c> otherwise</returns>
/// <param name="retValue">The managed object associated with the supplied external COM object or <c>null</c> if it could not be created.</param>
private unsafe bool TryGetOrCreateObjectForComInstanceInternal(
IntPtr externalComObject,
IntPtr innerMaybe,
CreateObjectFlags flags,
object? wrapperMaybe,
object? userState,
[NotNullWhen(true)] out object? retValue)
{
if (externalComObject == IntPtr.Zero)
Expand All @@ -1062,7 +1094,7 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal(
// and return.
if (flags.HasFlag(CreateObjectFlags.UniqueInstance))
{
retValue = CreateAndRegisterObjectForComInstance(identity, inner, flags, ref referenceTrackerMaybe);
retValue = CreateAndRegisterObjectForComInstance(identity, inner, flags, userState, ref referenceTrackerMaybe);
return retValue is not null;
}

Expand Down Expand Up @@ -1116,7 +1148,7 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal(

// If the user didn't provide a wrapper and couldn't unwrap a managed object wrapper,
// create a new wrapper.
retValue = CreateAndRegisterObjectForComInstance(identity, inner, flags, ref referenceTrackerMaybe);
retValue = CreateAndRegisterObjectForComInstance(identity, inner, flags, userState, ref referenceTrackerMaybe);
return retValue is not null;
}
finally
Expand All @@ -1137,15 +1169,32 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal(
IntPtr identity,
IntPtr inner,
CreateObjectFlags flags,
object? userState,
ref IntPtr referenceTrackerMaybe)
{
object? retValue = CreateObject(identity, flags);
CreatedWrapperFlags wrapperFlags = CreatedWrapperFlags.None;

object? retValue = userState is NoUserState
? CreateObject(identity, flags)
: CreateObject(identity, flags, userState, out wrapperFlags);

if (retValue is null)
{
// If ComWrappers instance cannot create wrapper, we can do nothing here.
return null;
}

if (wrapperFlags.HasFlag(CreatedWrapperFlags.NonWrapping))
{
return retValue;
}

if (wrapperFlags.HasFlag(CreatedWrapperFlags.TrackerObject))
{
// The user has determined after inspecting the COM object that it should have tracker support.
flags |= CreateObjectFlags.TrackerObject;
}

return RegisterObjectForComInstance(identity, inner, retValue, flags, ref referenceTrackerMaybe);
}

Expand Down Expand Up @@ -1391,6 +1440,23 @@ public static void RegisterForMarshalling(ComWrappers instance)
/// </remarks>
protected abstract object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags);

/// <summary>
/// Create a managed object for the object pointed at by <paramref name="externalComObject"/> respecting the values of <paramref name="flags"/>.
/// </summary>
/// <param name="externalComObject">Object to import for usage into the .NET runtime.</param>
/// <param name="flags">Flags used to describe the external object.</param>
/// <param name="userState">User state provided by the call to <see cref="GetOrCreateObjectForComInstance(nint, CreateObjectFlags, object)" />.</param>
/// <param name="wrapperFlags">Flags used to describe the created wrapper object.</param>
/// <returns>Returns a managed object associated with the supplied external COM object.</returns>
/// <remarks>
/// The default implementation throws <see cref="NotImplementedException"/>.
/// If the object cannot be created and <code>null</code> is returned, the call to <see cref="GetOrCreateObjectForComInstance(nint, CreateObjectFlags, object)"/> will throw a <see cref="ArgumentNullException"/>.
/// </remarks>
protected virtual object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags, object? userState, out CreatedWrapperFlags wrapperFlags)
{
throw new NotImplementedException(SR.NotImplemented_CreateObjectWithUserState);
}

/// <summary>
/// Called when a request is made for a collection of objects to be released outside of normal object or COM interface lifetime.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

namespace System.Runtime.InteropServices
{

/// <summary>
/// Enumeration of flags for <see cref="ComWrappers.GetOrCreateObjectForComInstance(IntPtr, CreateObjectFlags)"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.InteropServices
{
/// <summary>
/// Enumeration of flags for <see cref="ComWrappers.CreateObject(IntPtr, CreateObjectFlags, object?, out CreatedWrapperFlags)"/>.
/// </summary>
[Flags]
public enum CreatedWrapperFlags
{
None = 0,

/// <summary>
/// Indicate if the supplied external COM object implements the <see href="https://learn.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.referencetracker/nn-windows-ui-xaml-hosting-referencetracker-ireferencetracker">IReferenceTracker</see>.
/// </summary>
TrackerObject = 1,

/// <summary>
/// The managed object doesn't keep the native object alive. It represents an equivalent value.
/// </summary>
/// <remarks>
/// Using this flag results in the following changes:
/// <see cref="ComWrappers.TryGetComInstance" /> will return <c>false</c> for the returned object.
/// The features provided by the <see cref="CreateObjectFlags.TrackerObject" /> flag will be disabled.
/// Integration between <see cref="WeakReference" /> and the returned object via the native <c>IWeakReferenceSource</c> interface will not work.
/// <see cref="CreateObjectFlags.UniqueInstance" /> behavior is implied.
/// Diagnostics tooling support to unwrap objects returned by `CreateObject` will not see this object as a wrapper.
/// The same object can be returned from `CreateObject` wrapping different COM objects.
/// </remarks>
NonWrapping = 0x2
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,10 @@ public void FromManaged(object? managed) { }
public void Free() { }
}
}
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
[System.CLSCompliantAttribute(false)]
public partial class StrategyBasedComWrappers : System.Runtime.InteropServices.ComWrappers
{
Expand All @@ -488,6 +492,7 @@ public StrategyBasedComWrappers() { }
protected virtual System.Runtime.InteropServices.Marshalling.IIUnknownCacheStrategy CreateCacheStrategy() { throw null; }
protected static System.Runtime.InteropServices.Marshalling.IIUnknownCacheStrategy CreateDefaultCacheStrategy() { throw null; }
protected sealed override object CreateObject(nint externalComObject, System.Runtime.InteropServices.CreateObjectFlags flags) { throw null; }
protected sealed override object? CreateObject(nint externalComObject, System.Runtime.InteropServices.CreateObjectFlags flags, object? userState, out System.Runtime.InteropServices.CreatedWrapperFlags wrapperFlags) { throw null; }
protected virtual System.Runtime.InteropServices.Marshalling.IIUnknownInterfaceDetailsStrategy GetOrCreateInterfaceDetailsStrategy() { throw null; }
protected virtual System.Runtime.InteropServices.Marshalling.IIUnknownStrategy GetOrCreateIUnknownStrategy() { throw null; }
protected sealed override void ReleaseObjects(System.Collections.IEnumerable objects) { }
Expand Down Expand Up @@ -762,16 +767,18 @@ public struct ComInterfaceDispatch
public System.IntPtr Vtable;
public unsafe static T GetInstance<T>(ComInterfaceDispatch* dispatchPtr) where T : class { throw null; }
}
public System.IntPtr GetOrCreateComInterfaceForObject(object instance, CreateComInterfaceFlags flags) { throw null; }
protected unsafe abstract ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count);
public object GetOrCreateObjectForComInstance(System.IntPtr externalComObject, CreateObjectFlags flags) { throw null; }
protected abstract object? CreateObject(System.IntPtr externalComObject, CreateObjectFlags flags);
public object GetOrRegisterObjectForComInstance(System.IntPtr externalComObject, CreateObjectFlags flags, object wrapper) { throw null; }
public object GetOrRegisterObjectForComInstance(System.IntPtr externalComObject, CreateObjectFlags flags, object wrapper, System.IntPtr inner) { throw null; }
public System.IntPtr GetOrCreateComInterfaceForObject(object instance, System.Runtime.InteropServices.CreateComInterfaceFlags flags) { throw null; }
protected unsafe abstract ComInterfaceEntry* ComputeVtables(object obj, System.Runtime.InteropServices.CreateComInterfaceFlags flags, out int count);
public object GetOrCreateObjectForComInstance(System.IntPtr externalComObject, System.Runtime.InteropServices.CreateObjectFlags flags) { throw null; }
public object GetOrCreateObjectForComInstance(System.IntPtr externalComObject, System.Runtime.InteropServices.CreateObjectFlags flags, object? userState) { throw null; }
protected abstract object? CreateObject(System.IntPtr externalComObject, System.Runtime.InteropServices.CreateObjectFlags flags);
protected virtual object? CreateObject(System.IntPtr externalComObject, System.Runtime.InteropServices.CreateObjectFlags flags, object? userState, out System.Runtime.InteropServices.CreatedWrapperFlags wrapperFlags) { throw null; }
public object GetOrRegisterObjectForComInstance(System.IntPtr externalComObject, System.Runtime.InteropServices.CreateObjectFlags flags, object wrapper) { throw null; }
public object GetOrRegisterObjectForComInstance(System.IntPtr externalComObject, System.Runtime.InteropServices.CreateObjectFlags flags, object wrapper, System.IntPtr inner) { throw null; }
protected abstract void ReleaseObjects(System.Collections.IEnumerable objects);
public static void RegisterForTrackerSupport(ComWrappers instance) { }
[System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")]
public static void RegisterForMarshalling(ComWrappers instance) { }
public static void RegisterForMarshalling(System.Runtime.InteropServices.ComWrappers instance) { }
public static void GetIUnknownImpl(out System.IntPtr fpQueryInterface, out System.IntPtr fpAddRef, out System.IntPtr fpRelease) { throw null; }
}
[System.FlagsAttribute]
Expand All @@ -790,6 +797,13 @@ public enum CreateObjectFlags
Aggregation = 4,
Unwrap = 8,
}
[System.FlagsAttribute]
public enum CreatedWrapperFlags
{
None = 0,
TrackerObject = 1,
NonWrapping = 2
}
[System.CLSCompliantAttribute(false)]
public readonly partial struct CULong : System.IEquatable<System.Runtime.InteropServices.CULong>
{
Expand Down
Loading
Loading