Running external program without flashing command line


#1

Hi All

I’m developing a plugin that needs to call an external command line program to perform a task that cannot be performed in Ruby (nor with a Ruby C extension). Everything works fine, except that the command line window shows while the external program is running. I think I’ve read somewhere that there is a way to call such an external program without showing the command line, but cannot find it.

Currently I call the program with parameters using `backticks`. I’m targeting SketchUp 2014 (Ruby 2.0.0) on Windows.

Any help is appreciated!


#2

For windows look at START /B examples. https://ss64.com/nt/start.html

and the CMD.EXE options here. https://ss64.com/nt/cmd.html


#3

(Simplest solution: avoid to run the plugin on Windows)

There are several criteria that seem to exclude each other when working out a solution for Windows.

  1. You want to run it “quiet” and avoid the black scary command prompt.
  2. You may not want to freeze SketchUp’s single-threaded Ruby process and the whole UI: not completely synchronous.
  3. You may want to know when it is finished: not completely asynchronous.
  4. You may want to have stdout and stderr.

I thought my old TextureResizer code was ugly and I wanted to renovate it. Most of all I wanted get rid of writing output to temporary files. After a long research it turned out the only, commonly used solution was similar to what I already had. Take a look into the latest async_batch.rb in TextureResizer on EWH (my API wraps it into a promise which you maybe do not need).

  1. Running a batch file with call or start always opens a black GUI window. The standard work-around is to invoke it from a visual basic script with wscript.
  2. I dispatch the process asynchronously.
  3. I have to observe when the external process completes. Currently I do this with SketchUp’s asynchronous UI timer.
  4. To get stdout/stderr from an IO object, you would need to run synchronously. But when you use handle = WsShell.Exec you get again a flashing black window. Otherwise, the output is empty and the only workaround is to write&read it from a file.

It is 2018 and this is a sad story.


Eneroth Open Newer Version
#4

In Windows Ruby’s running a system command with back-ticks etc always opens a window - albeit briefly.
You can’t sidestep it, but you can minimize the time it appears by running a very short .cmd [or .bat] file which then runs a main .cmd file, by using the START /B method - which then runs it window-free - the initial window closes as soon as it’s completed.
You can write the two files into the TEMP folder and launch the short one using:

UI.openURL("file:///#{full_path_to_temp.cmd}")

Another [better?] way to sidestep any command windows opening is to use VBScript to run all of the system code.
Again you’d write a VBS file to the TEMP folder, and then use openURL to run it…
Along the lines of:

Set wShell = CreateObject ("Wscript.Shell") 
wShell.Run "cmd /c SOME_CMD_CODE", 0

#5

You could also write a small Ruby C Extension which will let you execute binaries nativly.


#6

Can the Ruby C extension call binaries that uses a newer version of the SketchUp SDK than the currently running SU version?


#7

You just launch the executable from the C extension, you can then use native OS calls which should give you more control on how to launch it.


#8

Based on TIG’s and Aerilius’ replies I managed to find a solution.

Here’s my general system-call-with-no-flashing-window method.

require "tempfile"

# Run system call without flashing command line window on Windows.
# Runs asynchronously.
# Windows only hack.
#
# @param cmd String.
#
# @return [Void].
def self.system_call(cmd)
  # HACK: Run the command through a VBS script to avoid flashing command line
  # window.
  file = Tempfile.new(["cmd", ".vbs"])
  file.write("Set WshShell = CreateObject(\"WScript.Shell\")\n")
  file.write("WshShell.Run \"#{cmd.gsub('"', '""')}\", 0\n")
  file.close
  UI.openURL("file://#{file.path}")

  nil
end

As this code runs asynchronously I used this method, based on Aerilius’s ImageMagic library, to run a piece of code only once a file has been created.

# Run block once a file has been created.
#
# @param path [String]
# @param async [Boolean]
# @param delay [Flaot]
# @param block [Proc]
#
# @return [Void]
def self.on_exist(path, async = true, delay = 0.2, &block)
  if File.exist?(path)
    block.call
    return
  end

  if async
    UI.start_timer(delay) { on_exist(path, async, delay, &block) }
  else
    sleep(delay)
    on_exist(path, async, delay, &block)
  end

  nil
end