Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove flickering from running consecutive exe files

Tags:

c#

windows

winapi

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

like image 642
Christo S. Christov Avatar asked Jul 10 '15 07:07

Christo S. Christov


2 Answers

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.

like image 188
Old Fox Avatar answered Oct 13 '22 18:10

Old Fox


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.

like image 4
Jack Hughes Avatar answered Oct 13 '22 19:10

Jack Hughes