سؤال

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?

  1. 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;
        }
    }
  1. 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);
    }
  1. After compilation the WRLNativeComponent.winmd file is created. I can see the Math class with public static FastAdd method.

  2. 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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top