Colabora |
Instancia única de AplicaciónMecanismo para hacer que una aplicación sólo pueda ejecutarse una vez
Fecha: 16/May/2008 (13-05-08)
|
IntroducciónEn este artículo se describirá una forma sencilla, configurable y útil de aplicar un patrón de instancia única para impedir que una aplicación se ejecute más de una vez en una misma sesión o en un mismo PC.
El requerimiento1.- Se requiere un mecanismo para evitar que se pueda correr una aplicación dos veces en la misma PC. Este bloqueo deberá ser configurable para permitir que sea POR SESIÓN o GLOBAL. POR SESIÓN significa que podrá haber varios usuarios logueados al mismo PC (sesiones) cada quien corriendo una única instancia de la aplicación, pero no podrá correrse más de una instancia de la aplicación en cada sesión de usuario. GLOBAL significa que sólo podrá existir una única instancia de la aplicación en todo el PC, independientemente de los usuarios logueados. Sólo un usuario podrá tener abierta la aplicación en determinado momento. 2.- Además es necesario que, en caso de que exista una instancia de la aplicación corriendo previamente, la ventana principal de esta instancia sea mostrada. Nota: 3.- Deberá funcionar correctamente en los siguientes casos: - Cuando se renombre el archivo ejecutable
Mecanismo de exclusión mutua (Mutex):Para solucionar este problema se utilizará una clase que implemente un mutex de nombre único: El mutex provee un mecanismo de señalización entre procesos que permite, entre otras cosas, determinar si otro proceso creó un mutex con anterioridad de forma atómica. Además, un mutex será cerrado (o abandonado) siempre que se cierre la aplicación. Una forma de crear (abrir) un mutex de señalización en VB.NET es la siguiente: New System.Threading.Mutex(initiallyOwned As Boolean, mutexName As String, createdNew As Boolean) El primer parámetro del contructor (initiallyOwned As Boolean) indica si se requiere que el hilo llamante sea el dueño (owner) del mutex. En este caso será falso, ya que cualquier instancia del programa intentará crear el mutex, y este continuará en su estado inicial durante toda su vida. El segundo parámetro (Name as String) es el nombre que tendrá
el mutex, este nombre puede incluir el prefijo "Global\" o "Local\" según la
visibilidad requerida.
El tercer parámetro (createdNew as Boolean) es un parámetro de salida que contendrá un valor Verdadero en caso de que el mutex haya sido creado por el constructor y devolverá False en caso de que el mutex ya existiera con anterioridad. Por lo tanto con un mutex podría solucionarse la primera
parte del problema haciendo que la aplicación abra un mutex cuyo nombre
sea el nombre del ensamblado y lo mantenga durante toda la ejecución de la
aplicación.
Mostrar la ventana principalPara la segunda parte es necesario un mecanismo para enfocar
y mostrar la ventana principal de un proceso.
Una forma de obtener el ID de la ventana principal de un proceso a partir de su nombre es la siguiente: Dim mainhWnd As System.IntPtr = _ (From p In Process.GetProcessesByName("NombreProceso") _ Where Not p.MainWindowHandle.Equals(IntPtr.Zero) _ Select p.MainWindowHandle).FirstOrDefault Esta instrucción devolverá el identificador de la ventana principal del primer proceso llamado exactamente "NombreProceso". Además se evitan aquellos procesos que no tengan una ventana principal (cuyo MainWindowHandle sea IntPtr.Zero). La función Process.GetProcessesByName(NombreAmistoso As String) devuelve un
arreglo de Process (System.Diagnostics.Process) con todos los procesos
cuyo nombre amistoso sea el pasado como parámetro.
Una vez que se tiene el Windows Handle de la ventana principal, es necesario
decirle al sistema operativo que la muestre y la enfoque. Esto se hará mediante
dos llamadas a la API de Windows (user32.dll), una a la función
SetForegroundWindow y otra a la función ShowWindow. <System.Runtime.InteropServices.DllImport("user32.dll")> _ Private Shared Function ShowWindow(ByVal hWnd As System.IntPtr, ByVal nCmdShow As Integer) As Integer End Function <System.Runtime.InteropServices.DllImport("user32.dll")> _ Private Shared Function SetForegroundWindow(ByVal hWnd As System.IntPtr) As Integer End Function Private Const SW_SHOWMAXIMIZED As Integer = 3 Private Const SW_SHOWNORMAL As Integer = 1 Ambas funciones toman como primer parámetro el Windows Handle de la ventana en cuestión. El segundo parámetro de la función ShowWindow es una constante que indica en qué estado será mostrará la ventana. Como ejemplo se incluyen dos constantes que puede tomar este parámetro: SW_SHOWMAXIMIZED para indicar que la ventana se maximizará antes de mostrarse y SW_SHOWNORMAL para indicar que la ventana se mostrará en estado normal. El código completo de una función que muestra la ventana principal de un proceso (con el mismo nombre que el proceso actual) se transcribe a continuacion: Private Shared Function MostrarVentanaPPalProceso() As Boolean Dim sProcessName As String = Process.GetCurrentProcess.ProcessName 'Apuntador a la ventana ppal del proceso con nombre sProcessName Dim mainhWnd As System.IntPtr = _ (From p In Process.GetProcessesByName(sProcessName) _ Where Not p.MainWindowHandle.Equals(IntPtr.Zero) _ Select p.MainWindowHandle).FirstOrDefault If Not mainhWnd.Equals(IntPtr.Zero) Then 'Muestro la ventana SetForegroundWindow(mainhWnd) ShowWindow(mainhWnd, SW_SHOWNORMAL) Return True Else Return False End If End Function Esta función obtiene el nombre del proceso actual en la variable sProcessName (que será el nombre del ejecutable pero sin la extensión exe) y a continuación obtiene el ID de su ventana principal para mostrarla con las dos llamadas a la API de Windows SetForegroundWindow y ShowWindow. Nota: La solución. Clase clsInstanciaPrevia.A continuación se transcribe el código completo de la clase que implementa este patrón de "instancia única de aplicación": Public Class clsInstanciaPrevia ' Mutex local para sólo permitir una instancia de la aplicación por usuario Private Shared _mutex As System.Threading.Mutex 'API de Windows <System.Runtime.InteropServices.DllImport("user32.dll")> _ Private Shared Function ShowWindow(ByVal hWnd As System.IntPtr, ByVal nCmdShow As Integer) As Integer End Function <System.Runtime.InteropServices.DllImport("user32.dll")> _ Private Shared Function SetForegroundWindow(ByVal hWnd As System.IntPtr) As Integer End Function Private Const SW_SHOWMAXIMIZED As Integer = 3 Private Const SW_SHOWNORMAL As Integer = 1 'Enumerador para inicar el tipo de bloqueo Public Enum eTipo As Integer POR_SESION = 0 [GLOBAL] = 1 End Enum 'Función que devuelve TRUE si ya existe una instancia previa del programa corriendo 'En caso de que la aplicación estuviera corriendo, intenta darle foco a la ventana principal Public Shared Function InstanciaPrevia(ByVal Tipo As eTipo) As Boolean 'Obtengo el nombre del ensamblado donde se encuentra ésta función Dim NombreAssembly As String = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name 'Nombre del mutex según Tipo (visibilidad) Dim mutexName As String = If(Tipo = eTipo.GLOBAL, "Global\", "Local\") & NombreAssembly Dim newMutexCreated As Boolean Try 'Abro/Creo mutex con nombre único _mutex = New System.Threading.Mutex(False, mutexName, newMutexCreated) If newMutexCreated Then 'Se creó el mutex, NO existe instancia previa Return False Else 'El mutex ya existía, Libero el mutex _mutex.Close() 'Intento otorgar el foco al programa ya abierto anteriormente If Not MostrarVentanaPPalProceso() Then 'No se encontró la ventana principal MsgBox("Ya existe una instancia previa del programa corriendo.") End If Return True End If Catch ex As Exception MsgBox(ex.Message) Return False End Try End Function 'Intenta mostrar la ventana principal de los procesos con mi mismo nombre Private Shared Function MostrarVentanaPPalProceso() As Boolean Dim sProcessName As String = Process.GetCurrentProcess.ProcessName 'Apuntador a la ventana ppal del proceso con nombre sProcessName Dim mainhWnd As System.IntPtr = _ (From p In Process.GetProcessesByName(sProcessName) _ Where Not p.MainWindowHandle.Equals(IntPtr.Zero) _ Select p.MainWindowHandle).FirstOrDefault If Not mainhWnd.Equals(IntPtr.Zero) Then 'Muestro la ventana SetForegroundWindow(mainhWnd) ShowWindow(mainhWnd, SW_SHOWNORMAL) Return True Else Return False End If End Function End Class La variable privada _mutex será el mutex que deberá mantenerse abierto hasta el
cierre de la aplicación.
Tanto la variable _mutex como la función InstanciaPrevia son Shared dentro de la clase para que sean únicas dentro de la clase, independientemente de las instancias de ésta. A este tipo de funciones/propiedades se las accede únicamente mediante NombreDeLaClase.NombreMetodo y no está permitido accederlas a través de instancias de la clase. El punto de acceso a esta clase es la función InstanciaPrevia. Utilizando la clase clsInstanciaPreviaA continuación se transcribe el código necesario para utilizar la clase clsInstanciaPrevia dentro de una aplicación Windows Form con un Formulario como objeto de inicio : Private Sub Form1_Load() Handles MyBase.Load If clsInstanciaPrevia.InstanciaPrevia(clsInstanciaPrevia.eTipo.POR_SESION) Then Me.Close() End If End Sub En este caso se cierra el formulario si existe una instancia previa de la aplicación.
Para utilizar la clase clsInstanciaPrevia dentro de una aplicación Windows Form con un Sub Main como objeto de inicio: Otra caso podría ser cuando la aplicación comienza en un Sub Main y la clase clsInstanciaPrevia se encuentra en otro ensamblado (por ejemplo ClassLibrary1). En ese caso, el código es el siguiente: Imports System.Windows.Forms Module Principal Public Sub main() If Not clsInstanciaPrevia.InstanciaPrevia(clsInstanciaPrevia.eTipo.POR_SESION) Then Application.Run(Form1) End If End Sub End Module En este caso sólamente se corre la aplicación cuando la función InstanciaPrevia devuelve Falso. En el código de ejemplo se incluye la clase clsInstanciaPrevia y un ejemplo de uso.
Espero que les sea útil. Espacios de nombres usados en el código de este artículo:System.Windows.Forms
|
Lo comentado en este artículo está probado (y funciona) con la siguiente configuración:
El autor se compromete personalmente de que lo expuesto en este artículo es cierto y lo ha comprobado usando la configuración indicada anteriormente.
En cualquier caso, el Guille no se responsabiliza del contenido de este artículo.
Si encuentras alguna errata o fallo en algún link (enlace), por favor comunícalo usando este link:
Gracias.
Código de ejemplo (comprimido): |
Fichero con el código de ejemplo: thepirat_Instancia_Unica_Aplicacion.zip - 13 KB
|