Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Shutting Down a Unity Player

Discussion in 'Windows' started by apanchavati, Dec 25, 2017.

  1. apanchavati

    apanchavati

    Joined:
    Oct 14, 2017
    Posts:
    2
    Hi,

    I'm currently trying to use Unity as a player to display a game object in a UWP AR application. The UWP application contains multiple XAML pages. One of these XAML pages contains the "Unity Game" encapsulated in a swapchainpanel. At some point the user may want to navigate away from the page containing the swapchainpanel. However, I find that when I do this, the game continues to run in the background.

    Is there anyway to shutdown the Unity Game once the user as navigated away from the page containing the swapchainpanel?

    Thank you in advance
     
  2. jcman

    jcman

    Joined:
    Jan 12, 2014
    Posts:
    15
    I would also like to know how to do this. I have AppCallbacks.Dispose(), but I get a null reference exception immediately after calling it. I also tried Application.Quit(), but that didn't work either. So for now I am just pausing with AppCallbacks.UnityPause(1), and then unpausing when the user navigates back to the page.

    I am on Unity 2017.3.0f3.

    This is the crash callstack, just in case someone knows what's going on:
    Exception thrown at 0x00007FF8AA7FBE4B (ntdll.dll) in UwpApp.exe: 0xC0000005: Access violation writing location 0x0000000000000024.

    > ntdll.dll!00007ff8aa7fbe4b() Unknown Non-user code. Cannot find or open the PDB file.
    ntdll.dll!00007ff8aa7d0c39() Unknown Non-user code. Cannot find or open the PDB file.
    ntdll.dll!00007ff8aa7d0b50() Unknown Non-user code. Cannot find or open the PDB file.
    UnityPlayer.dll!PlatformMutex::Lock(void) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!Mutex::Lock(void) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!UnityPlayer::AppCallbacks::Callbacks::pop(struct std::pair<class UnityPlayer::AppCallbackItem ^,void *> &) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!UnityPlayer::AppCallbacks::ExecuteQueuedCallbacks(void) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!UnityPlayer::AppCallbacks::_AppThreadImplementation(void *) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!UnityPlayer::AppCallbacks::_AppThread(void *) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!<lambda>(void)() Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!Platform::Details::__abi_FunctorCapture<class <lambda_e23ad135b05b73fd64a73b437f2a909e>,void,struct Windows::Foundation::IAsyncAction ^>::Invoke(struct Windows::Foundation::IAsyncAction ^) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!Windows::System::Threading::WorkItemHandler::Invoke(struct Windows::Foundation::IAsyncAction ^) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!Windows::System::Threading::WorkItemHandler::[Windows::System::Threading::WorkItemHandler::__abi_IDelegate]::__abi_Windows_System_Threading_WorkItemHandler___abi_IDelegate____abi_Invoke(struct Windows::Foundation::IAsyncAction ^) Unknown Non-user code. Symbols loaded.
    threadpoolwinrt.dll!00007ff88a0f3d1a() Unknown Non-user code. Cannot find or open the PDB file.
    threadpoolwinrt.dll!00007ff88a0f1ae2() Unknown Non-user code. Cannot find or open the PDB file.
    kernel32.dll!00007ff8a99c1fe4() Unknown Non-user code. Cannot find or open the PDB file.
    ntdll.dll!00007ff8aa81ef91() Unknown Non-user code. Cannot find or open the PDB file.
     
  3. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,507
    You could use Application.Unload for this purpose. Note, that API hasn't been around for long and isn't used much, so it might contain some rough edges.

    @jcman, when does that crash happen?
     
  4. jcman

    jcman

    Joined:
    Jan 12, 2014
    Posts:
    15
    I did Application.Unload as well as Application.Quit. I should have been clearer above - neither of these has a problem directly. I just can't figure out how to restart Unity without restarting my app (I want to be able to start/shutdown Unity when navigating between pages in my app).

    I assume to restart Unity I should call Application.Unload() or Quit(), then AppCallbacks.Dispose(), then go through the usual startup flow again. However, calling AppCallbacks.Dispose() causes the crash with the callstack from the previous email, even if I Quit() or Unload() the Unity Application first.

    I also tried to reuse my AppCallbacks object, but you can't call InitializeD3DXaml multiple times. You also cannot create a new AppCallbacks without deleting the first (it throws an exception in the management of the AppCallbacks singleton if I recall).
     
  5. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,507
    Don't dispose AppCallbacks. Just set it to null. Then, when you want to restart the app, create a new one and initialize as usual. Here's sample code from our automated tests that test Application.Unload:

    Script in Unity attached to an object:

    Code (csharp):
    1. public class TestUnloadingScript : MonoBehaviour
    2. {
    3.    int i = 0;
    4.  
    5.    public void Start()
    6.    {
    7.        Debug.Log("Inside TestUnloadingScript.Start()");
    8.    }
    9.  
    10.    public void Update()
    11.    {
    12.        if (i++ == 5)
    13.        {
    14.            Debug.Log("Unloading engine");
    15.            Application.Unload();
    16.        }
    17.    }
    18. }
    Code in exported VS project (IL2CPP scripting backend):

    App.h:

    Code (csharp):
    1. #pragma once
    2.  
    3. namespace UnloadApp
    4. {
    5.     ref class App sealed :
    6.         public Windows::ApplicationModel::Core::IFrameworkView,
    7.         public Windows::ApplicationModel::Core::IFrameworkViewSource
    8.     {
    9.     public:
    10.         App();
    11.         virtual void Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView);
    12.         virtual void SetWindow(Windows::UI::Core::CoreWindow^ window);
    13.         virtual void Load(Platform::String^ entryPoint);
    14.         virtual void Run();
    15.         virtual void Uninitialize();
    16.  
    17.         void RunUnityPlayer();
    18.  
    19.         virtual Windows::ApplicationModel::Core::IFrameworkView^ CreateView();
    20.  
    21.     private:
    22.         Windows::UI::Core::CoreWindow^ m_CoreWindow;
    23.         Windows::ApplicationModel::Core::CoreApplicationView^ m_ApplicationView;
    24.  
    25.         void OnActivated(Windows::ApplicationModel::Core::CoreApplicationView^ sender, Windows::ApplicationModel::Activation::IActivatedEventArgs^ args);
    26.         void SetupOrientation();
    27.     };
    28. }
    App.cpp:

    Code (csharp):
    1.  
    2. #include "pch.h"
    3. #include "App.h"
    4. #include "UnityGenerated.h"
    5. #include <iostream>
    6.  
    7. using namespace UnloadApp;
    8. using namespace Platform;
    9. using namespace UnityPlayer;
    10. using namespace Windows::ApplicationModel::Activation;
    11. using namespace Windows::ApplicationModel::Core;
    12. using namespace Windows::Foundation;
    13. using namespace Windows::UI::Core;
    14. using namespace Windows::UI::ViewManagement;
    15.  
    16. #define TEST_CHECK(condition, msg) do { if (!(condition)) { std::cout << "Test assertion failed: " << msg << std::endl; if (IsDebuggerPresent()) { __debugbreak(); } } } while (false)
    17.  
    18. App::App()
    19. {
    20.     SetupOrientation();
    21. }
    22.  
    23. void App::Initialize(CoreApplicationView^ applicationView)
    24. {
    25.     m_ApplicationView = applicationView;
    26.     applicationView->Activated += ref new TypedEventHandler<CoreApplicationView^, IActivatedEventArgs^>(this, &App::OnActivated);
    27. }
    28.  
    29. void App::SetWindow(CoreWindow^ window)
    30. {
    31.     m_CoreWindow = window;
    32. }
    33.  
    34. void App::Load(String^ entryPoint)
    35. {
    36. }
    37.  
    38. static HMODULE GetLoadedModuleHandle(const wchar_t* name)
    39. {
    40.     return GetModuleHandleW(name);
    41. }
    42.  
    43. void App::RunUnityPlayer()
    44. {
    45.     auto appCallbacks = ref new AppCallbacks();
    46.     appCallbacks->SetCoreApplicationViewEvents(m_ApplicationView);
    47.     appCallbacks->SetCoreWindowEvents(m_CoreWindow);
    48.     appCallbacks->InitializeD3DWindow();
    49.     appCallbacks->Run();
    50. }
    51.  
    52. void App::Run()
    53. {
    54.     // We run two loops: first, unloading the engine without unloading the player DLL, and then unloading the player dll
    55.     // We need to make sure engine can be unloaded and reloaded even without unloading the player dll as in some cases it might not get unloaded
    56.     //
    57.     // Nit: we are cheating with std::cout. On first startup, the engine will open our stdout handle to write to our player log
    58.     // and it never closes it (nor we'd want it to). Furthermore, on UWP our test framework doesn't use player connection and
    59.     // instead reads from the log file directly, so we can tell the test framework what's happening just by writing to stdout
    60.     // through std::cout, and we can read it from test framework by using "process.Log.Expect".
    61.  
    62.     auto unityPlayer = LoadPackagedLibrary(L"UnityPlayer.dll", 0);
    63.  
    64.     for (int i = 0; i < 5; i++)
    65.     {
    66.         RunUnityPlayer();
    67.         std::cout << "Unloading engine succeeded." << std::endl;
    68.     }
    69.  
    70.     FreeLibrary(unityPlayer);
    71.     unityPlayer = nullptr;
    72.  
    73.     for (int i = 0; i < 5; i++)
    74.     {
    75.         RunUnityPlayer();
    76.  
    77.         // Pump UI events so all of our threads waiting on UI thread get a chance to exit
    78.         LARGE_INTEGER frequency, start, current;
    79.         QueryPerformanceFrequency(&frequency);
    80.         QueryPerformanceCounter(&start);
    81.         current = start;
    82.  
    83.         do
    84.         {
    85.             Windows::UI::Core::CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(Windows::UI::Core::CoreProcessEventsOption::ProcessAllIfPresent);
    86.             QueryPerformanceCounter(&current);
    87.         }
    88.         while (static_cast<double>(current.QuadPart - start.QuadPart) / static_cast<double>(frequency.QuadPart) < 3.0);
    89.  
    90.         typedef HRESULT (__stdcall *DllCanUnloadNowFunc)();
    91.  
    92.         unityPlayer = GetLoadedModuleHandle(L"UnityPlayer.dll");
    93.         auto dllCanUnloadNow = reinterpret_cast<DllCanUnloadNowFunc>(GetProcAddress(unityPlayer, "DllCanUnloadNow"));
    94.         auto canUnloadNow = dllCanUnloadNow();
    95.         TEST_CHECK(canUnloadNow == S_OK, "UnityPlayer.dll!DllCanUnloadNow returned false, which means we leaked at least one COM object.");
    96.  
    97.         CoFreeUnusedLibrariesEx(0, 0);
    98.  
    99.         unityPlayer = GetLoadedModuleHandle(L"UnityPlayer.dll");
    100.         TEST_CHECK(unityPlayer == nullptr, "UnityPlayer.dll could not be unloaded for some reason.");
    101.  
    102.         std::cout << "Unloading engine succeeded." << std::endl;
    103.     }
    104.  
    105.     std::cout << "Test is done." << std::endl;
    106. }
    107.  
    108. void App::Uninitialize()
    109. {
    110. }
    111.  
    112. IFrameworkView^ App::CreateView()
    113. {
    114.     return this;
    115. }
    116.  
    117. void App::OnActivated(CoreApplicationView^ /*sender*/, IActivatedEventArgs^ /*args*/)
    118. {
    119.     m_CoreWindow->Activate();
    120. }
    121.  
    122. void App::SetupOrientation()
    123. {
    124.     Unity::SetupDisplay();
    125. }
    Code in exported VS project (.NET scripting backend) - App.cs:

    Code (csharp):
    1. using System;
    2. using Windows.ApplicationModel;
    3. using Windows.ApplicationModel.Activation;
    4. using Windows.ApplicationModel.Core;
    5. using Windows.UI.Core;
    6. using Windows.UI.ViewManagement;
    7. using UnityPlayer;
    8. using Windows.System;
    9.  
    10. namespace UnloadApp
    11. {
    12.     class App : IFrameworkView, IFrameworkViewSource
    13.     {
    14.         private WinRTBridge.WinRTBridge m_Bridge;
    15.         private CoreApplicationView m_ApplicationView;
    16.         private CoreWindow m_Window;
    17.  
    18.         public App()
    19.         {
    20.             SetupOrientation();
    21.  
    22.             m_Bridge = new WinRTBridge.WinRTBridge();
    23.         }
    24.  
    25.         public virtual void Initialize(CoreApplicationView applicationView)
    26.         {
    27.             m_ApplicationView = applicationView;
    28.             applicationView.Activated += ApplicationView_Activated;
    29.         }
    30.  
    31.         private void ApplicationView_Activated(CoreApplicationView sender, IActivatedEventArgs args)
    32.         {
    33.             CoreWindow.GetForCurrentThread().Activate();
    34.         }
    35.  
    36.         public void SetWindow(CoreWindow coreWindow)
    37.         {
    38.             m_Window = coreWindow;
    39.         }
    40.  
    41.         public void Load(string entryPoint)
    42.         {
    43.         }
    44.  
    45.         public void Run()
    46.         {
    47.             // Run 5 times
    48.             for (int i = 0; i < 5; i++)
    49.             {
    50.                 var appCallbacks = new AppCallbacks();
    51.                 appCallbacks.SetBridge(m_Bridge);
    52.                 appCallbacks.SetCoreApplicationViewEvents(m_ApplicationView);
    53.                 appCallbacks.SetCoreWindowEvents(m_Window);
    54.                 appCallbacks.InitializeD3DWindow();
    55.                 appCallbacks.Run();
    56.             }
    57.         }
    58.  
    59.         public void Uninitialize()
    60.         {
    61.         }
    62.  
    63.         [MTAThread]
    64.         static void Main(string[] args)
    65.         {
    66.             var app = new App();
    67.             CoreApplication.Run(app);
    68.         }
    69.  
    70.         public IFrameworkView CreateView()
    71.         {
    72.             return this;
    73.         }
    74.  
    75.         private void SetupOrientation()
    76.         {
    77.             Unity.UnityGenerated.SetupDisplay();
    78.         }
    79.     }
    80. }
     
    jcman likes this.
  6. jcman

    jcman

    Joined:
    Jan 12, 2014
    Posts:
    15
    Thanks for the detailed response - much appreciated.

    I'm running a XAML app and all in C#, rather than a C++ Win app so I have a couple differences. My startup code now looks like this:

    Code (csharp):
    1.  
    2.     m_swapChainPanel = new SwapChainPanel();
    3.     m_callbacks = new AppCallbacks();
    4.     m_callbacks.SetBridge(m_bridge);
    5.     m_callbacks.SetSwapChainPanel(m_swapChainPanel);
    6.     m_callbacks.SetCoreWindowEvents(coreWindow);
    7.     m_callbacks.InitializeD3DXAML();
    8.  
    I notice that you have a call to SetCoreApplicationViewEvents that I am missing, and I can't see how to get the CoreApplicationView in a C# XAML app - I assume that SetSwapChainPanel covers what is required?

    My shutdown code is:

    Code (csharp):
    1.  
    2.     // On Unity Thread, run async with InvokeOnAppThread and awaited...
    3.     UnityEngine.Application.Unload();
    4.  
    5.     // Then continuing on UI thread with
    6.     m_swapChainPanel = null;
    7.     m_callbacks = null;
    8.  
    So, I reuse the WinRTBridge, and re-create the SwapChainPanel and the AppCallbacks.

    Anyway, the Unload succeeds - I get some messages about no leaked weakptrs etc. But when I try to recreate the AppCallbacks I get an exception when the singleton disposes the old one (see below). Incidentally, I am now re-creating the swap chain panel, which I wasn't before. If I reuse the swap chain panel, I get something much like the original callstack, except when the AppCallbacks singleton disposes the old one.

    Thanks again!

    Here's the new output and callstack:

    Exception thrown at 0x144DBD39 (UnityPlayer.dll) in UwpApp.exe: 0xC0000005: Access violation reading location 0x2261F744.

    UnityPlayer.dll!std::_Destroy_range<struct std::_Wrap_alloc<class std::allocator<struct InputBuffer::pointerInput> > >(struct InputBuffer::pointerInput *,struct InputBuffer::pointerInput *,struct std::_Wrap_alloc<class std::allocator<struct InputBuffer::pointerInput> > &) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!std::vector<struct InputBuffer::pointerInput,class std::allocator<struct InputBuffer::pointerInput> >::_Tidy(void) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!UnityPlayer::AppCallbacks::~AppCallbacks(void) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!UnityPlayer::AppCallbacks::[Platform::IDisposable]::__abi_Platform_IDisposable____abi_<Dispose>(void) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!UnityPlayer::AppCallbacks::[Platform::Object]::__abi_Release(void) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!__abi_winrt_ptr_assign(void * *,class Platform::Object const volatile ^) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!UnityPlayer::AppCallbacks::AppCallbacks(void) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!UnityPlayer::__AppCallbacksActivationFactory::[Platform::Details::IActivationFactory]::ActivateInstance(void) Unknown Non-user code. Symbols loaded.
    UnityPlayer.dll!UnityPlayer::__AppCallbacksActivationFactory::[Platform::Details::IActivationFactory]::__abi_Platform_Details_IActivationFactory____abi_ActivateInstance(class Platform::Object ^ *) Unknown Non-user code. Symbols loaded.
    [Managed to Native Transition] Annotated Frame
    > UwpApp.exe!UwpApp.Unity.UnityApp.InitializeUnityInternalAsync(Windows.UI.Core.CoreWindow coreWindow) Line 211 C# Symbols loaded.
    UwpApp.exe!UwpApp.Unity.UnityApp.InitializeUnityAsync(Windows.UI.Core.CoreWindow coreWindow) Line 120 C# Symbols loaded.
    UwpApp.exe!UwpApp.Unity.Control.UnityPlayerControl.StartUnityAsync(Windows.UI.Core.CoreWindow coreWindow) Line 37 C# Symbols loaded.
    UwpApp.exe!UwpApp.MainPage.StartUnity() Line 59 C# Symbols loaded.
    UwpApp.exe!UwpApp.MainPage.UnityPlayerButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) Line 115 C# Symbols loaded.
    [Native to Managed Transition] Annotated Frame
    Windows.UI.Xaml.dll!06d18913() Unknown Non-user code. Cannot find or open the PDB file.
    [Frames below may be incorrect and/or missing, no symbols loaded for Windows.UI.Xaml.dll] Annotated Frame
    Windows.UI.Xaml.dll!06dc13f2() Unknown Non-user code. Cannot find or open the PDB file.
    Windows.UI.Xaml.dll!06dc3cab() Unknown Non-user code. Cannot find or open the PDB file.
    ...
     
    Last edited: Jan 3, 2018
  7. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,507
    Which Unity version are you on? Are you experimenting on your whole project or an empty Unity project?
     
  8. jcman

    jcman

    Joined:
    Jan 12, 2014
    Posts:
    15
    That was with Unity 2017.2.0p2-MRTP5 (required for Windows Mixed Reality headset support). The Unity App is almost empty except a single Behaviour with some functions to switch between Desktop and VR mode (not called in the tests above), and a simple scene with some primitives in it.

    Oh. I just tried with 2017.3.0f3 and now it works. So, you were right about just not disposing the AppCallbacks. Funnily enough I was using that version until a few hours ago when I added MR headset support. Which was just before I read your response and changed my shutdown code. Sigh.

    A major point of what I'm doing is to be able to launch in VR. I assume when the MRTP build switches to 2017.3 it'll all be fixed.

    Thanks for all the help!
     
  9. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,507
    Yeah, unloading has been broken before 2017.3.

    AFAIK 2017.3 should work for Windows mixed reality. They had special builds for 2017.2 because they weren't able to add support in time to ship in the official Unity version.
     
  10. jcman

    jcman

    Joined:
    Jan 12, 2014
    Posts:
    15
    Sadly not for me :( With 2017.3.0f3 switching to the WindowsMR device causses a null dereference here:

    > UnityPlayer.dll!HolographicRenderManager::TryGetMainHolographicCamera(void)
    UnityPlayer.dll!VRDeviceHoloLens::GetHoloLensPose(void)
    UnityPlayer.dll!VRDeviceHoloLens::UpdateAllCameraInformation(void)
    ...

    Anyway, that's a separate problem. I will stick with just pausing Unity and go to shutting it down again when all the fixes make it into the same build. Thanks!