Porting Unity 2017 UWP c# project to 2020 UWP c++ can't find UnityEngine

Have a project I wrote against 2017 that generated c# code. I want to move it to the most recent version and continue development. I see that I have to move to c++ for the generated project, no problem I am well versed in c++ and UWP.

I have most of the code moved over but an running into an issue in that I can’t find the UnityEngine namespace equivalent. And then as a result UnityEngine.GameObject. The code I am trying to port is:

namespace UnityXAML
{
    public delegate void UnityEvent(object arg);
    public sealed class Communications
    {
        /// Called to send a message to the unity components
        public static void SendMessageToUnity(string msg)
        {
            UnityEngine.GameObject gameObject = UnityEngine.GameObject.Find("Camera");
            if (gameObject != null)
            {
                gameObject.GetComponent<ButtonHandlers>().ShowFeedback(msg);
            }
            else
            {
                throw new Exception("Camera not found, have you exported the correct scene?");
            }
        }

        /// Connects the event delegates to be able to receive events from unity.
        public static void SetEventCallback(UnityEvent e)
        {
            UnityEngine.GameObject gameObject = UnityEngine.GameObject.Find("Camera");
            if (gameObject != null)
            {
                var bh = gameObject.GetComponent<ButtonHandlers>();
                if (bh != null)
                {
                    bh.onEvent = new ButtonHandlers.OnEvent(e);
                }
            }
            else
            {
                throw new Exception("Camera not found, have you exported the correct scene?");
            }
        }
    }
}

Hey I made a sample project here: https://github.com/TautvydasZilys/unity-uwp-il2cpp-with-csharp-project-example/blob/master/Assets/Scripts/PrintActivationArguments.cs

It doesn’t exactly do what you want - it also recreates a C# project. However, you could just disregard that part and just use C++.

The main difference when going from C# to C++ is that you cannot call Unity’s C# API directly anymore. To overcome this, you need to setup a .cpp “bridge” file which can be used by both C# scripts in Unity and the generated C++ project. It could look something like this:

https://github.com/TautvydasZilys/unity-uwp-il2cpp-with-csharp-project-example/blob/master/Assets/Scripts/ActivationArgumentsHelper.cpp

There are two main functions of interest:

  1. One marked as __declspec(dllexport): this function can be called from the generated C++ project.
  2. Another one is marked with extern “C”, and it can be called from using C# scripts in Unity using [DllImport(__Internal)].

Depending on which way you need to go, you’d set a callback as a global variable from one side, and then invoke it from the other side.

You would place this .cpp file in your Unity project and Unity will compile it for you. Of course, this part is optional and you can use whatever plugin mechanism you want: native .dll files, windows runtime components, whatever else you can imagine can be called into from both C# scripts and raw C++ code.

Let me know if you need me to clarify anything.

I created a communications.cpp and added it to assets\shims.

When I build the project I get a file in IL2CppOutputProject(desktop) with source\cppplugins\communications.cpp

Now how am I supposed to reference this from my UWP project? I can’t add the IL2CppOutputProject project as a dependency because it is incompatible (according to Visual Studio 2019).

Just forward declare it:

__declspec(dllimport) void DoStuff(int param);

Alternatively, you can put the method declaration in a .h file and include it from here.

So within the shim code does the c++ file have access to the unity engine?
If say I want to be able to change a unity buttons text from within my xaml project do I need to:

  • Store a static GameObject pointer in the shim file? Where do I get the pointer definition?
  • On unity startup tell the shim file “here is the button”, which is then stored in the shim
  • From xaml call the shim UpdateButtonText(string foo)
  • Use the object pointer and manipulate the text (how would I get access to the internals of that button).

Or do I need to

  • Store a callback in the shim with a string parameter
  • On unity startup register a callback from one of my c# scripts that takes a string param
  • From xaml call the shim UpdateButtonText(string foo)
  • Use the stored callback to forward the string to the c# which can then manipulate the gameobject

Unity’s API is not accessible from C++. You will have to callback to Unity’s C# land before you can interact with any Unity objects. So yeah, it’s the second option.

I was thinking that was going to be the case:
In my ButtonHandlers.cs handler I have:

public class ButtonHandlers : MonoBehaviour {
delegate void ShimCallbackDelegate(int number);

void ShimCallback(int number) // member method

In start:
#if ENABLE_WINMD_SUPPORT && UNITY_WSA
SetupAnswerEventCallback(ShimCallback);
#endif

In my shim I have
typedef void(__stdcall* AnswerEventCallback)(int value);
extern “C”
{
// Called from within unity
void __stdcall SetupAnswerEventCallback(AnswerEventCallback callback);
}

When I start my application I get a message in the development console:
“NotSupportedException: IL2CPP does not support marshaling delegates that point to instance methods to native code. The method we’re attempting to marshal is:ButtonHandlers: ShimCallback.”

Yeah, as the exception says, you need to make ShimCallback a static function. In addition, you also have to add [MonoPInvokeCallback] attribute on it.

Ok, got that side working, and if I use the callback from within the c# it works to add a string to my feedback object (which is a unity.ui.text).

From the c++ side, I am now calling the shim:
void MainPage::OnSendToUnityClick(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
// Validate unity is up and running as expected
if (UnityPlayer::AppCallbacks::Instance->IsInitialized())
{
// Unity interactions must occur on the App thread
auto dispatcher = CoreWindow::GetForCurrentThread()->Dispatcher;
ThreadPool::RunAsync(ref new WorkItemHandler(this, dispatcher
{
CallUnity();
}));
}
}

Which then does:
// This will be called from the desktop code as a hook to talk to unity
__declspec(dllexport) void __stdcall CallUnity()
{
CriticalSection::Lock lock(s_CriticalSection);
auto callback = s_AnswerEventCallback;
if (callback != nullptr)
{
callback(2112);
}
}

But it is then crashing down in the IL2CPP code (this works when called from the unity script):
VirtActionInvoker1< String_t* >::Invoke(75 /* System.Void UnityEngine.UI.Text::set_text(System.String) */, L_3, L_6);

SetupActivatedEventCallback(Exception thrown at 0x00007FF9EBD2D759 in NewUnityWebViewTest.exe: Microsoft C++ exception: Il2CppExceptionWrapper at memory location 0x000000F1D67CCAC0.
Unhandled exception at 0x00007FF96F58736C (ucrtbased.dll) in NewUnityWebViewTest.exe: An invalid parameter was passed to a function that considers invalid parameters fatal.

You’re calling into Unity’s API from the wrong thread. First of all, you shouldn’t do ThreadPool::RunAsync. You can just call it directly. Then on the Unity side, you should do

[MonoPInvokeCallback]
static void ShimCallback(int number)
{
    UnityEngine.WSA.Application.InvokeOnAppThread(() =>
    {
        s_Text.text = number.ToString();
    }, false);
}