共用方式為


Getting rid of TypeLoadException in your WinRT app

I've seen enough people asking about this so I thought I should talk about this in my blog. They see strange first-chance TypeLoadException happening in their application but it doesn't seem never cause any problem (app doesn't crash) and everything still seem to work 'fine'. This seem to happen mostly if you have a C++ Windows Store project with a managed Windows Runtime Component project working together. For example, they might have a C++/CX class that looks like this:

 namespace MyCppApp
{
     ref class Foo sealed : public CSharpWinRTComponent::IFoo
     {
     public:
         virtual void Bar()
         {
         }
     };
}

Note that this is a private ref class (ref=WinRT in this case) that implements a managed WinRT interface IFoo defined in a managed WinRT component.

Then they pass an object instance of this type to a managed WinRT component:

    WindowsRuntimeComponent1::StaticClass::Func(ref new Foo());

And guess what happens?

If you look at the object inside StaticClass::Func in managed debugger, you'll see the type is actually __ComObject, not the Foo class as you've expected.

Even though WinRT does provide you with the strong type support (unlike COM, where everything is interface based) that are very familiar to C# developers, the strong type support doesn't come magically. What actually is happening is when CLR sees a WinRT interface getting passed to managed code, it has no idea about what the interface is, and it needs to ask interface a simple question "Who are you" through IInspectable::GetRuntimeClassName. The answer from the interface is typically a class name (but could also be an interface name, which CLR would actually use for other purposes), and CLR will call Windows API RoResolveNamespace to figure out where that type lives, and loads up the WinMD containing the type.

By default, a C++ application project generates a WinMD for you and the WinMD would contain contain all the public types in that app. This means the private type Foo is actually not in any WinMDs and CLR would fail to create the Foo type instance for you. Fortunately, we have a plan B - CLR would create a __ComObject instance that would happily take any interface calls as long as the object actually supports them (through QI, of course). So the underlying type of the object may not be what you expect, but hey, as long as you can cast this object to the IFoo interface, and call the methods on IFoo, who cares.

The fix would be simply making the type public - and for return, you get the real type in managed code.

If you don't want to make it public (and for good reasons), you can also use RuntimeClassName attribute on IFoo interface:

 namespace MyCppApp
{
    ref class Foo sealed : 
        [Platform::Metadata::RuntimeClassName] public CSharpWinRTComponent::IFoo
    {
    public:
        virtual void Bar()
        {

        }
    };
 }

This tells C++/CX to use WindowsRuntimeComponent1.IFoo as the runtime class name returning to the CLR. CLR would recongizes this interface (well, it's a managed interface, after all), and happily accept it as the best answer. This interface is still not a class type, but at least this is something CLR regconizes and follows the WinRT rules. You would still get a __ComObject, but at least you are not getting TypeLoadException.

I mentioned earlier that making this type public would also fix the problem. The truth is, it's not that simple. When WinRT resolves a type name (to find the WinMD that contains the type), it finds the WinMD by namespace. For example, if the type's namespace (returned from IInspectable::GetRuntimeClassName()) begins with "MyApp", then it would look for a WinMD named MyApp.WinMD. C++/CX generates a WinMD usually with the same name of your project, and if your type has a different namespace, it would never get found, even though it is actually in the WinMD (you can confirm by using ildasm to open the WinMD up - it's .NET assembly, after all). So, you need to make sure the public types actually has the same namespace.

One might ask - is CLR being very picky about the names? it usually doesn't cause any harm after all, right? It depends. Imagine if you pass this object instance to javaScript - how javaScript would know what functions are available in this object? By asking the same question "Who are you" through IInspectable::GetRuntimeClassName. If the name is invalid, JavaScript would have trouble calling any method from it. Although JavaScript might be able to make some guesses from the method signature from which the object is returned (for example, if the method returns IFoo, then javaScript figures this object must be an IFoo, at least), it is still highly recommended to fix this WinRT component

In summary, the TypeLoadException is CLR's way of telling you - something is wrong with this WinRT object instance and it tells us a name that CLR doesn't recongize. It typically would not cause any functionality issues in managed code (other than potential performance issues), but might cause problems in JavaScript, It is recommended to fix the WinRT component that generates this exception so that it returns the right name through IInspectable::GetRuntimeClassName.