Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

UWP command line arguments

Discussion in 'Windows Mixed Reality' started by MerkSim, Jul 11, 2018.

  1. MerkSim

    MerkSim

    Joined:
    Mar 1, 2018
    Posts:
    12
    Hello,

    I am wondering if there is a way to modify the generated project when building a UWP app to:
    -call it easily through command line
    -pass custom arguments that can be used in the default loaded scene (i.e. set the text of a UI element to be the first argument)

    I 've managed to call my app through the CMD with an Alias, but I cannot parse any extra arguments after that and pass then to Unity. I 'm using Environment.GetCommandLineArgs(); in my scene, but I don't think that is the right way, especially when trying to pass the arguments through the UWP project from CMD.

    Any suggestions?
    Thanks
     
  2. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680
    UWP apps don't support "traditional" command line arguments like regular win32 apps do as they don't get invoked directly, but rather "Activated" through special Windows activation APIs.

    In Unity, we "fake" command line arguments by allowing the app to pass them to "AppCallbacks" constructor. What you could do is modify your app to read these arguments from a file before constructing AppCallbacks, and then also write a script that writes them out to that file next to the executable before launching the app. That's how most of our tests internally are setup.
     
  3. seb_krueger

    seb_krueger

    Joined:
    Jan 4, 2019
    Posts:
    17
    But how can you use the values set via the AppCallbacks? How can the developer access them in a Unity Script?

    Thanks
     
  4. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680
    seb_krueger likes this.
  5. seb_krueger

    seb_krueger

    Joined:
    Jan 4, 2019
    Posts:
    17
  6. seb_krueger

    seb_krueger

    Joined:
    Jan 4, 2019
    Posts:
    17
    Though, might there be an issue with quoted strings in the arguments string? Something like this arrives with backslashes in front of the double quotes in the Environemen.GetCommandLineArgs:

    Code (CSharp):
    1. Platform::String^ cmdLineArgumentents = LR"(-File "E:\projects\Unity\deploy\build\bin\x64\Debug\AppX\Data\file.txt")";
     
  7. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680
    I don't think there are any issues like that with it. AppCallbacks takes an array of arguments, and Environment.GetCommandLineArgs gives you back exactly what you passed to AppCallbacks constructor. It never procecsses or converts them to a single command line.
     
    seb_krueger likes this.
  8. seb_krueger

    seb_krueger

    Joined:
    Jan 4, 2019
    Posts:
    17
    I tested it with a single argument string:

    Code (CSharp):
    1. Platform::String^ cmdLineArgumentents = LR"(-File "E:\projects\Unity\deploy\build\bin\x64\Debug\AppX\Data\file.txt")";
    Environment.GetCommandLineArgs contained this:

    Code (CSharp):
    1. -File \"E:\projects\Unity\deploy\build\bin\x64\Debug\AppX\Data\file.txt\"
    And I wonder wether the leading back slashes in front of the double quotes are correct and if not, what the reason is, that we see it here.
     
  9. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680
    How did you pass the arguments to AppCallbacks constructor?
     
  10. seb_krueger

    seb_krueger

    Joined:
    Jan 4, 2019
    Posts:
    17
  11. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680
    It works for me:

    Code (csharp):
    1. m_AppCallbacks = ref new AppCallbacks(ref new Platform::Array<Platform::String^>
    2. {
    3.     L"-File",
    4.     LR"(F:\projects\UnityProjects\New Unity Project (157)\bin\build\bin\x64\Debug\AppX\Data\file.txt)"
    5. });
    The resulting Environment.GetCommandLineArgs contained an array of 2 elements and I was able to read the file successfully using the path from the second argument.
     
    Agredek and seb_krueger like this.
  12. seb_krueger

    seb_krueger

    Joined:
    Jan 4, 2019
    Posts:
    17

    I'll try that! Thanks a lot!
     
  13. mikewarren

    mikewarren

    Joined:
    Apr 21, 2014
    Posts:
    90
    So, I must be missing something.
    • Isn't the App.cpp file auto generated and therefore going to get overwritten when the player is built from within the Editor? If I customize it, aren't I going to lose those changes.
    • This looks like the arguments are hard coded into the AppCallbacks constructor and can't be changed at run time anyway. So, the solution here is to hard code a path to a file and read the arguments from there?
      • Why would I use this approach over just processing a setup file in a monobehaviour? (Assuming I'm not using any default player parameters that are processed by the unity engine.)
    Mike
     
    Last edited: Oct 9, 2019
  14. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680
    Unity doesn't overwrite your custom changes of App.cpp, the manifest file or the project files. So if you make changes to it, commit it to your source control and always build the project from Unity on top of it, you should be fine.

    Generally, command line args are only useful at development time because you can't set them when launching UWP app from the start menu. There's no reason to pass arguments to AppCallbacks constructor if you can control how they are consumed. They are only useful when you want to change engine behaviour or use a 3rd party library that uses Environment.GetCommandLineArgs().
     
  15. mikewarren

    mikewarren

    Joined:
    Apr 21, 2014
    Posts:
    90
    Thank you. I agree with your statements in part. I'm working on a general purpose application that can be configured at runtime to plot user specific data in 3D. I use command line parameters to...
    1. Load a configuration file (specified on the command line) that contains JSON settings to data sources and plot configurations.
    2. Force the engine to use a particular vrmode (or none) from the standard engine cmd line parameters.
    I'm not very familiar with the UWP development, so if you have suggestion, I'd appreciate them.

    My best ideas are
    1. Create a new file type for my plot configuration files and make a file association so that the app will launch automatically. Haven't figured out how to get the file selected. (Is this a protocol launch?)
    2. Create an application setup file that will have to be modified manual to indicate the VR mode, and will reside in a known location. This, I think, is what this thread is suggesting.
    Thanks for the tips.
     
  16. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680
    How do you envision your app getting launched? Why not build a UI that let your specify those parameters or select a configuration file on disk?
     
  17. mikewarren

    mikewarren

    Joined:
    Apr 21, 2014
    Posts:
    90
    The same app is going to be used on a variety of data sets. For "canned" visualizations where an analyst has already pre-configured what he wants to see, it's usually launched from a batch file or button on menu that has been configured to launch the application with a particular configuration file.

    During development, the analyst will run a data set against a variety of configurations (in desktop, a HMD, and in a CAVE (stereo VR), mode). It's just easier to pass cmd line parameters or create batch files to do that.

    I'm not against an interactive UI. It just takes longer for the user to load the visualization and is more difficult to automate. And, I know it's changing, but Unity's UI support just doesn't work very well in VR yet.

    I think the best compromise I'm finding is to associate my app with a new file type for the visualization configuration files and given them a unique extension. I can configure Unity to launch the app on that type, but I don't know how to get the file that caused the launch from within a Monobehavior yet.
     
  18. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680
    Try this: https://forum.unity.com/threads/how...-xaml-file-in-uwp-il2cpp.558445/#post-3702604
     
  19. mikewarren

    mikewarren

    Joined:
    Apr 21, 2014
    Posts:
    90
    Thanks. Probably won't get to it until next week, but I'll take a look.
     
  20. mikewarren

    mikewarren

    Joined:
    Apr 21, 2014
    Posts:
    90
    Finally got a chance to take a look at the code you posted. Didn't get it working, but did realize that on file activation, the file is contained WSA.Applicaiton.argument member.

    https://docs.unity3d.com/ScriptReference/WSA.Application-arguments.html

    However, I'm getting a directory not found exception on a path that is perfectly valid.

    DirectoryNotFoundException: Could not find a part of the path "C:\Users\mew9\Desktop\Visualization\Config.kpl".
    at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x00000] in <00000000000000000000000000000000>:0
    at System.IO.StreamReader..ctor (System.String path, System.Text.Encoding encoding, System.Boolean detectEncodingFromByteOrderMarks, System.Int32 bufferSize, System.Boolean checkHost) [0x00000] in <00000000000000000000000000000000>:0
    at System.IO.StreamReader..ctor (System.String path) [0x00000] in <00000000000000000000000000000000>:0
    at System.IO.File.ReadAllText (System.String path) [0x00000] in <00000000000000000000000000000000>:0

    Thought it might be this issue.
    https://issuetracker.unity3d.com/issues/uwp-il2cpp-file-dot-exists-method-returns-false

    So, I enabled the app's file system privledge, and I added broadFileSystemAccess to the manifest per this article
    https://docs.microsoft.com/en-us/windows/uwp/files/file-access-permissions
    (The editor is complaining about Capability being an invalid element in the restricted capabilities namespace.)

    If it's a security problem, I'm surprised by the DirectoryNotFound exception. Can I use the FileStream class in UWP if the app has the proper permissions?
     
  21. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680
    It gets DirectoryNotFoundException because it doesn't know if directory exists or not.

    Generally, you should solve this problem by adding the supplied StorageFile/StorageFolder to FutureAccessList. Once you do that, you can then access it using System.IO classes.

    broadFileSystemAccess file access is complicated because not only it is a 'restricted' capability, it also doesn't work out of the box: the user needs to go to system settings and specifically grant your app permissions to access their file system.
     
  22. mikewarren

    mikewarren

    Joined:
    Apr 21, 2014
    Posts:
    90
    I'd rather do it the right way, so thank you for pointing me to the FutureAccessList. It seems strange to me that an application would be able to give itself access to a file/folder this way. And, when I tried to add access to the Application.arguments file, I got an access violation anyway. (Maybe I need to add all the folders in the path as well?)

    The examples I see all use a picker, but I'd really want the user to be able to just double click on a file and go.


    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;

    using UnityEngine;

    #if ENABLE_WINMD_SUPPORT && UNITY_WSA
    using Windows.Storage;
    using Windows.Storage.AccessCache;
    #endif

    using Kraken;

    public class UPWLoadTest : MonoBehaviour
    {
    // Start is called before the first frame update
    void Start()
    {
    }

    private void Update()
    {
    if (Input.GetKeyDown(KeyCode.Space))
    Load();
    }

    // Start is called before the first frame update
    async void Load()
    {
    #if ENABLE_WINMD_SUPPORT && UNITY_WSA
    Debug.Log( UnityEngine.WSA.Application.arguments );

    string prefix = "File=";
    string wsaArgs = UnityEngine.WSA.Application.arguments.Trim();
    if ( wsaArgs.Length > 0 && wsaArgs.StartsWith(prefix) )
    {
    string path = wsaArgs.Substring(prefix.Length);
    StorageFile file = await StorageFile.GetFileFromPathAsync(path);
    StorageApplicationPermissions.FutureAccessList.Add( file );

    Debug.Log("Loading: " + path );
    string text = File.ReadAllText(path);

    Debug.Log("text: " + text );
    }
    #endif
    }
    }

     
  23. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680
  24. mikewarren

    mikewarren

    Joined:
    Apr 21, 2014
    Posts:
    90
    Ok. But, I don't have access to the StorageFile object from the ActivationArgs on the C# side, right?
    It doesn't exactly solve my problem anyway. The config file from the file activation is going to contain paths to data files for loading. I'm trying hard to avoid the file/folder pickers as the app will be run in VR CAVEs and HMDs. And, the data and config files are auto generated for visualization (as often as daily). If I add a StorageFolder to the FutureAccessList, will the contents and subfolders also be authorized? And, future additions to the folder? It would be acceptable to authorize a folder once using the picker strategy.

    The other option I stumbled upon here is to add the ALL_APPLICATIONS_PACKAGES group to the folder(s) where data is stored. It's more work to set up, but looks like it might be more convenient long term.

    https://www.mr-kg.com/uwp-file-access/

    Appreciate the advice. My entire UWP experience is with Unity, so I'm out of my element.
     
  25. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680
    I *think* you might be able to subscribe to that event inside Awake() function. If that doesn't work, you may need to modify generated VS project.

    I think that would work.
     
  26. mikewarren

    mikewarren

    Joined:
    Apr 21, 2014
    Posts:
    90
    I hate to ask, but is there a Unity/UWP sample around that shows how to access local files (with or without the FutureAccessList)? I get a COM exception just trying to open the FileOpenPicker. (Wrong thread, maybe?)

    Update:
    https://forum.unity.com/threads/how-to-show-native-file-picker.381528/

    Update:
    Nope. Still getting a COM exception.


    async void Start()
    {
    #if WINDOWS_UWP
    UnityEngine.WSA.Application.InvokeOnUIThread( OpenPicker, true);
    #endif
    }

    #if WINDOWS_UWP
    async static private void OpenPicker()
    {
    Debug.Log("creating file picker");
    var filePicker = new FileOpenPicker();
    Debug.Log("picking single file");
    StorageFile file = await filePicker.PickSingleFileAsync();
    Debug.Log("file pick done");
    }
    #endif
     
    Last edited: Nov 19, 2019 at 4:08 PM
  27. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    6,680