Skip to content
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

IDynamicInterfaceCastable interface #36654

Closed
AaronRobinsonMSFT opened this issue May 18, 2020 · 54 comments
Closed

IDynamicInterfaceCastable interface #36654

AaronRobinsonMSFT opened this issue May 18, 2020 · 54 comments
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime.CompilerServices
Milestone

Comments

@AaronRobinsonMSFT
Copy link
Member

AaronRobinsonMSFT commented May 18, 2020

Background and Motivation

In the .NET Native runtime support existed for a .NET class to participate in a C-style cast when that class didn't support the cast-to type. The COM QueryInterface() is an example where this kind of scenario exists. In COM, the instance is represented by an opaque IUnknown pointer and the only way to discover what is supported by the instance is to call QueryInterface() with a GUID representing the question of "Does this instance support this type?". The answer is typically a simple "yes" (S_OK) or "no" (E_NOINTERFACE). If the answer is "yes", a casted to instance of the type is returned, otherwise null. There are scenarios where the current instance may not have implemented this type but can provide an instance that does - this is called a tear-off.

In .NET, the metadata for a type is static and therefore if a type can't be cast to another type because it isn't in metadata that is correct. This means that a type has no reason to participate in the casting question. However, when implementing support for a COM scenario this rigidity isn't as beneficial since it may not be possible to know all the supported types on an instance. This proposal provides a way to let types provide an answer and an object instance that does satisfy the requested type eventhough the original instance does not and still adhere to the static metadata constraints of .NET.

In .NET Native there are two mechanisms to address this problem. The first, ICastable interface, proved to have usability issues. Usage of the ICastable API was error prone and had a potentially catastrophic consequence if used incorrectly - silent success or an unstable runtime. The ICastable API exists in the CoreCLR runtime but is not publicly exposed and exists only to support MCG scenarios.

The second approach was CastableObject. This approach didn't return a type but instead returned an actual object instance to dispatch on. The CastableObject type is an abstract type that contained some minor state for caching purposes. This approach did require inserting a new type into the user's type hierarchy. Updating the type hierarchy and the stored state of the abstract type made this solution more reliable, but less performant than ICastable.

For CoreCLR, the following proposal is based on lessons learned from .NET Native along with a recent C# language feature, default interfaces, that make a modified version of the ICastable approach easier to implement with confidence. The proposed interface followed by a usage example are described below.

Goals

  • Support the ComWrappers API in creating C# friendly wrappers for external IUnknown based objects.
  • Avoid having to call QueryInterface() for all possible supported types when an external IUnknown based object enters the runtime.
  • Support IL Linker scenarios.

Non-Goals

  • Remove or alter current ICastable scenarios.

Proposed API

namespace System.Runtime.CompilerServices
{
    /// <summary>
    /// Interface used to participate in a type cast failure.
    /// </summary>
    public interface ICastableObject
    {
        /// <summary>
        /// Called when an implementing class instance is cast to an interface type that
        /// is not contained in the class's metadata.
        /// </summary>
        /// <param name="interfaceType">The interface type.</param>
        /// <param name="throwIfNotFound">Indicates if the function should throw an exception rather than default(RuntimeTypeHandle).</param>
        /// <returns>The type that should be used to dispatch for <paramref name="interfaceType"/> on the current object.</returns>
        /// <remarks>
        /// This is called if casting this object to the given interface type would
        /// otherwise fail. Casting here means the IL isinst and castclass instructions
        /// in the case where they are given an interface type as the target type. This
        /// function may also be called during interface dispatch.
        ///
        /// The returned type must be an interface type marked with the <see cref="CastableObjectImplementationAttribute"/>, otherwise <see cref="System. InvalidOperationException" />
        /// will be thrown. When the <paramref name="throwIfNotFound" /> is set to false,
        /// a return value of default(RuntimeTypeHandle) is permitted. If <paramref name="throwIfNotFound" />
        /// is true and default(RuntimeTypeHandle) is returned then <see cref="System.InvalidCastException" />
        /// will be thrown unless an exception is thrown by the implementation.
        /// </remarks>
        RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType, bool throwIfNotFound);
    }

    /// <summary>
    /// Attribute required by any type that is returned by <see cref="ICastableObject.GetInterfaceImplementation(RuntimeTypeHandle, bool)"/>.
    /// </summary>
    /// <remarks>
    /// This attribute is used to enforce policy in the runtime and make
    /// <see cref="ICastableObject" /> scenarios linker friendly.
    /// </remarks>
    [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
    public sealed class CastableObjectImplementationAttribute : Attribute
    {
    }
}

Design notes

  • The default interface method itself will be called with a this pointer that by definition should implement the enclosing type (e.g. IFooImpl) as well as any implementing types (e.g. IFoo). We will need to define the exact semantics and requirements here.
  • Virtual Stub Dispatch (VSD) caching.
    • ICastableObject could control casting on a per object basis, as we can easily call GetInterfaceImplementation() at each cast opportunity.
    • ICastableObject would control the result of a dispatching on an interface at a per TYPE level. So, a given type could not use different default interface type (e.g. IFooImpl) for different instances. This would be sufficient for any plausible use of this feature for interop, but it might impact useablility for aspect oriented programming, etc.
    • The end result is that a given object may or may not implement the interface, but if it does, all implementations must be the same.
  • By throwing an exception from the interface impl the debugger will report the exception as coming from that type. We should suggest to users of this API to utilize some debugger attributes to make everything look normal.

Usage Examples

Consider the following interface and class.

interface IFoo
{
    int CallMe(int i);
}

class Baz
{
    ...
}

class Bar : ICastableObject
{
    ...

    // Call when cast is performed on an instance of Bar but the type isn't in Bar's metadata.
    RuntimeTypeHandle ICastableObject.GetInterfaceImplementation(RuntimeTypeHandle interfaceType, bool throwIfNotFound)
    {
        Debug.Assert(interfaceType.Value != IntPtr.Zero);

        if (interfaceType == typeof(IFoo).TypeHandle)
            return typeof(IFooImpl).TypeHandle;
        
        if (throwIfNotFound)
        {
            var typeName = Type.GetTypeFromHandle(interfaceType).FullName;
            throw new InvalidCastException($"Don't support {typeName}");
        }

        return default;
    }

    // An "implemented" interface instance that will handle "this" of type "Bar".
    // Note that when this default interface implementation is called, the "this" will
    // be typed as a "IFooImpl".
    [CastableObjectImplementation]
    public interface IFooImpl : IFoo
    {
        int IFoo.CallMe(int i)
        {
            // Perform desired action on the "this" pointer which will be of type Bar.
            //  - Cast to some other type
            //  - Unsafe.As<T>()
            //  - Table look up
            //  - etc.
            ...
        }
    }
}

The following is an example of usage.

Baz z = ...;

// Will result in InvalidCastException being thrown.
IFoo zf = (IFoo)z;

Bar b = ...;

// However, since Bar implements ICastableObject, GetInterfaceImplementation will be called.
IFoo bf = (IFoo)b;
// bf is a Bar.IFooImpl with a Bar 'this'

// Will call Bar.IFooImpl.CallMe()
bf.CallMe(27);

Community impact

@AaronRobinsonMSFT AaronRobinsonMSFT added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Runtime.CompilerServices labels May 18, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label May 18, 2020
@AaronRobinsonMSFT
Copy link
Member Author

AaronRobinsonMSFT commented May 18, 2020

@jkoritzinsky
Copy link
Member

I dislike the lack of type-safety around the this object in IFooImpl. I feel like this has the same safety issue as ICastable in that regard. You still end up with a this object that is not the expected type, so you end up with the same problems around calling virtual methods or casting, right? The goal with ICastableObject was to ensure that your this always matched the expected type, so it didn't have any of these issues.

@AaronRobinsonMSFT
Copy link
Member Author

AaronRobinsonMSFT commented May 18, 2020

I feel like this has the same safety issue as ICastable in that regard

It does have a similar issue, but no where near as dangerous. The distinction here is the limiting factor of interfaces. There are no fields access issues here because there are no fields only method dispatch issues. Those need to be considered, but the default interface approach limits a large vector of the worst kind of issues in ICastable.

The CastableObject approach was safer but at considerable complexity compared to ICastable. Given the limitations for how the type can be used with default interfaces, I'm not as convinced the type assurance benefit offsets the performance and implementation costs for how this API will be used.

@AaronRobinsonMSFT
Copy link
Member Author

AaronRobinsonMSFT commented May 18, 2020

Also, note that ICastableObject was never exposed only the abstract CastableObject was. Following the approach in .NET Native would require exposing an abstract type not an interface.

@weltkante
Copy link
Contributor

You still end up with a this object that is not the expected type, so you end up with the same problems around calling virtual methods or casting, right?

So whats the worst that can happen here? What would happen if you call another interface method that doesn't have a default implementation? If it just throws an exception and doesn't corrupt runtime state that sounds fine to me.

@jkoritzinsky
Copy link
Member

@Scottj1s from a CSWinRT perspective, this will require a lot of additional code generation and an introduction of a "target" switch for code-gen since it's so heavily based on default interface methods, which aren't valid in the .NET standard target.

@AaronRobinsonMSFT
Copy link
Member Author

AaronRobinsonMSFT commented May 18, 2020

same problems around calling virtual methods or casting, right?

If this is asking must a cast be performed because this is now what you want, the answer is "yes". In fact the this will not have the interface (i.e. IFoo) at all, otherwise the original cast would have succeeded, so any call to IFoo would fail at runtime.

What would happen if you call another interface method that doesn't have a default implementation?

This question is implied in the design notes and we need to understand how/if we can fail "gracefully". At a minimum we need to not corrupt the runtime so a fail fast might be what we get, but that is something we need to investigate.

@Scottj1s
Copy link

Scottj1s commented May 18, 2020

@jkoritzinsky, as I understand the spec, this would require a parallel implementation of each interface (IFooImpl) to address the casting and dispatching of the smuggled 'this' pointer. In that regard, it add some significant bloat. I was hoping there might be a way to somehow fold ABI::IFoo and IFooImpls together. Can you elaborate on the "target" switch?

@Scottj1s
Copy link

@AaronRobinsonMSFT, I think we need to address support for classes as well as interfaces. I'm concerned that this design, which allows an interface cast to be implemented with little overhead, is imposing the constraint that classes can't be supported. But that will be a common use case for C#/WinRT.

@AaronRobinsonMSFT
Copy link
Member Author

@Scottj1s If that is truly needed then we would need to think about actually going the CastableObject route and storing some object state. I would like to understand why class casting is needed. A concrete example would be helpful here.

@Scottj1s
Copy link

Scottj1s commented May 18, 2020

@AaronRobinsonMSFT, here's an example:
microsoft/CsWinRT#279
Saw your question there and answered. The type being returned is a DeviceInformationCollection, but C#/WinRT doesn't know that. All it has to go on is what GetRuntimeClassName returns, which in this case is the default interface. That will be true for all C++/WinRT code-based implementations (not metadata-based, and defined by projected templates)

@jkoritzinsky
Copy link
Member

Another example (although more theoretical): WinUI specifies a custom runtime class name for most every type they define in native code, even if it isn't exposed in managed code. Let's say that they introduce a new implementation of Button called FooButton and forget to expose FooButton in their winmd metadata. FooButton's GetRuntimeClassName would return "Microsoft.UI.Xaml.Controls.FooButton" instead of "Microsoft.UI.Xaml.Controls.Button". This button would work completely fine in XAML, but if it was ever passed to managed code, we would have to fall back to the ICastableObject casting infrastructure to cast the resulting object to MUX.Controls.Button.

The impetus for requesting ICastableObject from WinUI was this exact scenario with interfaces, so I wouldn't be amazed if they would also hit it with classes.

@Scottj1s
Copy link

DeviceInformationCollection is a bad example - that's a C#/WinRT code-gen isssue. Jeremy's example is exactly what I was trying to describe: code-based and not metadata-based. Most C++/WinRT implementations will behave this way.

@AaronRobinsonMSFT
Copy link
Member Author

and forget to expose FooButton in their winmd metadata.

I would consider that a bug though. Right? If so I don't think we can design anything that will mitigate these kind of bugs.

The impetus for requesting ICastableObject from WinUI was this exact scenario with interfaces, so I wouldn't be amazed if they would also hit it with classes.

That isn't entirely accurate. The ICastableObject scenario is for type erased declarations. Consider functions that declare object/IInspectable arguments and the onus is on the user to cast and inspect. Being able to put that instance in a wrapper that can lazily query for only those types the user knows should be there while handling the static metadata problem is what this is targeted at addressing. Interfaces that are newly added to an implementation or undeclared/hidden is a nice side-effect supported scenario. When a type doesn't properly declare an interface that it expects users to know about and doesn't update its metadata seems like a bug, but I welcome examples.

@jkotas
Copy link
Member

jkotas commented May 18, 2020

this would require a parallel implementation of each interface (IFooImpl) to address the casting and dispatching of the smuggled 'this' pointer. In that regard, it add some significant bloat

I do not see where this bloat would come from. Can you elaborate?

classes

For classes, the projections have to create the right wrapper type from the start (e.g. using GetRuntimeClassName or using the type from the signature). It is how the built-in COM and WinRT always worked. Allowing objects to change their type on the fly would be a very questionable .NET runtime feature.

an introduction of a "target" switch for code-gen since it's so heavily based on default interface methods, which aren't valid in the .NET standard target.

We stopped designing the platform features for .NET standard first several years ago. The problem with designing for .NET standard first is that it makes the design optimized for the past, not for the future, that is a sure way to run the platform into the ground. We design for future (ie the latest .NET version) first. If there is a strong reason to have the feature available for netstandard too, we look at the best way to make that happen in some form - it is typically degraded experience and requires extra work. We do not let the netstandard constrains to leak into the forward looking design.

@Scottj1s
Copy link

I think I'm persuaded. A runtimeclass is a convenient interface container. If a type hasn't been modeled that way, the user will have to cast per-interface, which is fine. All members must be exposed via interfaces anyway.

@jkoritzinsky
Copy link
Member

this would require a parallel implementation of each interface (IFooImpl) to address the casting and dispatching of the smuggled 'this' pointer. In that regard, it add some significant bloat

I do not see where this bloat would come from. Can you elaborate?

In a design more similar to CoreRT's CastableObject, C#/WinRT could directly re-use their current "ABI" types that implement the .NET definition of the interface over an IntPtr. With this design, C#/WinRT will have to provide both the current ABI type implementation, as well as a new implementation that ICastableObject will return that unwraps the object and either constructs the current ABI type and calls that to do the work, or emits the implementation again to do all of the work inline.

an introduction of a "target" switch for code-gen since it's so heavily based on default interface methods, which aren't valid in the .NET standard target.

We stopped designing the platform features for .NET standard first several years ago. The problem with designing for .NET standard first is that it makes the design optimized for the past, not for the future, that is a sure way to run the platform into the ground. We design for future (ie the latest .NET version) first. If there is a strong reason to have the feature available for netstandard too, we look at the best way to make that happen in some form - it is typically degraded experience and requires extra work. We do not let the netstandard constrains to leak into the forward looking design.

The netstandard mention was more for @Scottj1s since C#/WinRT will have to think about it for their usage of this feature.

@Scottj1s
Copy link

Is there any story here for netstandard? I was hoping we could bury the details in winrt.runtime.dll, so an explicit target switch wouldn't be necessary. Beyond that, would netstandard just not have any dynamic casting support? That's certainly acceptable, if we have no choice (as with reference tracking).

@AaronRobinsonMSFT
Copy link
Member Author

@Scottj1s unfortunately this entire path would require .NET5. The default interface support required some runtime features.

@MichalStrehovsky
Copy link
Member

Cc @vitek-karas @marek-safar since this affects interface sweeping in Linker (IFooImpl would never be seen as applied on an allocated type and linker would likely strip its interfaces and implementations, leaving just an empty type to satisfy the typeof.) We might need to require that ICastableObject.GetInterfaceImplementation is always structured in a way that a simple local dataflow analysis sees all RuntimeTypeHandles this method might return.

Also, Marek, could you add relevant folks from Mono side?

@MichalStrehovsky
Copy link
Member

What kind of code do we expect to see in ICastableObject.GetInterfaceImplementation? Is there any way we could make this more declarative (e.g. encoding a table of all possible interface -> interface implementation mappings using custom attributes, potentially filtered using some code at runtime if the mapping isn't always applicable).

Making this declarative would be more friendly to static analysis that is necessary to make this work with linking and AOT.

@weltkante
Copy link
Contributor

weltkante commented May 19, 2020

Is there any way we could make this more declarative

Please don't constrain the implementation for non-AOT scenarios too much, we'll probably have to roll our own RCW implementation since I suspect our scenarios will not to be supported out of the box (we use custom TLB generation since TLBs have been dropped from .NET Core, but they do work if implemented manually), so if the low level support is too restrictive this would prevent us from adapting ComWrapper.

The new implementation should be fully capable to replace the current RCW/CCW system with a custom one, not build a new locked-in box that you have to work around. (It sounds like we'd have to fork the runtime to extend a declarative system if it turns out to be short sighted, which is exactly the same problem the old COM interop had and ComWrappers tries to solve by moving implementations to user code, so being too declarative has me worried of repeating the same mistakes again.)

@MichalStrehovsky
Copy link
Member

@weltkante Are you expecting to be using something like TypeBuilder to construct interface types at runtime? I wouldn't be opposed to an extra API to allow those scenarios, but those will be marked IL Linker unsafe and one will not be able to use IL Linker with that API (without seeing a bunch of warnings, that is).

If you're not using TypeBuilder I don't see why a declarative approach wouldn't work - the set of possible interface implementations is finite and known at compile time.

@MichalStrehovsky
Copy link
Member

During the API review we discussed whether we should allow leaking out arbitrary exceptions from the GetInterfaceImplementation.

One thing occurred to me - should we also loop in the codegen team on this?

  • One thing is that castclass/isinst/unbox/refanyval have the list of exceptions they're allowed to throw defined in the spec. isinst is basically not allowed to throw. Codegens make assumptions around that.
  • There's also assumptions that casting operations are side-effect free, but that's hard to police for arbitrary user code.

@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review blocking Marks issues that we want to fast track in order to unblock other important work labels Jun 2, 2020
@terrajobst
Copy link
Member

terrajobst commented Jun 2, 2020

Video

  • Looks good, but we should make the type name less attractive, such as IDynamicInterfaceCastable
  • The implementer of IDynamicInterfaceCastable has to abide to the contract of only throwing when throwIfNotFound is true and even then should only throw InvalidCastException. However, the runtime doesn't handle the throwing so that implementers can tweak the message
  • However, it is valid for the implementer to return default and the let the runtime throw
  • It would be nice if throwIfNotFound would be renamed to indicate whether it's an is/as or a hard cast
namespace System.Runtime.InteropServices
{
    public interface IDynamicInterfaceCastable
    {
        RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType, bool throwIfNotFound);
    }
    [AttributeUsage(AttributeTargets.Interface,
                    AllowMultiple = false,
                    Inherited = false)]
    public sealed class DynamicInterfaceCastableImplementationAttribute : Attribute
    {
    }
}

@AaronRobinsonMSFT
Copy link
Member Author

@dotnet/jit-contrib @AndyAyersMS @BruceForstall @davidwrighton Any thoughts on @MichalStrehovsky's comment at #36654 (comment).

@jkotas
Copy link
Member

jkotas commented Jun 2, 2020

leaking out arbitrary exceptions

The transient exceptions like OutOfMemoryException or certain FileLoadExceptions can leak from pretty much everywhere. The codegen makes simplifying assumption that optimizing out these exceptions or moving them around is acceptable.

@jkotas
Copy link
Member

jkotas commented Jun 2, 2020

I think we should allow leaking arbitrary exceptions. There is no good way to tell whether the exception is one of these transient exceptions that are ok to leak; or whether the implementation is misbehaving. The documentation should describe the contract that well-behaved implementation IDynamicInterfaceCastable should abide to.

@terrajobst
Copy link
Member

terrajobst commented Jun 2, 2020

The question is how callers are supposed to reason about this behavior. I guess I buy that catching InvalidCastException isn't really a thing and people use is and as for for that. So in that sense I guess I don't actually care about which exceptions a cast can produce, assuming it's never going to happen as an implicit cast which would be the case here as the whole point of the feature is to extend the static type hierarchy at runtime.

@jkotas
Copy link
Member

jkotas commented Jun 2, 2020

Pretty much anything in .NET can throw SomethingWentHorriblyWrongException. It includes is cast.

For example, is in .NET Framework can go over remoting boundary and so it can throw e.g. SocketException when the remoting channel was network based and got disconnected. We do not have remoting in .NET Core, but casting is still fairly complex operations and we would be likely able to construct artificial cases where it can throw arbitrary exceptions, even without IDynamicInterfaceCastable.

For callers, it means to only catch exceptions that they expect and understand. Leave all other exceptions alone as they are most likely SomethingWentHorriblyWrongExceptions.

@weltkante
Copy link
Contributor

weltkante commented Jun 2, 2020

AccessViolationException and similar exceptions probably can always happen in managed code calling out to native code, and I don't want anyone catching and ignoring those due to misunderstanding the requirements, either exceptions need to be allowed to escape or there needs to be clear documentation how to panic if something goes wrong.

@KevinCathcart
Copy link
Contributor

There are actually 3 different circumstances in which the method on the new interface is called. There is the hard cast (castclass), soft cast (isinst), and when called by by the stubs trying to resolve the virtual call (prior to the result getting cached).

The approved api allows the implementation to distinguish between the first two, but it cannot tell the difference between the first and the third. This seems like it could be useful. For example, if you are checking the instances during cast operations, this might be somewhat costly. One may want to skip those checks during virtual calls that are taking the slow path in the VSD.

Skipping those checks would normally be safe for two reasons. First, if the calling code is verifiable, then it must have already done one of the cast operations, which must have returned success. Second, if the calling code was unsafe (not verifiable) and made the interface call without the cast operation, it could just as easily have passed the object to some other function that takes the target interface, and makes the virtual interface call on it. In that case, if the VSD already has the virtual call cached, then it would bypass IDynamicInterfaceCastable entirely. So if unsafe code accidentally calling the DIMs without running a cast opcode first is a concern for somebody, they already would need to make the DIMs resilient to this scenario.

And of course, this is only one possible reason for wanting to know the that the Interface is being invoked for virtual call reasons, as opposed to casting reasons. There may be other reasons for wanting to know the difference. I’m not thinking of any off the top of my head, but that does not mean there are no others.

So it might make sense to tweak the api slightly. Perhaps the bool argument should become a three way enum?

@jkotas
Copy link
Member

jkotas commented Jun 7, 2020

Another observation is that the implementation interface is not needed for casting. If we wanted to allow the callbacks to do the minimal amount of work possible, we may want to split it into two methods:

RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType);
bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented);

@elinor-fung
Copy link
Member

elinor-fung commented Jun 8, 2020

I like splitting into two.

namespace System.Runtime.InteropServices
{
    public interface IDynamicInterfaceCastable
    {
        /// <summary>
        /// Called when an implementing class instance is cast to an interface type that
        /// is not contained in the class's metadata.
        /// </summary>
        /// <param name="interfaceType">The interface type.</param>
        /// <param name="throwIfNotImplemented">Indicates if the function should throw an exception instead of returning false.</param>
        /// <returns>Whether or not this object can be cast to the given interface</returns>
        /// <remarks>
        /// This is called if casting this object to the given interface type would
        /// otherwise fail. Casting here means the IL isinst and castclass instructions
        /// in the case where they are given an interface type as the target type.
        ///
        /// If <paramref name="throwIfNotImplemented" /> is true and an exception
        /// is not thrown by the implementation, but false is returned, then
        /// <see cref="System.InvalidCastException" /> will be thrown.
        /// </remarks>
        bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented);

        /// <summary>
        /// Called during interface dispatch when the given interface type cannot be found
        /// in the class's metadata.
        /// </summary>
        /// <param name="interfaceType">The interface type.</param>
        /// <returns>The type that should be used to dispatch for <paramref name="interfaceType"/>.</returns>
        /// <remarks>
        /// When this function is called, the cast of this object to the given interface
        /// should already have been verified through the castclass/isinst instructions.
        ///
        /// The returned type must be an interface type marked with the
        /// <see cref="DynamicInterfaceCastableImplementationAttribute"/>, otherwise
        /// <see cref="System.InvalidOperationException" /> will be thrown.
        /// </remarks>
        RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType);
    }
}

@benstevens48
Copy link

Just in case this is related and no-one had considered it, I thought I'd mention a source of crashes with the existing implementation in a C# UWP app.

Implicitly casting a list of class instances to a list of interfaces that they implement frequently results in an app crash when the list is used later on. For example, if I do this:

var myList = new List<StorageFile>();
//add some storage files to list
var dataPackage = new DataPackage();
await dataPackage.SetStorageItemsAsync(myList);

Then the app crashes (well it did last time I checked, but I haven't tested recently). Note that the definition of dataPackage.SetStorageItemsAsync is

public void SetStorageItems(IEnumerable<IStorageItem> value);

so the list is implicitly cast from IEnumerable<StorageFile> to IEnumerable<IStorageItem>.

This is not the only case where I have experienced problems before. In one case the crash actually went away by looping through the list using an iterator with empty body, like

foreach(item in list){
//Do nothing!!
}

This was terribly confusing as I only added the loop to try to debug the issue (I had a Debug.WriteLine in it to start with) and it turned out that my debugging code fixed the problem!!

I hope that whatever solution you come up with can handle this implicit casting of lists correctly.

@AaronRobinsonMSFT
Copy link
Member Author

@benstevens48 Thanks for letting us know. This API will be leveraged by the C#/WinRT tool chain and that would be responsible for handling a majority of these kind of UX cases. If possible it might be interesting to try the above now using the new tool chain and check the behavior. If this isn't possible, creating an issue at https://github.com/microsoft/CsWinRT would put the behavior on their radar for future validation.

@elinor-fung
Copy link
Member

Implemented in #37042

Interop-CoreCLR 5.0 automation moved this from To do to Done Jun 12, 2020
@elinor-fung elinor-fung changed the title ICastableObject interface IDynamicInterfaceCastable interface Jun 16, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime.CompilerServices
Projects
No open projects
Development

No branches or pull requests