Search code examples
c#c++unity-game-enginepluginswindows-runtime

Why calling the constructor from a WinRT component from C# Unity script results in NullReferenceException?


I'm working on a Unity plugin which is a C++/WinRT component and when I try to access the runtime class from a Unity C# script, it throws NullReferenceException. Here are the simplified contents of the component and the unity script I'm having problems with:

WinRT RTClass.idl:

namespace RuntimeComponent1
{
    runtimeclass HelperClass
    {
        HelperClass();
        Int32 MyProperty;
    }

    [default_interface]
    runtimeclass RTClass
    {
        RTClass();
        Int32 MyProperty;
    }
}

WinRT HelperClass.h:

#pragma once
#include "HelperClass.g.h"

namespace winrt::RuntimeComponent1::implementation
{
    struct HelperClass: HelperClassT<HelperClass>
    {
        HelperClass();

        int32_t MyProperty();
        void MyProperty(int32_t value);
    };
}

namespace winrt::RuntimeComponent1::factory_implementation
{
    struct HelperClass: HelperClassT<HelperClass, implementation::HelperClass>
    {
    };
}

WinRT RTClass.h:

#pragma once
#include "RTClass.g.h"

namespace winrt::RuntimeComponent1::implementation
{
    struct RTClass : RTClassT<RTClass>
    {
        RTClass();

        int32_t MyProperty();
        void MyProperty(int32_t value);
    };
}

namespace winrt::RuntimeComponent1::factory_implementation
{
    struct RTClass : RTClassT<RTClass, implementation::RTClass>
    {
    };
}

WinRT HelperClass.cpp

#include "pch.h"
#include "HelperClass.h"
#include "HelperClass.g.cpp"

namespace winrt::RuntimeComponent1::implementation
{
    HelperClass::HelperClass()
    {
    }
    int32_t HelperClass::MyProperty()
    {
        throw hresult_not_implemented();
    }

    void HelperClass::MyProperty(int32_t /* value */)
    {
        throw hresult_not_implemented();
    }
}

WinRT RTClass.cpp

#include "pch.h"
#include "RTClass.h"
#include "RTClass.g.cpp"

namespace winrt::RuntimeComponent1::implementation
{
    RTClass::RTClass()
    {
    // Do some stuff in the constructor
    }
    int32_t RTClass::MyProperty()
    {
        throw hresult_not_implemented();
    }

    void RTClass::MyProperty(int32_t /* value */)
    {
        throw hresult_not_implemented();
    }
}

Unity script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if ENABLE_WINMD_SUPPORT
using RuntimeComponent1;
#endif

public class TestScript : MonoBehaviour
{
#if ENABLE_WINMD_SUPPORT
RTClass rt;
#endif

    // Start is called before the first frame update
    void Start()
    {
        #if ENABLE_WINMD_SUPPORT
    rt = new RTClass(); // <--- NullReferenceException here
    // ...
        #endif
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

The runtime component has more than 1 runtime classes and if I try to create an instance of the component's default class (RTClass) in the Unity script, it gives NullReferenceException.

The WinRT component is built for ARM architecture in Debug mode. I use Unity 2021.3.27f1 LTS but also tested it with 2022.3.3f1 LTS, same results. I copied the .dll and .winmd to Unity Project Dir\Assets\Plugins\WSA\ARM and set the Platform Settings > CPU to ARM. In the Player Settings, Unsafe Code is allowed, Api compatibility level is set to .NET Framework. The build platform is also set to UWP and the architecture is ARM. What I think is the problem is on the winRT components side since intellisense does not complain in the C# script and Unity successfully compiles the project, the exception happens in runtime. What could be the cause of this?


Solution

  • I've managed to solve the problem by removing the runtime class HelperClass declaration from RTClass.idl and deleting HelperClass.h and .cpp. Then I created a new View Model (C++/WinRT) named HelperClass which generates and groups the HelperClass.h and .cpp under HelperClass.idl. By building this, it generated 2 winmd files (RTClass.winmd and HelperClass.winmd) but it also merged them under the root namespace RuntimeComponent1.winmd. Turns out with this approach, Unity doesn't complain about making a new instance of the runtime class.