14

I have a couple of .exe files that I run as follows :

   public void RunCalculator(Calculator calculator)
    {
        var query = Path.Combine(EpiPath, calculator.ExeName + ".exe");

        if (File.Exists(Path.Combine(EpiPath, "ffs.exe")))
        {
            var p = new Process();
            p.StartInfo.FileName = query;
            p.StartInfo.WorkingDirectory = EpiPath;
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            p.StartInfo.Arguments = String.Join(" ", calculator.Arguments);
            p.Start();
            p.WaitForExit();
        }
        else throw new InvalidOperationException();
    }

This code works, but there's still some flickering being caused from the exe-s being ran multiple times. Is there ANY way to remove the flickering because it's really annoying for the users to experience it since it happens for a few second (there are quite a few exe's being ran).

I tried using a task to do it on a different thread but since each exe depends on the work on the previous ( it writes to a file ) I get an IO exception.

It also seems that the exe files only work if their ProcessPriority is set to Realtime.

Edit:

Afew more details about what I tried recently : As @Jack Hughes suggested I tried using a background worker to solve this problem :

Here's what I did :

I call RunWorkerAsync() function on the background worker which on it's turn calls the RunCalculator function for each of the calculators in order.The flickering still persists.

Edit2 : I have created a detailed repository which contains my way of running the exe files, the exe files and the ProcessHelper class which Old Fox suggested. You can find instructions on how to use the repository inside the README file.

Link to repository : https://github.com/interdrift/epiwin-flick

Christo S. Christov
  • 2,268
  • 3
  • 32
  • 57
  • 1
    What dou you mean by "flickering"? – Alberto Jul 10 '15 at 07:33
  • The exe's are ran, they perform some calculations and some IO operations and they close. After each exe is being ran it closes then the next one opens. The operations that each of them perform cause the whole Windows to be flickering (I assume it's related to the way Windows operates). – Christo S. Christov Jul 10 '15 at 07:37
  • Are you launching the above and lots of others from the GUI thread? If you are doing a lot of work on the GUI thread, and waiting for the results, then you are blocking the GUI from being updated. When it finally does get to update itself it may look like it is flickering. Put your processing into a background thread and keep the main GUI thread free as much as possible. – Jack Hughes Jul 10 '15 at 08:18
  • @JackHughes Could you provide an example for that. I tried using a task to do it on a different thread but since each exe depends on the work on the previous ( it writes to a file ) I get an IO exception. – Christo S. Christov Jul 10 '15 at 08:22
  • If you look at the accepted answer [here](http://stackoverflow.com/questions/5483565/how-to-use-wpf-background-worker) you'll find a good example of how to do it. – Jack Hughes Jul 10 '15 at 08:37
  • Have you tried launching a dummy exe instead of `ffs.exe`? – Alexander Balabin Jul 20 '15 at 11:19

3 Answers3

13

I had the same "flickering" problem as you describe when I created an ActiveX extension using C#. The extension had to start an hidden Console Application, however every time I started the app the console appeared for a few ms.

To solve this I tried many things such as: taking the source code of Process class and debug it, UAC check, VM's vs Real machines and etc.

The solution I found was to use Win32Api. The following snippet starts a new process without the "flickering":

public class ProcessHelper
{

    public const Int32 USE_STD_HANDLES = 0x00000100;
    public const Int32 STD_OUTPUT_HANDLE = -11;
    public const Int32 STD_ERROR_HANDLE = -12;

    //this flag instructs StartProcessWithLogonW to consider the value StartupInfo.showWindow when creating the process
    public const Int32 STARTF_USESHOWWINDOW = 0x00000001;



    public static ProcessStartResult StartProcess(string exe,
                                                  string[] args = null,
                                                  bool isHidden = false,
                                                  bool waitForExit = false,
                                                  uint waitTimeout = 0)
    {
        string command;

        var startupInfo = CreateStartupInfo(exe, args, isHidden, out command);

        ProcessInformation processInfo;

        var processSecAttributes = new SecurityAttributes();

        processSecAttributes.Length = Marshal.SizeOf(processSecAttributes);

        var threadSecAttributes = new SecurityAttributes();

        threadSecAttributes.Length = Marshal.SizeOf(threadSecAttributes);

        CreationFlags creationFlags = 0;

        if (isHidden)
        {
            creationFlags = CreationFlags.CreateNoWindow;
        }

        var started = Win32Api.CreateProcess(exe,
                                                command,
                                                ref processSecAttributes,
                                                ref threadSecAttributes,
                                                false,
                                                Convert.ToInt32(creationFlags),
                                                IntPtr.Zero,
                                                null,
                                                ref startupInfo,
                                                out processInfo);


        var result = CreateProcessStartResult(waitForExit, waitTimeout, processInfo, started);

        return result;
    }

    private static StartupInfo CreateStartupInfo(string exe, string[] args, bool isHidden, out string command)
    {
        var startupInfo = new StartupInfo();

        startupInfo.Flags &= USE_STD_HANDLES;
        startupInfo.StdOutput = (IntPtr) STD_OUTPUT_HANDLE;
        startupInfo.StdError = (IntPtr) STD_ERROR_HANDLE;

        if (isHidden)
        {
            startupInfo.ShowWindow = 0;
            startupInfo.Flags = STARTF_USESHOWWINDOW;
        }

        var argsWithExeName = new string[args.Length + 1];

        argsWithExeName[0] = exe;

        args.CopyTo(argsWithExeName, 1);

        var argsString = ToCommandLineArgsString(argsWithExeName);

        command = argsString;

        return startupInfo;
    }

    private static string ToCommandLineArgsString(Array array)
    {
        var argumentsBuilder = new StringBuilder();

        foreach (var item in array)
        {
            if (item != null)
            {
                var escapedArgument = item.ToString().Replace("\"", "\"\"");
                argumentsBuilder.AppendFormat("\"{0}\" ", escapedArgument);
            }
        }

        return argumentsBuilder.ToString();
    }

    private static ProcessStartResult CreateProcessStartResult(bool waitForExit, uint waitTimeout,
        ProcessInformation processInfo, bool started)
    {
        uint exitCode = 0;
        var hasExited = false;

        if (started && waitForExit)
        {
            var waitResult = Win32Api.WaitForSingleObject(processInfo.Process, waitTimeout);

            if (waitResult == WaitForSingleObjectResult.WAIT_OBJECT_0)
            {
                Win32Api.GetExitCodeProcess(processInfo.Process, ref exitCode);
                hasExited = true;
            }
        }

        var result = new ProcessStartResult()
        {
            ExitCode = (int) exitCode,
            Started = started,
            HasExited = hasExited
        };
        return result;
    }

}

[Flags]
public enum CreationFlags
{
    CreateSuspended = 0x00000004,

    CreateNewConsole = 0x00000010,

    CreateNewProcessGroup = 0x00000200,

    CreateNoWindow = 0x08000000,

    CreateUnicodeEnvironment = 0x00000400,

    CreateSeparateWowVdm = 0x00000800,

    CreateDefaultErrorMode = 0x04000000,
}

public struct ProcessInformation
{
    public IntPtr Process { get; set; }
    public IntPtr Thread { get; set; }
    public int ProcessId { get; set; }
    public int ThreadId { get; set; }
}

public class ProcessStartResult
{
    public bool Started { get; set; }

    public int ExitCode { get; set; }

    public bool HasExited { get; set; }

    public Exception Error { get; set; }

}

[StructLayout(LayoutKind.Sequential)]
public struct SecurityAttributes
{
    public int Length;
    public IntPtr SecurityDescriptor;
    public int InheritHandle;
}

public struct StartupInfo
{
    public int Cb;
    public String Reserved;
    public String Desktop;
    public String Title;
    public int X;
    public int Y;
    public int XSize;
    public int YSize;
    public int XCountChars;
    public int YCountChars;
    public int FillAttribute;
    public int Flags;
    public UInt16 ShowWindow;
    public UInt16 Reserved2;
    public byte Reserved3;
    public IntPtr StdInput;
    public IntPtr StdOutput;
    public IntPtr StdError;
}

public static class WaitForSingleObjectResult
{
    /// <summary>
    /// The specified object is a mutex object that was not released by the thread that owned the mutex
    /// object before the owning thread terminated. Ownership of the mutex object is granted to the 
    /// calling thread and the mutex state is set to nonsignaled
    /// </summary>
    public const UInt32 WAIT_ABANDONED = 0x00000080;
    /// <summary>
    /// The state of the specified object is signaled.
    /// </summary>
    public const UInt32 WAIT_OBJECT_0 = 0x00000000;
    /// <summary>
    /// The time-out interval elapsed, and the object's state is nonsignaled.
    /// </summary>
    public const UInt32 WAIT_TIMEOUT = 0x00000102;
}

public class Win32Api
{
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool GetExitCodeProcess(IntPtr process, ref UInt32 exitCode);

    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds);

    [DllImport("kernel32.dll")]
    public static extern bool CreateProcess
        (string lpApplicationName,
            string lpCommandLine,
            ref SecurityAttributes lpProcessAttributes,
            ref SecurityAttributes lpThreadAttributes,
            bool bInheritHandles,
            Int32 dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            [In] ref StartupInfo lpStartupInfo,
            out ProcessInformation lpProcessInformation);
}

Edit:

I tried the above code with startupInfo.ShowWindow = 7 (which should launch the application without stealing the focus), some of the apps still stealing the focus, therefore I deduced that some of them use a kind of Bring to front method....

I played a little bit with a your code in a WPF window, then I found that if the UI is in Sleep the app doesn't loss the focus:

    private  void Button_Click(object sender, RoutedEventArgs e)
    {
        Task.Factory.StartNew(() =>
        {

            //Provide path to epi
            string epiPath = @"c:/EPISUITE41";
            Level3ntCalculator cl = new Level3ntCalculator();
            var runner = new Calculators.Epi.Runners.ProcessRunner(epiPath);


            runner.WriteXSmilesFiles("CCCCC1CCCCCCC1");
                    cl.Calculate(runner);

        });

        Thread.Sleep(2000);
    }

This is not a good solution! However you need to do the sleep only when you start one of your processes... So I tried to use this information:

    private  void Button_Click(object sender, RoutedEventArgs e)
    {
        var ac = new Action(() => Application.Current.Dispatcher.Invoke(
            () =>
            {
                Thread.Sleep(50);
            }));
        Task.Factory.StartNew(() =>
        {

            //Provide path to epi
            string epiPath = @"c:/EPISUITE41";
            Level3ntCalculator cl = new Level3ntCalculator();
            var runner = new Calculators.Epi.Runners.ProcessRunner(epiPath, ac);


            runner.WriteXSmilesFiles("CCCCC1CCCCCCC1");
                    cl.Calculate(runner);

        });

    }

And then I changed ProcessRunner:

    public void RunCalculator(Calculator calculator)
    {
     //bla bla bla...
        new Thread(new ThreadStart(_action)).Start();
        p.Start();
     //...
    }

In this snippet you execute Thread.Sleep in the UI thread only for a very short of time(the user will never know...).

It's a bad solution... However it solved the problem.

Old Fox
  • 8,629
  • 4
  • 34
  • 52
  • Exe's don't run using this method, probably it changes the priority of the process? Not really sure why. – Christo S. Christov Jul 20 '15 at 11:20
  • 1
    How do you execute the method `ProcessHelper.StartProcess`?(how the line looks like) Please verify that the process wasn't start in the `Task Manager`(Processes tab). If you want I can upload an example into github. – Old Fox Jul 20 '15 at 11:45
  • Why the process shouldn't be started in the Task Manager processes? Anyway, it's hard to track if the process has started since it exits almost immediately. – Christo S. Christov Jul 20 '15 at 11:56
  • My fault, you should see it in the `Task Manager`(Processes tab), but you shouldn't see it in the `Applications` tab. Now I'm using a linux machine. I'll upload a working example later(about 5 hour from now) – Old Fox Jul 20 '15 at 12:07
  • @Chris [This](https://github.com/OldFox1/ExecuteSilentProcess/tree/master) is my example. Take the code and try to execute it with your problematic process. The example execute `foo` process as a hidden one. Just a side note: In the source solution the method `ToCommandLineArgsString` used to help pass a `Json` object into the process as an argument, so maybe this is the problem you've faced. – Old Fox Jul 20 '15 at 16:04
  • Thanks for the effort, please take a look on the repository I've created on github where I've demonstrated that this doesn't work in my case. I've also added my way of running the application and the flickering can be observed. – Christo S. Christov Jul 21 '15 at 08:36
  • @Chris the "flickering" is `Focus` losing. I've downloaded your example, installed the 3erd party apps.When I execute using my example, it's execute the 3erd party apps, however the console still losing the focus. your case is different then I thought. Does your real app a `Console` or `WinForm`? if you app is a `Winform` read [this question](http://stackoverflow.com/questions/4499239/c-sharp-process-start-focus-issue). If the app is `Console`, I've already tried anything in first and second page in google... – Old Fox Jul 25 '15 at 13:49
  • it looks like the 3erd party apps calls to "bring to front method" if so there's no solution. When I'll have time, I'll reverse the apps to check if they do so.... – Old Fox Jul 25 '15 at 13:51
  • They seem to request the realtime process priority.That could also have some weight. Thanks for the effort :) – Christo S. Christov Jul 25 '15 at 13:52
  • @Chris you welcome, you've asked an interesting question... The real app is `Console`, `Winform` or `WPF`? I need it to know where should I put the effort... – Old Fox Jul 25 '15 at 14:03
  • This will be used in a WPF application – Christo S. Christov Jul 25 '15 at 14:04
  • @Chris please read my edit. The edit isn't a good solution, however it solve the problem... Please let me know if you have any question... – Old Fox Jul 27 '15 at 14:26
  • 1
    Seems promissing. I will see if it could fit the use case and reply. You deserved the bounty, altho this solution could prove useful I think I will end up having to dissemble to application to see what could be done. PS: I've developed a parallelized version of the application with multiple such folder with exe-s. The light show is amazing. – Christo S. Christov Jul 27 '15 at 14:57
4

Place the work into a background thread so that you are not blocking the main GUI thread.

Something like this:

public class MainWindow : Window
{
    private readonly BackgroundWorker worker = new BackgroundWorker();

    public MainWindow()
    {
        this.Loaded += MyWindow_Loaded;
        worker.DoWork += worker_DoWork;
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;
    }

    private void MyWindow_Loaded(object sender, RoutedEventArgs e)
    {
        // Start the background worker. May be better started off on a button or something else app specific
        worker.RunWorkerAsync();
    }

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
       // run all .exes here...
       // Note: you cannot access the GUI elements directly from the background thread
       RunCalculator();
       RunOtherExes();
    }

    private void worker_RunWorkerCompleted(object sender, 
                                           RunWorkerCompletedEventArgs e)
    {
        //update ui once worker complete its work
    }

    private void RunCalculator()
    {
        var p = new Process();
        p.StartInfo.FileName = query;
        p.StartInfo.WorkingDirectory = path;
        p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.Arguments = String.Join(" ", calculator.Arguments);
        p.Start();
        p.WaitForExit();
    }

    private void RunOtherExes()
    {
        // ...
    }

}

You can also update the GUI thread with progress from the background thread too if you need that.

Jack Hughes
  • 5,514
  • 4
  • 27
  • 32
  • If I do this will the order of execution of the exe files be synchronous? I need it to be synchronous in order to get the correct output. This means that I must be sure that exe file A is finished before executing exe file B. – Christo S. Christov Jul 10 '15 at 10:03
  • Yes. `RunCalculator` is executed first and calls `WaitForExit` on the process so that the background thread waits for the process to end before starting the next. Then `RunOtherExes` is called that would do the same to the other processes. – Jack Hughes Jul 10 '15 at 10:16
  • Unfortunetely using this approach still causes the flickering. – Christo S. Christov Jul 10 '15 at 11:05
  • Can you post the rest of the project? – Jack Hughes Jul 10 '15 at 11:26
  • I'm not allowed to post the project I'm working on because of the non disclosure agreements. I have most of the implementation explained in my question tho. Thanks for your response. – Christo S. Christov Jul 10 '15 at 11:32
  • Presumably you've commented out `RunCalculator` and `RunOtherExes` and the flickering stops? – Jack Hughes Jul 10 '15 at 11:39
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/82939/discussion-between-chris-and-jack-hughes). – Christo S. Christov Jul 10 '15 at 11:54
  • finally managed to get a source code out you can check it out here : https://github.com/interdrift/epiwin-flick – Christo S. Christov Jul 21 '15 at 08:54
  • When you launch the process, have you tried setting the `RedirectStandardOutput` to true in the process `StartInfo`? It is presumably the console app writing to stdout that is causing the flickering. – Jack Hughes Jul 21 '15 at 09:25
  • Yes, it has no effect on the flickering part. – Christo S. Christov Jul 21 '15 at 10:01
3

It is still not very clear what kind of flickering you're experiencing, however the fact that you're doing WaitForExit() on the GUI thread look troublesome. Given that you're running an external program I do not really see the point of launching it from another thread and waiting, maybe using the Exited event instead of blocking wait would release the GUI thread to do its job and stop the issue.

Do not forget to set EnableRaisingEvents to true and IIRC the event will be raised on thread pool so return to the UI context before touchin any controls.

Alexander Balabin
  • 2,055
  • 11
  • 13