1

Within my application the user can select different files and executables via a OpenFileDialog. From the path of the chosen file a ProcessModelis created and added to a ObservableCollection.

The plan is that the user can select different files and programs and add them to a list within the application. The software should then open - and later close - the chosen files as soon as a certain event is fired (in this case a gesture by the user captured with the camera).

The ProcessModel holds several properties for different options, but the ones important for my question are set in the constructor as follows:

    public ProcessModel(string path)
    {
        ProcessName = Path.GetFileNameWithoutExtension(path);
        processExtension = Path.GetExtension(path);
        ProcessPath = path;
        instances = new List<Process>();
    }

What I want to do with each ProcessModel is, that in case a certain event in the application is triggered I want to start the associated process. Furthermore, I want to track, how many instances of the same processes have been started and also be able to close them via another event. To achieve this, I listen to the Process.Exited event and handle my list of instances accordingly. Before I get to the actual issue, here are the methods I use (all within my ProcessModelclass):

creating and starting a new process:

    /// <summary>
    /// Starts a new instance of the current process
    /// </summary>
    public void StartNewInstance()
    {
        try
        {
            Process newInstance;
            if (processExtension == GenDefString.ExecutableExtension)
            {
                newInstance = new Process();
                newInstance.StartInfo.UseShellExecute = false;
                newInstance.StartInfo.FileName = ProcessPath;
                newInstance.Start();
            }
            else
            {
                newInstance = Process.Start(ProcessPath);
            }
            newInstance.EnableRaisingEvents = true;
            newInstance.Exited += OnProcessExited;
            instances.Add(newInstance);
            UpdateNrOfInstances();
        }
        catch(Exception e)
        {
            MessageBox.Show(e.Message);
        }
    }

stopping the last instance in the list:

    /// <summary>
    /// stops the last instance int he list
    /// </summary>
    public void StopLastInstance()
    {
        if (instances.Count < 1) return; 
        try
        {
            var instanceToDelete = instances.Last();
            instanceToDelete.Exited -= OnProcessExited;
            instanceToDelete.CloseMainWindow();
            instanceToDelete.Close();
            instances.RemoveAt(instances.Count - 1);
            UpdateNrOfInstances();
        }
        catch(Exception e)
        {
            MessageBox.Show(e.Message);
        }
    }

method, which listens to the event of the process being closed (externally):

    /// <summary>
    /// Trigger Status changed Event was raised
    /// </summary>
    /// <param name="source"></param>
    /// <param name="e"></param>
    public void OnProcessExited(object source, EventArgs e)
    {
        var process = source as Process;
        if (process == null) return;
        instances.Remove(process);
        UpdateNrOfInstances();
    }

Updating the number of current instances to the GUI:

    /// <summary>
    /// Sets the value for the number of instances (used for GUI update)
    /// </summary>
    private void UpdateNrOfInstances()
    {
        NrOfInstancesRunning = instances.Count;
    }

As you can see in the StartNewInstance() method, I check if the extension is from an executable or an software associated file (GenDefString.ExecutableExtension is a string holding @".exe"). Everthing is working as intented, however if for example the user puts two different .pdf files, the second process is immediately terminated, because my pdf-viewer has already been started by ProcessModel associated to the first .pdf file.

For this case I need to handle things differently. As far as I can see, I have to ways of doing it:

  • Forcing each newly started process into it's own Window: I don't really consider this a good idea additionally to not really knowing how to achieve that with all different software types.
  • Informing the user what is happening and why there are 0 instances in the respective item, when clearly the file is opened. I prefer this one and my approach would be to get information from the source argument of the OnProcessExited() method.

So my question: how can I distinguish if the process exit happened because of the described case?

EDIT: My current approach would be to track the process IDs, however I'm wondering if there is a better solution, maybe already implemented to the Process-Class

EDIT2: For better understanding, the complete project can be found here.

Roland Deschain
  • 2,211
  • 19
  • 50
  • @dynamoid two different pdf files are loaded as two tabs into one pdf-viewer --> one process for the pdf viewer. So the second pdf starts a new process, but if the view is already running, this process is exited immediately. This seems to happen automatically (I only can debug so far, that the Exit-event is triggered) – Roland Deschain Dec 05 '18 at 11:16
  • no, it means that if I double click on a PDF file, while another is already open, the second file is loaded as new tab into the PDF viewer. This is standard behavior. But I want to open the files programmatically and in the background this means that the process for the second PDF file is killed immediately after it is started, since there is only one process for the PDF VIEWER needed. – Roland Deschain Dec 05 '18 at 11:26
  • Added the link to the project to the OP, so maybe it helps to understand my problem if you have the full picture – Roland Deschain Dec 05 '18 at 11:29

2 Answers2

4

You are seeing the behavior of a single instance process. Standard examples of such programs are any Microsoft Office app, browsers like IE, Adobe Reader is likely to be relevant to this question. These are very large processes that consume a lot of system resources, starting them multiple times could bring the machine to its knees. In the olden days anyway.

The underlying mechanism is the same for all of them. When you start the second instance it discovers that the program is already running. It uses a process interop mechanism like a named pipe to pass the command line arguments to the first instance. In this case the path of the file you are trying to open. The first instance creates another window to show the content of the file with its own taskbar button. Pretty indistinguishable from the process running multiple times, other than that you'll see the Exited event fire quickly after starting it. Exactly how long that takes is unpredictable, usually in less than a second but might be many seconds when the machine is loaded.

Not the only quirk, Process.Start() might even return null. In other words, no process created at all. I only know of Explorer.exe behaving that way. A side-effect of its api creating the process (ShellExecuteEx() under the hood) and it recognizing that it is asked to start itself.

Notable is that implementing such a process is directly supported in the .NETFramework. A bit obscure, the namespace is not terribly popular as of late.

There is very little you can do about this. There is no standard mechanism to force a program to not do this, it might have a command line option but that's specific to the program you start. Nothing you can see back in the Process object either, it looks like an entirely normal process termination with the ExitCode property at 0. Other than that it exited a lot quicker than normal, that is the only real cue you have. With the complication that the process might have malfunctioned, albeit that ExitCode ought to be non-zero when that happens. Seeing the first process open the document requires UI Automation, but might not be so easy to make universal. Find sample code in this post.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • So...manually tracking the first process of an associated file and after that ignoring other files of the same type it is? Hmm, I was hoping I overlooked something... – Roland Deschain Dec 06 '18 at 11:14
  • That doesn't work either, a program may open multiple file types. Like .doc, .dot, .dotx for Word. It is notable that you made a fairly standard SO mistake, nobody has a real guess at the reason why you wrote this code so can't suggest a better approach. – Hans Passant Dec 06 '18 at 13:18
  • That is right. I edited the OP to describe the reason for the code (2nd paragraph). – Roland Deschain Dec 06 '18 at 14:02
0

I would add a List<string> SingleInstance loaded from persistence(see later).

If the processExtension is in the list, launch in new window.

If the process fails for this reason(at start within .5s of launch) add the processExtension to the list and relaunch in a separate window. Save the list to Registry or file to be reloaded on startup.

This procedure only starts new windows for processes not able to open multiple documents as described. If needed add a dictionary<string,int> to count the existing instances, to allow the in-process load of the first file, and fail-over to external processes.

  • yes, but that means tracking the processes by hand. I was curious if I could gather the information, why a process was ended, within the `.Exited` Event. – Roland Deschain Dec 05 '18 at 08:33