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.
A runtime class may have at most one activation factory. Each use of one of the
Activatablemacros 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
Mathclass 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
Mathclass, 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 theIMathStaticsinterface, 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
MathStaticsclass needs to implement both theIMathStaticsstatic members interface and theIActivationFactoryinterface, which is used for default construction (this is required because you declared yourMathtype as default constructible, using theactivatableattribute without a factory interface name).Your activation factory needs to be implemented like so:
The
ActivationFactorybase class template provides a default implementation of theIActivationFactoryinterface. This default implementation simply returnsE_NOTIMPLwhen a client attempts to default construct an instance of theMathtype, so we need to override this member function to actually default construct aMathobject.Note that when using the
InspectableClassStaticto complete the implementation ofIInspectablefor 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.ActivatableClassWithFactoryis used to register a runtime class with an associated activation factory.