G_subprocess_new() behavior on windows

Dear all,
I recently faced the requirement to write a piece of code to test OpenGL options before adapting the rendering environment for my main program.
Thanks to the help of @ebassi it worked out nicely … for Linux … I had no chance to test this out for macOS but I am confident that it will also work properly (will check that tomorrow actually).

Today however I tried to test the new windows version of my program (build using meson on msys2), installed it and faced the following issue:

  1. No way to launch the program when I double click on the desktop shortcut created for that purpose:

What you see there is the console output:

Prefix= C:\Program Files\Atomes
proc_name = C:\Program Files\Atomes\bin\atomes_startup_testing.exe

Where Prefix is the installation directory of my program.
And proc_name is the correct name of the program that should be launch but somehow is not found and the call fails, but the binary is indeed in the proper place.

  1. But then when I try to run the same program via msys2 no problem at all:

Exact same information in the back, but different result … any idea what is going on ?
Just because most often people launch windows application via shortcuts not msys2 :wink:

Hi,

The launched executable seems not found in PATH…

Instead of using GSubprocess directly, I recommend GSubprocessLauncher instead, in that sequence:

  • g_subprocess_launcher_new()
  • g_subprocess_launcher_set_cwd() → pass “C:\Program Files\Atomes\bin” here
  • g_subprocess_launcher_spawn()

The “spawn()” returns a GSubprocess, you can use “g_subprocess_wait()” on it like you already do.

The advantage of GSubprocessLauncher is that you can control the environment of the subprocess, including its current working directory. I used it on Windows too (from PyGObject), it worked great!

Indeed @gwillems the launched executable seems not found in PATH,
in the case of the shortcut launch only !

I tried your method but it does not change the outcome:

In that screen capture:
proc_dir is the directory passed as argument to g_subprocess_launcher_set_cwd
proc_name is the name of the binary to launch passed as argument to g_subprocess_launcher_spawn

And again I can start the application successfully if I use MSYS2 (see above)
and even if the directory is not in the PATH environment variable.
I am afraid that this is (once more) out of my league …

Can you please print what these functions return?

g_get_current_dir ()
g_find_program_in_path ("atomes_startup_testing.exe")
g_canonicalize_filename ("atomes_startup_testing.exe", NULL)

Thanks again for helping out,
here is what I found:

The thing is that I print the result of g_get_current_dir() after changing directory to proc_dir using g_subprocess_launcher_set_cwd but I guess current location and where to run subprocesses are not similar …

When I edit the properties of the shortcut, to start in C:\Program Files\Atomes\bin (instead of the default C:\Program Files\Atomes)

then the path seems corrects, however it cannot find atomes_startup_testing.exe anyway:

And again the result is normal when launching the command via MSYS2, here is the corresponding (and successful) outpout:

$ /c/Program\ Files/Atomes/bin/atomes
Prefix= C:\Program Files\Atomes
proc_dir= C:\Program Files\Atomes\bin
Current_directory= C:\msys64\home\leroux\Atomes-meson
Find_prog_in_path= C:\Program Files\Atomes\bin\atomes_startup_testing.exe
Canonicalize_file= C:\msys64\home\leroux\Atomes-meson\atomes_startup_testing.exe

Does something like this work?

g_subprocess_launcher_spawn (g_find_program_in_path ("atomes_startup_testing.exe"));

By the way, what do you pass to g_subprocess_launcher_set_cwd (...) ?
Is it a hardcoded path, or (better) do you dynamically detect the location of “atomes.exe” to use the path as cwd?

Nope it does not work,
What I pass to g_subprocess_launcher_set_cwd() is printed on the lines:
proc_dir= in what you can see above.

Really weird…

I just tried with a small python script, I can launch exes from random places (not even in cwd or PATH) as long as I give the full path:

sl = Gio.SubprocessLauncher()
sl.spawnv(["c:\\TEMP\\gtk\\_build\\demos\\widget-factory\\gtk4-widget-factory"])

It even works without adding “.exe” at the end.

Note that I’m using an UCRT64 environment instead of the old MINGW64, but that shouldn’t matter (i hope…).

What happens if you navigate to C:\Program Files\Atomes\bin and launch atomes_startup_testing.exe with a double-click?

Hello,
If I go to C:\Program Files\Atomes\bin and double click:

  • On atomes_startup_testing.exe : no issue
  • On atomes.exe same error as with the shortcut launch

Just for information, this is the code in atomes that is supposed to launch the subprocess, maybe you will spot something I don’t:

  GError * error = NULL;
  gchar * proc_dir = NULL;
#ifdef G_OS_WIN32
  PACKAGE_PREFIX = g_win32_get_package_installation_directory_of_module (NULL);
  proc_dir = g_build_filename (PACKAGE_PREFIX, "bin", NULL);
#else
  proc_dir = g_build_filename (PACKAGE_LIBEXEC, NULL);
#endif
  g_print ("proc_dir= %s\n", proc_dir);
  // GSubprocess * proc = g_subprocess_new (G_SUBPROCESS_FLAGS_NONE, & error, proc_dir, NULL);
  // GSubprocess * proc = g_subprocess_new (G_SUBPROCESS_FLAGS_NONE, & error, g_find_program_in_path("atomes_startup_testing.exe"), NULL);
  
  GSubprocessLauncher * proc_launch = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
  g_subprocess_launcher_set_cwd (proc_launch, proc_dir);
  g_free (proc_dir);
  g_print ("Current_directory= %s\n", g_get_current_dir ());
  g_print ("Find_prog_in_path= %s\n", g_find_program_in_path ("atomes_startup_testing.exe"));
  g_print ("Canonicalize_file= %s\n", g_canonicalize_filename ("atomes_startup_testing.exe", NULL));
  gchar * proc_name = NULL;
#ifdef G_OS_WIN32
  proc_name = g_build_filename("atomes_startup_testing.exe", NULL);
//  proc_name = g_build_filename(g_find_program_in_path("atomes_startup_testing.exe"), NULL);
#else
  proc_name = g_build_filename("atomes_startup_testing", NULL);
#endif
  g_print ("proc_name= %s\n", proc_name);
  GSubprocess * proc = g_subprocess_launcher_spawn (proc_launch, & error, proc_name, NULL);
  g_free (proc_name);
  if (error != NULL)
  {
    g_print ("Error: %s\n", error -> message);
  }
  g_subprocess_wait (proc, NULL, & error);
  int res = g_subprocess_get_exit_status (proc);

And again this work in MSYS2, I though about some environment variable that could affect this, but no way to know which one … nope that is not it, try to encode all environment from MSYS2 using a bunch of g_setenv() and it has no effect.

Ok, so the shortcut points to atomes.exe. During startup, atomes.exe launches atomes_startup_testing.exe as a subprocess to check a few OpenGL options. After a while atomes_startup_testing.exe exits and atomes.exe continues execution.

I have a few questions:

  1. Are you sure that launching atomes_startup_testing.exe from File Explorer succeeds? Could it be that the process is terminated before reaching the main function? There are many ways to check, here’s how using PowerShell:
    1. Navigate to C:\Program Files\Atomes\bin with File Explorer
    2. Type powershell in the address bar
    3. Type $p = Start-Process -Wait -PassThru .\atomes_startup_testing.exe in PowerShell
    4. After the process has ended, type $p.ExitCode
  2. Do you get any GError* back from g_subprocess_launcher_spawn?

Ah, I now see that the error is "Failed to execute child process (No such file or directory)".

Be sure to copy both gspawn-win64-helper.exe and gspawn-win64-helper-console.exe in C:\Program Files\Atomes\bin. Try that code:

#include <process.h> /* for _spawnl */

/*...*/

  char *proc_dir = NULL;
#ifdef G_OS_WIN32
  char *prefix = g_win32_get_package_installation_directory_of_module (NULL);
  proc_dir = g_build_filename (prefix, "bin", NULL);
  g_free (prefix);
#else
  proc_dir = g_build_filename (PACKAGE_LIBEXEC, NULL);
#endif
  g_print ("proc_dir= %s\n", proc_dir);

#ifdef G_OS_WIN32
  const char *proc_name = "atomes_startup_testing.exe";
#else
  const char *proc_name = "atomes_startup_testing";
#endif
  char *proc_path = g_build_filename (proc_dir, proc_name, NULL);
  g_print ("proc_path= %s\n", proc_path);

  /* Try one of these methods */
#if 1
  GError *error = NULL;
  GSubprocess *proc = g_subprocess_new (G_SUBPROCESS_FLAGS_NONE, &error, proc_path, NULL);
  g_print ("subprocess: %p\n", proc);
  if (error)
    {
      g_print ("error: %s\n", error->message);
      g_clear_error (&error);
    }

  g_clear_object (&subprocess);
#elif 1
  GError *error = NULL;
  GSubprocessLauncher *proc_launch = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
  g_subprocess_launcher_set_cwd (proc_launch, proc_dir);

  GSubprocess *proc = g_subprocess_launcher_spawn (proc_launch, &error, proc_path, NULL);
  g_print ("subprocess: %p\n", proc);
  if (error)
    {
      g_print ("error: %s\n", error->message);
      g_clear_error (&error);
    }

  g_clear_object (&subprocess);
  g_clear_object (&proc_launch);
#elif 1
  errno = 0;
  inptr_t ret = _spawnl (_P_WAIT, proc_path, proc_name, NULL );
  int saved_errno = errno;
  g_print ("ret: %d", (int)ret); /* TODO */
  g_print ("errno: %d\n", saved_errno);
#endif

  g_free (proc_path);
  g_free (proc_dir);
1 Like

@lb90 thank for helping out,

Ok, I missed to copy the gspawn-* binaries in Atomes\bin, and now it works !

Now, I just need to figure out how to flag these as dependencies and install them along with atomes

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.