I want to create a WinRT component using C++ and WRL (Windows Runtime C++ Template Library) to be consumable in a managed code via C# static method call.
int sum = Math.FastAdd(5,6);
The implementation which doesn't work for me is below.
What can be wrong here?
- In the IDL file create a Math class. It will be a host for static methods on a managed side. Create IMathStatics interface with FastAdd method. This one just contains a bunch of static methods. Mark Math class with a static attribute with the parameter of IMathStatics.
import "inspectable.idl"; #define COMPONENT_VERSION 1.0 namespace WRLNativeComponent { runtimeclass Math; [uuid(EFA9D613-BA8F-4F61-B9E7-C6BE7B7765DD)] [exclusiveto(WRLNativeComponent.Math)] [version(COMPONENT_VERSION)] interface IMathStatics : IInspectable { HRESULT FastAdd([in] int a, [in] int b, [out, retval] int* value); } [uuid(650438BA-C401-49E1-8F06-58DCD5A4B685), version(COMPONENT_VERSION)] interface IMath : IInspectable { HRESULT InstanceMethod(void); } [static(WRLNativeComponent.IMathStatics, COMPONENT_VERSION)] [version(COMPONENT_VERSION), activatable(COMPONENT_VERSION)] runtimeclass Math { [default] interface IMath; } }
- Create MathStatics C++ class. Let InspectableClassStatic macro to point to IMathStatics string identifier. Add ActivatableStaticOnlyFactory macro to point to MathStatics class implementation.
#pragma once #include <wrl.h> #include "MyMath_h.h" // generated from IDL using namespace Microsoft::WRL; namespace WRLNativeComponent { class Math : public Microsoft::WRL::RuntimeClass, ABI::WRLNativeComponent::IMath> { InspectableClass(RuntimeClass_WRLNativeComponent_Math, BaseTrust); public: Math(void) {} ~Math(void) {} STDMETHODIMP InstanceMethod() override { return S_OK; } }; class MathStatics : public Microsoft::WRL::ActivationFactory { InspectableClassStatic(InterfaceName_WRLNativeComponent_IMathStatics, BaseTrust); public: MathStatics(void) {} ~MathStatics(void) {} STDMETHODIMP FastAdd(_In_ int a, _In_ int b, _Out_ int* value) override { if (value == nullptr) return E_POINTER; *value = a + b; return S_OK; } }; ActivatableClass(Math); ActivatableStaticOnlyFactory(MathStatics); }
-
After compilation the WRLNativeComponent.winmd file is created. I can see the Math class with public static FastAdd method.
-
Construct C# client to call the static method. When the call is made, the 'System.InvalidCastException' is thrown. This expected to work correctly.
Best Answer
A runtime class may have at most one activation factory. Each use of one of the
Activatable
macros registers an activation factory for a runtime type. Therefore, the following code from your libraryattempts to register two activation factories: the first registers a simple activation factory for the
Math
class and the second registers another simple activation factory that isn't actually usable (we'll see why in the moment).Because the first simple activation factory is associated with the
Math
class, it gets returned when the C# component attempts to call the static member function. The C# component then attempts to cast this interface pointer to theIMathStatics
interface, which the simple activation factory does not implement, so the cast fails and you get theInvalidCastException
.Since there can only be one activation factory for a given runtime class, your
MathStatics
class needs to implement both theIMathStatics
static members interface and theIActivationFactory
interface, which is used for default construction (this is required because you declared yourMath
type as default constructible, using theactivatable
attribute without a factory interface name).Your activation factory needs to be implemented like so:
The
ActivationFactory
base class template provides a default implementation of theIActivationFactory
interface. This default implementation simply returnsE_NOTIMPL
when a client attempts to default construct an instance of theMath
type, so we need to override this member function to actually default construct aMath
object.Note that when using the
InspectableClassStatic
to complete the implementation ofIInspectable
for an activation factory, the class name should be the name of the runtime class (in this case,RuntimeClass_WRLNativeComponent_Math
), not the name of the statics interface. Activation is performed by type name, and it is this name that is used by the WRL infrastructure to look up the activation factory for a runtime type using its name.ActivatableClassWithFactory
is used to register a runtime class with an associated activation factory.