<?xml version="1.0"  encoding="ISO-8859-1" ?> <!-- encoding="windows-1252" ?> -->
<?xml-stylesheet type="text/xsl" href="../csharp.xsl"?> 

<document>
<title>Procesos</title>

<navigation>
 <prev>
  <ref>intro.xml</ref>
  <title>Introducción</title>
 </prev>
 <next>
   <ref>thread.xml</ref>
   <title>Hebras</title>
 </next>
</navigation>



<document>
<tag>process-start</tag>
<title>Ejecución de procesos</title>

<text>
La clase <type>System.Diagnostics.Process</type> permite crear y monitorizar procesos (accediendo a la información que se visualiza en el Administrador de Tareas de Windows).
</text>

<text>
El método <type>Process.Start()</type> equivale a la llamada <type>ShellExecute</type> del API de Windows (Win32) y es el que deberemos utilizar para lanzar un proceso. Los parámetros del proceso se especifican mediante un objeto de la clase <type>ProcessStartInfo</type>.  Al encapsular una llamada al shell de Windows, el método <type>Start</type> también podemos usarlo para abrir un fichero de cualquier tipo de los que tengan acciones asociadas en el registro de Windows.
<!--Supports shell verbs (print, open)-->
</text>

<example>
<title>Ejecución de procesos</title>

<image>
 <url>image/process-start.gif</url>
</image>

<text>
Probar las distintas formas de lanzar un proceso escribiendo la respuesta al evento <type>Click</type> de cada botón de la ventana que se muestra en la imagen de encima:
</text>


<text>
<i>Lanzar proceso:</i>
<code>
  System.Diagnostics.Process.Start("iexplore.exe");
</code>
</text>


<text>
<i>Lanzar proceso con parámetros:</i>
<code>
  string proceso = "iexplore.exe";
  string args = "http://elvex.ugr.es/decsai/Csharp/";

  System.Diagnostics.Process.Start("iexplore.exe", args);
</code>
</text>


<text>
<i>Lanzar un proceso utilizando ProcessStartInfo:</i>
<code>
  using System.Diagnostics;

  ...
  ProcessStartInfo info = new ProcessStartInfo();

  info.FileName = "iexplore.exe";
  info.Arguments = "http://elvex.ugr.es/decsai/CSharp/";
  info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Maximized;

  Process.Start(info);
</code>
</text>

<text>
<i>Lanzar el proceso adecuado para un fichero cualquiera:</i>
<code>
  OpenFileDialog openFileDialog = new OpenFileDialog();

  openFileDialog.InitialDirectory = "c:\\" ;
  openFileDialog.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
  openFileDialog.FilterIndex = 2 ;
  openFileDialog.RestoreDirectory = true ; // Vuelve al directorio actual

  if (openFileDialog.ShowDialog() == DialogResult.OK) {
     System.Diagnostics.Process.Start(openFileDialog.FileName);
  }
</code>
</text>

<text>
<i>Abrir una URL:</i>
<code>
  System.Diagnostics.Process.Start("http://elvex.ugr.es/decsai/Csharp/");
</code>
</text>




<text>
<i>Uso de los verbos del shell:</i>
<code>
  using System.Diagnostics;

  ...
  ProcessStartInfo info = new ProcessStartInfo();
	
  info.FileName = "Ade.jpg";
  info.WorkingDirectory = "f://";
  info.Verb = "edit"; // vs. "open" || "print"

  Process.Start(info);	

    // Verbos comunes
    // --------------
    // open     Abre un ejecutable, documento o carpeta
    // edit     Edita un documento
    // print    Imprime un documento
    // explore  Explora una carpeta
    // find     Inicia una búsqueda en el directorio especificado
</code>
</text>

 <link>
   <url>src/Shell.vs2003.zip</url>
   <icon>zip</icon>
   <text>Descargar código fuente (Visual Studio 2003)</text>
 </link>
 <text></text>
 <link>
   <url>src/Shell.vs2005.zip</url>
   <icon>zip</icon>
   <text>Descargar código fuente (Visual Studio 2005)</text>
 </link>

</example>

<text>
RECOMENDACIÓN: Cuando se utiliza el método <type>Process.Start</type>, también se debería llamar siempre al método <type>Process.Close</type> para liberar la memoria asociada al objeto de tipo <type>Process</type>.
</text>

</document>

<document>
<tag>process-kill</tag>
<title>Finalización de procesos</title>

<text>
En los ejemplos anteriores, si quisiéramos que nuestra aplicación detuviese su ejecución hasta que el proceso lanzado finalizase su ejecución, sólo tendríamos que llamar al método <type>WaitForExit</type>:
</text>

<example>
<code>
Process proceso = Process.Start(...);

proceso.WaitForExit();
proceso.Close();
</code>
</example>

<text>
La espera podría hacerse por un período de tiempo limitado si utilizamos un parámetro en la llamada al método <type>WaitForExit</type>:
</text>

<example>
<title>Ejecución invisible de comandos MS-DOS</title>
<code>
Process proceso = new Process()

string  salida = Application.StartupPath + "/output.txt";
string  path   = System.Environment.GetFolderPath 
                   (Environment.SpecialFolder.System);
   
proceso.StartInfo.FileName = "cmd.exe";
proceso.StartInfo.Arguments = 
  "/C dir \""+path+"\" >> \"" + salida +"\" &amp;&amp; exit";
   
proceso.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proceso.StartInfo.CreateNoWindow = true;
proceso.Start()
   
proceso.WaitForExit(1000);   // 1 segundo de timeout

if (!proceso.HasExited()) {
   proceso.Kill();           // Finalizamos el proceso
} else {
   ...
}
</code>
</example>

<text>
Además, también podemos detectar cuándo finaliza un proceso que hayamos lanzado si empleamos el manejador de eventos <type>Exited</type> de la clase <type>Process</type>. De esta forma, no tenemos por qué bloquear la ejecución de nuestra aplicación mientras esperamos la terminación de un proceso:
</text>

<example>
<title>Evento asociado a la terminación de un proceso</title>
<code>
Process proceso = new Process()
...
proceso.Exited += new EventHandler(ExitHandler);
proceso.Start()
...
</code>

<text>
El método <type>ExitHandler</type> se encarga de realizar las tareas necesarias al finalizar la ejecución del proceso, como por ejemplo:
</text>

<code>
public void ExitHandler(object sender, EventArgs e) 
{
  Process proceso = (Process) sender;

  MessageBox.Show ( "PID: " + proceso.Id
                  + System.Environment.NewLine 
                  + "Hora: " + proceso.ExitTime 
                  + System.Environment.NewLine 
                  + "Código: " + proceso.ExitCode );

  proces.Close();
}
</code>
</example>

<text>
La clase <type>Process</type> también nos permite provocar la terminación de un proceso. Ésta se puede realizar explícitamente utilizando los métodos <type>Kill()</type> y <type>CloseMainWindow()</type>, siendo este último el método recomendado, pues equivale a que el usuario de la aplicación cierre ésta de la forma usual. Es decir, <type>CloseMainWindow</type> solicita cerrar la aplicación como si el propio usuario cerrase la ventana principal de la aplicación (lo cual puede provocar la aparición de mensajes de la aplicación), mientras que <type>Kill</type> finaliza el proceso "por las bravas", pudiendo ocasionar la pérdida de datos que no hayan sido previamente guardados.
</text>

<text>
En la siguiente sección veremos cómo se puede forzar la finalización de un proceso y también aprenderemos a acceder a la información relativa a los procesos que se estén ejecutando en una máquina Windows (la misma información que figura en el Administrador de Tareas de Windows).
</text>

</document>

<document>
<tag>process-monitor</tag>
<title>Monitorización de procesos</title>

<example>
<title>Monitor de procesos</title>

<image>
 <url>image/process-monitor.gif</url>
</image>

<text>
Creamos una aplicación Windows a la que denominamos <type>WindowMonitor</type>, cuyo formulario principal (Text="Procesos del sistema") contiene los siguientes componentes:
</text>

<list>
<item>ListBox <type>listProcesses</type>  (ScrollAlwaysVisible=true).</item>
<item>TextBox <type>textInfo</type> (BackColor=Info; Multiline=true).</item>
<item>Button <type>buttonClose</type> (Text="WindowClose"; ForeColor=ForestGreen).</item>
<item>Button <type>buttonKill</type> (Text="Kill"; ForeColor=DarkRed).</item>
<item>Label <type>labelWarning</type> (TextAlign=MiddleCenter; Text="¡OJO! Kill puede ocasionar la pérdida de datos al forzar la terminación de un proceso...").</item>
</list>

<text>
Al cargar el formulario, rellenamos la lista de procesos del sistema (evento <type>FormLoad</type>):
</text>

<code>
  Process[] procesos = Process.GetProcesses();

  listProcesses.Items.AddRange(procesos);	
</code>

<text>
Cuando el usuario selecciona un proceso concreto, mostramos información detallada acerca del proceso seleccionado (evento <type>SelectedIndexChanged</type> del ListBox):
</text>

<code>
  Process selected = (Process) listProcesses.SelectedItem;

  textInfo.Clear();

  if (selected!=null)
     textInfo.Lines = new string[] {
       "Proceso: " + selected.ProcessName,
       "PID: " + selected.Id,
	 // "Máquina: " + selected.MachineName,
       "Prioridad: " + selected.PriorityClass,
       "Uso de memoria: " + selected.WorkingSet + " bytes", 
	 // selected.PagedMemorySize 
	 // selected.VirtualMemorySize
       "Tiempo de CPU: " + selected.TotalProcessorTime,
       "Hora de inicio: " + selected.StartTime,
       "Módulo principal: " + selected.MainModule.FileName 
	 // selected.MainWindowTitle
     };
</code>

<text>
Finalmente, programamos la respuesta de la aplicación correspondiente a los dos botones de nuestra interfaz:
</text>

<code>
// Fuerza la finalización del proceso
// (puede provocar la pérdida de datos)

private void buttonKill_Click(object sender, System.EventArgs e) 
{
  Process selected = (Process) listProcesses.SelectedItem;

  if (selected!=null)
     selected.Kill();
}

// Solicita la terminación de un proceso
// (como si el usuario solicitase cerrar la aplicación)

private void buttonClose_Click(object sender, System.EventArgs e) 
{
  Process selected = (Process) listProcesses.SelectedItem;

  if (selected!=null) {

     if (selected.Id==Process.GetCurrentProcess().Id)
         MessageBox.Show("Ha decidido finalizar la aplicación actual");

     selected.CloseMainWindow();
  }
}
</code>

 <link>
   <url>src/Monitor.vs2003.zip</url>
   <icon>zip</icon>
   <text>Descargar código fuente (Visual Studio 2003)</text>
 </link>
 <text></text>
 <link>
   <url>src/Monitor.vs2005.zip</url>
   <icon>zip</icon>
   <text>Descargar código fuente (Visual Studio 2005)</text>
 </link>


</example>

<text>
NOTA: En la barra de herramientas [toolbox], dentro del apartado "Components", podemos encontrar un componente denominado <type>Process</type> que nos permite trabajar con procesos de forma visual.
</text>

</document>


<document>
<tag>process-io</tag>
<title>Operaciones de E/S</title>

<text>
En ocasiones nos interesa que la entrada de un proceso provenga directamente de la salida de otro proceso. Enviar la salida a un fichero y después leer el fichero no siempre es la mejor opción, ya que el uso de "pipes" resulta mucho más eficiente para conectar distintos procesos. La clase <type>Process</type> nos permite redireccionar los canales de E/S estándar (StdIn, StdOut y StdErr). Sólo tenemos que fijar a "true" las propiedades RedirectStandardInput, RedirectStandardOutput y RedirectStandardError, tras lo cual podemos acceder a las propiedades StandardInput, StandardOutput y StandardError del proceso (objetos de tipo StreamReader y StreamWriter).
</text>

<text>
Una observación: <type>Process.Start</type> utiliza por defecto la función ShellExecute del API Win32. Cuando utilicemos redireccionamientos, la propiedad <type>ProcessStartInfo.UseShellExecute</type> debe estar puesta a "false" antes de invocar al método <type>Process.Start</type>.
</text>

<example>
<code>
Process proceso = new Process();

proceso.StartInfo.FileName = "cmd.exe";
proceso.StartInfo.UseShellExecute = false;
proceso.StartInfo.CreateNoWindow = true;
proceso.StartInfo.RedirectStandardInput = true;
proceso.StartInfo.RedirectStandardOutput = true;
proceso.StartInfo.RedirectStandardError = true;
   
proceso.Start()
   
StreamWriter sIn = proceso.StandardInput;
sIn.AutoFlush = true;
   
StreamReader sOut = proceso.StandardOutput;
StreamReader sErr = proceso.StandardError;

sIn.Write("dir c:\" + System.Environment.NewLine);
sIn.Write("exit" + System.Environment.NewLine);

string output = sOut.ReadToEnd();

sIn.Close();
sOut.Close();
sErr.Close();
proceso.Close();

MessageBox.Show(output);
</code>
</example>

<text>
Para los programas que no utilizan StdIn se puede utilizar el método <type>SendKeys</type> para simular la pulsación de teclas:
</text>

<example>
<title>Acceso al Bloc de Notas con SendKeys</title>
<code>
Process proceso = new Process();

proceso.StartInfo.FileName    = "notepad";
proceso.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
proceso.EnableRaisingEvents   = true;
proceso.Start();
   
proceso.WaitForInputIdle(1000); // 1 segundo de timeout

if (proceso.Responding)
   System.Windows.Forms.SendKeys.SendWait
   ("Texto enviado con System.Windows.Forms.SendKeys");
</code>

 <link>
   <url>src/SendKeys.vs2003.zip</url>
   <icon>zip</icon>
   <text>Descargar código fuente (Visual Studio 2003)</text>
 </link>
 <text></text>
 <link>
   <url>src/SendKeys.vs2005.zip</url>
   <icon>zip</icon>
   <text>Descargar código fuente (Visual Studio 2005)</text>
 </link>

</example>

<text>
Cualquier combinación de teclas se puede enviar con <type>SendKeys</type>, lo que permite hacer prácticamente cualquier cosa con una aplicación Windows (siempre que nos aseguremos de que es la aplicación activa, la que tiene el foco).
</text>

<text>
No se pueden utilizar <type>SendKeys</type> hasta que no se haya creado y se visualice la ventana principal de la aplicación, por lo que en el ejemplo de arriba se empleó el método <type>Process.WaitForInputIdle</type>, que espera hasta que la aplicación pueda aceptar entradas por parte del usuario y cuyo funcionamiento es análogo a <type>Process.WaitForExit</type>.
</text>

<example>
<title>Acceso a recursos nativos de Windows</title>

<text>
Con SendKeys se puede simular la pulsación de cualquier combinación de teclas. No
obstante, SendKeys se limita a enviar eventos de pulsación de teclas a la aplicación
que esté activa en cada momento. Y no existe ninguna función estándar en .NET que
nos permita establecer la aplicación activa, pero sí en el API nativo de Windows.
</text>

<text>
El API de Windows incluye una gran cantidad de funciones (no métodos), del orden de
miles. En el caso que nos ocupa, podemos recurrir a las funciones FindWindow y
SetForegroundWindow del API Win32 para establecer la aplicación activa. Como
estas funciones no forman parte de la biblioteca de clases .NET, tenemos que acceder a
ellas usando los servicios de interoperabilidad con COM. Estos servicios son los que
nos permiten acceder a recursos nativos del sistema operativo y se pueden encontrar en
el espacio de nombres System.Runtime.InteropServices.
</text>

<text>
Para poder usar la función SetForegroundWindow, por ejemplo, tendremos que
escribir algo parecido a lo siguiente:
</text>

<code>
using System.Runtime.InteropServices;
...
[DllImport("User32",EntryPoint="SetForegroundWindow")]
private static extern bool SetForegroundWindow(System.IntPtr hWnd);
</code>

<text>
Una vez hecho esto, ya podemos acceder a la función SetForegroundWindow
como si de un método más se tratase:
</text>

<code>
Process proceso = ...

if (proceso.Responding) {
   SetForegroundWindow(proceso.MainWindowHandle);
   SendKeys.SendWait("Sorpresa!!!");
   SetForegroundWindow(this.Handle);
}
</code>

<text>
Y ya podemos hacer prácticamente cualquier cosa con una aplicación Windows,
controlándola desde nuestros propios programas.
</text>

 <link>
   <url>src/SendKeys.Interop.vs2003.zip</url>
   <icon>zip</icon>
   <text>Descargar código fuente (Visual Studio 2003)</text>
 </link>
 <text></text>
 <link>
   <url>src/SendKeys.Interop.vs2005.zip</url>
   <icon>zip</icon>
   <text>Descargar código fuente (Visual Studio 2005)</text>
 </link>
</example>
</document>


</document>

