0

I have a .py file I want to call and get the value it returns in my App.

My main intention is to use scikit-learn for machine learning within my C#-based Universal App, how would I accomplish that?

Salah Alshaal
  • 860
  • 2
  • 17
  • 41
  • 1
    What have you tried thus far? We kindly ask you to try and solve it on your own first before we try to give you help. – Torxed Jan 04 '16 at 18:22
  • @Torxed I have used Ironpython so far, but it did not include scikit-learn so I had to ditch that path – Salah Alshaal Jan 04 '16 at 18:27

3 Answers3

4

In UWP your options are pretty limited. Universal applications run in a sandbox and are restricted in how they can interact with classic desktop applications, such as Python, which prevents you from simply "shell-executing" your script using the local Python installation.

There was a way to do it in Windows 8.1 WinRT using brokered components, but the application needed to be sideloaded; it wouldn't work with applications installed from store. Of course, it required the desktop version of Windows and didn't work on Windows Phone, either. Also, I didn't see any mention of this in relation to Windows 10 UWP and I seriously doubt it still works.

Probably your best bet is to try the UWP version of CPython. I never used it, I don't know the state it is in and I have no idea, how much effort would be required to get scikit-learn working in it. But if you want your application to work on multiple platforms (desktop, mobile, IoT...), I don't know of another option.

On the other hand, if you're only targeting desktop, I would suggest you abandon the use of UWP and create a classic desktop application instead. This will allow you to use the standard CPython version without any restrictions, either by invoking the locally installed version or by embedding it into your app.

Damir Arh
  • 17,637
  • 2
  • 45
  • 83
  • Thank you for your response, I considered switching to WPF, but it does not include the SDK for Microsoft Band. – Salah Alshaal Jan 05 '16 at 06:46
  • I'm thinking of using Azure Machine Learning as it allows me to include Python scripts, what do you think? – Salah Alshaal Jan 05 '16 at 06:48
  • I'm not at all familiar with Microsoft Band SDK, but if it depends on UWP APIs, maybe you could use it with the help of [UWP for Desktop NuGet package](https://www.nuget.org/packages/UwpDesktop/). It allows you to access UWP APIs from desktop applications – Damir Arh Jan 05 '16 at 07:34
  • 1
    I have no experience with Azure Machine Learning, I only attended an introductory session about it once. It might make sense if it suits your needs, although it sound like a bit of an overkill. – Damir Arh Jan 05 '16 at 07:35
0

You could use a standalone-app proxy. As Damir Arh said, this approach can not be used to distribute your application via the store. The main idea is to register a custom extension for the proxy and launch it by executing a helper file.

Proxy:

static class Program
{
    const string ProxyExtension = ".python-proxy";
    const string ResultExtension = ".python-proxy-result";

    [STAThread]
    static void Main(params string[] args)
    {
        if (args.Length != 1)
            return;

        if ("install".Equals(args[0], StringComparison.Ordinal))
        {
            var path = Application.ExecutablePath;
            SetAssociation(ProxyExtension, "PythonProxy", path, "Python Proxy");
        }
        else
        {
            var path = args[0];
            if (!File.Exists(path))
                return;
            var serviceExt = Path.GetExtension(path);
            if (!ProxyExtension.Equals(serviceExt, StringComparison.OrdinalIgnoreCase))
                return;
            path = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path).TrimEnd());
            var ext = Path.GetExtension(path);
            if (!".py".Equals(ext, StringComparison.OrdinalIgnoreCase))
                return;

            var start = new ProcessStartInfo
            {
                FileName = "python.exe",
                Arguments = '"' + path + '"',
                UseShellExecute = false,
                RedirectStandardOutput = true,
                CreateNoWindow = true,
            };

            using (var process = Process.Start(start))
            {
                using (var reader = process.StandardOutput)
                {
                    var result = reader.ReadToEnd();
                    var output = path + ResultExtension;
                    using (var mutex = new Mutex(true, "PythonProxy Mutex"))
                    {
                        File.WriteAllText(output, result);
                    }
                }
            }
        }
    }

    public static void SetAssociation(string ext, string name, string openWithPath, string description)
    {
        using (var key = Registry.ClassesRoot.CreateSubKey(ext))
        {
            if (key == null)
                return;

            key.SetValue("", name);
        }

        using (var key = Registry.ClassesRoot.CreateSubKey(name))
        {
            if (key == null)
                return;

            key.SetValue("", description);

            using (var shellKey = key.CreateSubKey("shell"))
            {
                if (shellKey == null)
                    return;

                using (var openKey = shellKey.CreateSubKey("open"))
                {
                    if (openKey == null)
                        return;

                    using (var commandKey = openKey.CreateSubKey("command"))
                    {
                        if (commandKey == null)
                            return;

                        commandKey.SetValue("", $"\"{openWithPath}\" \"%1\"");
                    }
                }
            }
        }

        using (var key = Registry.CurrentUser.OpenSubKey($@"Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\{ext}"))
        {
            if (key != null)
                key.DeleteSubKey("UserChoice", false);
        }

        Native.SHChangeNotify(Native.SHCNE_ASSOCCHANGED, Native.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero);
    }

    class Native
    {
        public const uint SHCNE_ASSOCCHANGED = 0x08000000;
        public const uint SHCNF_IDLIST = 0x0000;

        [DllImport("shell32.dll")]
        public static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
    }
}

Universal Application:

public async void Fun()
{
    var local = ApplicationData.Current.LocalFolder;

    var scriptFile = await local.CreateFileAsync("script.py", CreationCollisionOption.ReplaceExisting);
    using (var stream = await scriptFile.OpenStreamForWriteAsync())
    using (var writer = new StreamWriter(stream))
    {
        await writer.WriteLineAsync(@"print ""Hello, World!""");
    }

    var proxyFile = await local.CreateFileAsync("script.py.python-proxy", CreationCollisionOption.ReplaceExisting);
    await Launcher.LaunchFileAsync(proxyFile);

    var resultPath = "script.py.python-proxy-result";
    var counter = 0;
    IStorageItem resultFile = null;
    while (resultFile == null)
    {
        if (counter != 0)
        {
            if (counter++ > 5)
                throw new Exception();
            await Task.Delay(250);
        }
        resultFile = await local.TryGetItemAsync(resultPath);
    }

    try
    {
        using (var mutex = new Mutex(true, "PythonProxy Mutex")) { }
    }
    catch (AbandonedMutexException) { }

    using (var stream = await local.OpenStreamForReadAsync(resultPath))
    using (var reader = new StreamReader(stream))
    {
        var content = await reader.ReadToEndAsync();
        var dialog = new MessageDialog(content);
        await dialog.ShowAsync();
    }

    await scriptFile.DeleteAsync();
    await proxyFile.DeleteAsync();
    await resultFile.DeleteAsync();
}
k.rode
  • 11
  • 1
-1

I believe if you use something like this ( How to print out the value that subprocess prints with C#? ) to run your python script and capture the stdout, you should be able to achieve the desired functionality

Community
  • 1
  • 1
Fin
  • 173
  • 1
  • 6