"CFileException 0" when saving file: don't use Process.spawn()

Several user questions are from people getting a message “CFileException 0” when trying to save a .skp file, and the file is not saved (instead a new file is created, with an extra number). I found out that this problem can be triggered by Ruby extensions that use “Process.spawn()”. This Ruby function causes some handles from the parent process (SketchUp) to be inherited by the child process. It has got an optional argument “close_others”, supposed to prevent that—and the default value should be true—but it doesn’t seem to work on Windows, at least in the version of Ruby within SketchUp 2018. If you don’t know what “inheriting handles” mean, in short, it means that whatever files are currently opened in SketchUp won’t be closed until the child process also closes them (or finishes running). That’s why, on Windows, SketchUp cannot overwrite the file it had opened.

As a workaround, the following (Windows-only) code creates a new process using directly the Windows API, passing it the correct flag “don’t inherit the handles” (it’s one of the zeroes).

require 'fiddle'
require 'fiddle/types'
require 'fiddle/import'

module Kernel32
    extend Fiddle::Importer
    dlload 'kernel32.dll'
    include Fiddle::Win32Types
    PROCESS_INFORMATION = struct [
        "HANDLE hProcess",
        "HANDLE hThread",
        "DWORD  dwProcessId",
        "DWORD  dwThreadId"
    ]
    extern 'BOOL CreateProcessA(LPCSTR, PVOID, PVOID, PVOID, BOOL, DWORD, PVOID, PVOID, const char *, struct PROCESS_INFORMATION *)'
    extern 'BOOL CloseHandle(HANDLE)'
end

def launch_my_process(cmd)
    startinfo = ([0] * 18).pack('Q' * 18)
    procinfo = Kernel32::PROCESS_INFORMATION.malloc
    procinfo.hProcess = 0
    procinfo.hThread = 0
    procinfo.dwProcessId = 0
    procinfo.dwThreadId = 0
    result = Kernel32.CreateProcessA(cmd, nil, nil, nil, 0, 0, nil, nil, startinfo, procinfo)
    if result == 0
        # failed!
    else
        Kernel32.CloseHandle(procinfo.hThread)
        Kernel32.CloseHandle(procinfo.hProcess)
    end
end
1 Like

We also know that using the global #system method can have the same result.

Another Windows only workaround that is more concise is using the WIN32OLE class.
As an example, running the Notepad applet as it’s own top level process …

require "win32ole"
WshShell = WIN32OLE.new("WScript.Shell")
WshShell.Run("%windir%/notepad.exe")

An example method taking a command argument …

def launch_my_process(cmd)
  require "win32ole" unless defined?(WIN32OLE)
  WIN32OLE.new("WScript.Shell").Run(cmd)
end

Note that a persistent reference to the Shell object is not actually needed.


REF: Run Method (Windows Script Host)

Cool, thanks!

In my use case I actually monitor for some time whether the launched subprocess is still running, so I guess I can’t use the WIN32OLE workaround—I don’t call CloseHandle(hProcess) immediately, but use the handle in additional calls to GetExitCodeProcess(), checking if the process appears to still be running.

1 Like

I’m now using the WIN32OLE solution since the program I need to run is Windows only.

My HTML form uses JQuery (which I supply) and javascript. I’m not interested in using the Fiddle library.

In Ruby 2.6, the behavior of “inherited fd’s” changed in several places as related to child processes. Hence, I’d always recommend setting the value, as opposed to relying on a default.

JFYI, there’s a few different ways to spawn child processes (different parameters, returns, etc), and almost all rely on the same underlying function call.

I suppose you’re making a theoretical point, given that my point was that “close_others” appears to be ignored anyway, buggily, at least on Windows on the version of Ruby that comes with SU 2018.

For Ruby on Windows, anything before 2.4 is questionable, and the later the version, the better. Before 2.4, no one was publicly fully testing Windows Ruby.

Ruby itself first started fully testing Ruby 2.5 on mswin. With 2.6, they started fully testing mswin & mingw (the version used in SU). Others are also testing.

1 Like