Страницы

Поиск по вопросам

пятница, 24 января 2020 г.

C# использование функции kernel32 CreateProcess

#c_sharp #winapi #процесс


Есть две программы, написанные на с#. Обе - консольные приложения, в каждой создается
форма (System.Windows.Forms.Form).
Первая программа в процессе своей работы создает/уничтожает вторую программу. Вот
код создания процесса второй программы:

var proc = new System.Diagnostics.Process
{
    StartInfo = new System.Diagnostics.ProcessStartInfo
    {
        FileName = exe,
        Arguments = args
    },
    EnableRaisingEvents = true
};
proc.Start();


Т.е. я имею консоль первой программы + окно первой программы + несколько таких же
пар для каждого запущенного экземпляра второй программы.
Все работает, как надо. Но мне жутко надоело, что новые процессы постоянно перехватывают
фокус на себя (при создании). Нашел вот такое решение (ответ benrwb). Проверил - фокус
больше не перехватывает. Программы запускаются, только вот 


консоль теперь одна на все программы 
при уничтожении созданной (второй) программы
закрывается и основная программа


Я посмотрел описание функции CreateProcess, ее флагов. Попытался добавить флаги DETACHED_PROCESS,
CREATE_NEW_CONSOLE и т.д. но методом тыка ничего путного не получилось. 

Как из программы c# запускать абсолютно несвязанные процессы (как если бы я вручную
запускал exe-файл) и при этом чтобы новые окна вообще не перехватывали фокус? (не надо
предлагать возвращать перехваченный новым окном фокус обратно - это не подходит)

UPD 1

Вопрос такой: как из программы, написанной на c#, сделать запуск другой программы,
написанной на c#, аналогично приведенному выше коду, но с помощью функции из kernel32.dll
CreateProcess. У меня есть доступ к исходным кодам обоих программ.

UPD 2

Следующий код создает новый процесс, не перехватывая фокус, но с консолью родительского
процесса. При закрытии созданного процесса закрывается и главный процесс. Если добавить
флаги CREATE_NEW_CONSOLE, DETACHED_PROCESS или CREATE_NEW_PROCESS_GROUP, результат
не меняется.

STARTUPINFO si = new STARTUPINFO();
si.cb = (UInt32)Marshal.SizeOf(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWNOACTIVATE;

UInt32 dwCreationFlags = ABOVE_NORMAL_PRIORITY_CLASS;

PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

CreateProcess(name, path + " " + inprms, IntPtr.Zero, IntPtr.Zero, false, dwCreationFlags,
IntPtr.Zero, System.IO.Path.GetDirectoryName(path), ref si, out pi);


UPD 3

Нашел такое "решение" средствами c#:

// основная программа - запуск дочерней программы
var proc = new System.Diagnostics.Process
{
    StartInfo = new System.Diagnostics.ProcessStartInfo
    {
        FileName = exe,
        Arguments = args,
        UseShellExecute = false,
        WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden
    },
    EnableRaisingEvents = true
};
proc.Start(); 

// дочерняя программа - событие загрузки главного окна
Load += (s, e) =>
{
    Show();
};     


При таком подходе у дочерних программ нет консоли, весь вывод направляется в консоль
главного процесса. Закрытие любого из процессов никак не влияет на другие процессы.
Не знаю, почему, но Show() не перехватывает фокус (как мне и нужно). 
Если написать UseShellExecute = true, то консоль так же будет одна, но Show() будет
перехватывать фокус на дочернее окно. Если кто-нибудь мне объяснит, почему, буду признателен.

UPD 4

Так же хотелось бы узнать, как же все-таки это можно сделать через CreateProcess().

UPD 5

После некоторого времени отладки оказалось, что описанный выше пример работает непредсказуемо
(то фокусит, то нет). Испробовав разные способы я нашел таки вариант, который меня устроил:

// основная программа - запуск дочерней программы
var proc = new System.Diagnostics.Process
{
    StartInfo = new System.Diagnostics.ProcessStartInfo
    {
        FileName = exe,
        Arguments = args,
        WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden
    },
    EnableRaisingEvents = true
};
proc.Start(); 

// дочерняя программа - при создании главного окна
using (Window = new MyWindow())
{
    Window.Show();
    Window.SendToBack();

    ConsoleVisible(WINDOW_STYLE.SW_SHOWMINNOACTIVE);

    Application.Run(Window);
} 

public static void ConsoleVisible(UInt16 WINDOW_STYLE)
{
    var handle = GetConsoleWindow();
    ShowWindow(handle, WINDOW_STYLE);
}

[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ShowWindow(IntPtr hWnd, UInt16 nCmdShow);


Запуск процесса с WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden скрывает
как само окно приложения, так и его консоль. В запускаемом приложении Show() восстанавливает
окно формы, но! не фокусит его, а только размещает поверх всех окон. С помощью SendToBack()
оно отправляется на задний план, фокус все это время не тронут. Консоль в это время
спрятана и нигде не отображается. Чтобы ее вернуть, вызываем WinApi с флагом SW_SHOWMINNOACTIVE.
Консоль появляется на панели задач в свернутом состоянии. Интересно то, что если использовать
флаг SW_SHOWNOACTIVATE, то консоль восстанавливается и автоматически фокусится, что
мне не подходит.

Благодарю всех, кто помогал.
    


Ответы

Ответ 1



По той же ссылке, что вы кинули есть более лаконичное решение: public class MyClass { [DllImport("user32.dll")] static extern bool SetForegroundWindow(IntPtr hWnd); public void doProcess(string filename, string arguments){ using (Process proc = new Process()) { proc.StartInfo.FileName = filename; proc.StartInfo.Arguments = arguments; proc.Start(); SetForegroundWindow(proc.MainWindowHandle); } } } Новые процессы просто переносим на задний план. Если у вас есть доступ к коду приложений, которые вы запускаете, то можете их доработать. Например, что бы можно было передавать какие-то аргументы и при запуске само приложение уходило на задний план или сворачивалось. А еще, вроде, можно запускать программу в свернутом состоянии через CMD: start /min "" "C:\Windows\notepad.exe"

Комментариев нет:

Отправить комментарий