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?
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; } }
#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.
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 library
ActivatableClass(Math);
ActivatableStaticOnlyFactory(MathStatics);
attempts 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 the IMathStatics
interface, which the simple activation factory does not implement, so the cast fails and you get the InvalidCastException
.
Since there can only be one activation factory for a given runtime class, your MathStatics
class needs to implement both the IMathStatics
static members interface and the IActivationFactory
interface, which is used for default construction (this is required because you declared your Math
type as default constructible, using the activatable
attribute without a factory interface name).
Your activation factory needs to be implemented like so:
class MathStatics : public ActivationFactory<IMathStatics>
{
InspectableClassStatic(RuntimeClass_WRLNativeComponent_Math, BaseTrust);
public:
MathStatics() {}
~MathStatics() {}
STDMETHODIMP ActivateInstance(_Outptr_result_nullonfailure_ IInspectable** ppvObject) override
{
return MakeAndInitialize<Math>(ppvObject);
}
STDMETHODIMP FastAdd(_In_ int a, _In_ int b, _Out_ int* value) override
{
if (value == nullptr) return E_POINTER;
*value = a + b;
return S_OK;
}
};
ActivatableClassWithFactory(Math, MathStatics);
The ActivationFactory
base class template provides a default implementation of the IActivationFactory
interface. This default implementation simply returns E_NOTIMPL
when a client attempts to default construct an instance of the Math
type, so we need to override this member function to actually default construct a Math
object.
Note that when using the InspectableClassStatic
to complete the implementation of IInspectable
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.