Search Unity

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

Discussion in 'Windows' started by DavidShoe, Mar 23, 2021.

  1. DavidShoe

    DavidShoe

    Joined:
    Mar 26, 2017
    Posts:
    7
    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:

    Code (CSharp):
    1. namespace UnityXAML
    2. {
    3.     public delegate void UnityEvent(object arg);
    4.     public sealed class Communications
    5.     {
    6.         /// Called to send a message to the unity components
    7.         public static void SendMessageToUnity(string msg)
    8.         {
    9.             UnityEngine.GameObject gameObject = UnityEngine.GameObject.Find("Camera");
    10.             if (gameObject != null)
    11.             {
    12.                 gameObject.GetComponent<ButtonHandlers>().ShowFeedback(msg);
    13.             }
    14.             else
    15.             {
    16.                 throw new Exception("Camera not found, have you exported the correct scene?");
    17.             }
    18.         }
    19.  
    20.         /// Connects the event delegates to be able to receive events from unity.
    21.         public static void SetEventCallback(UnityEvent e)
    22.         {
    23.             UnityEngine.GameObject gameObject = UnityEngine.GameObject.Find("Camera");
    24.             if (gameObject != null)
    25.             {
    26.                 var bh = gameObject.GetComponent<ButtonHandlers>();
    27.                 if (bh != null)
    28.                 {
    29.                     bh.onEvent = new ButtonHandlers.OnEvent(e);
    30.                 }
    31.             }
    32.             else
    33.             {
    34.                 throw new Exception("Camera not found, have you exported the correct scene?");
    35.             }
    36.         }
    37.     }
    38. }
    39.  
     
  2. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    Hey I made a sample project here: https://github.com/TautvydasZilys/u...er/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/u.../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.
     
  3. DavidShoe

    DavidShoe

    Joined:
    Mar 26, 2017
    Posts:
    7
    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).
     
  4. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    Just forward declare it:

    Code (csharp):
    1. __declspec(dllimport) void DoStuff(int param);
    Alternatively, you can put the method declaration in a .h file and include it from here.
     
  5. DavidShoe

    DavidShoe

    Joined:
    Mar 26, 2017
    Posts:
    7
    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:

    1. Store a static GameObject pointer in the shim file? Where do I get the pointer definition?
    2. On unity startup tell the shim file "here is the button", which is then stored in the shim
    3. From xaml call the shim UpdateButtonText(string foo)
    4. Use the object pointer and manipulate the text (how would I get access to the internals of that button).
    Or do I need to
    1. Store a callback in the shim with a string parameter
    2. On unity startup register a callback from one of my c# scripts that takes a string param
    3. From xaml call the shim UpdateButtonText(string foo)
    4. Use the stored callback to forward the string to the c# which can then manipulate the gameobject
     
  6. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    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.
     
  7. DavidShoe

    DavidShoe

    Joined:
    Mar 26, 2017
    Posts:
    7
    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."
     
  8. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    Yeah, as the exception says, you need to make ShimCallback a static function. In addition, you also have to add [MonoPInvokeCallback] attribute on it.
     
  9. DavidShoe

    DavidShoe

    Joined:
    Mar 26, 2017
    Posts:
    7
    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](IAsyncAction^)
    {
    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.
     
  10. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    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
    Code (csharp):
    1. [MonoPInvokeCallback]
    2. static void ShimCallback(int number)
    3. {
    4.     UnityEngine.WSA.Application.InvokeOnAppThread(() =>
    5.     {
    6.         s_Text.text = number.ToString();
    7.     }, false);
    8. }