2009-12-22 18 views
7

Estoy trabajando en una aplicación de la cual solo debe existir una instancia en un momento dado. Hay varias posibilidades de lograr esto:Win32: ¿Cómo obtener el proceso/thread que posee un mutex?

  • Verificar los procesos en ejecución para uno que coincida con el nombre de nuestro EXE (no fiable)
  • Encuentra la ventana principal (no fiable, y no siempre tienen una ventana principal)
  • Cree un mutex con un nombre único (GUID)

La opción mutex me parece la más confiable y elegante.

Sin embargo, antes de que termine mi segunda instancia, deseo enviar un mensaje a la instancia que ya se está ejecutando. Para esto, necesito un control para el hilo (o el proceso) que posee el mutex.

Sin embargo, parece que no hay una función API para obtener el creador/propietario de un mutex determinado. ¿Acabo de pasarlo por alto? ¿Hay alguna otra manera de llegar a este hilo/proceso? ¿Hay alguna otra manera de hacerlo?

actualización: This guy simplemente difundir un mensaje a todos los procesos en ejecución. Supongo que es posible, pero realmente no me gusta ...

+0

¿Duplicado? http://stackoverflow.com/questions/19147/what-is-the-correct-way-to-create-a-single-instance-application –

+0

No realmente. Ninguna de las respuestas me dice cómo manejar el proceso. – Thomas

+0

Aquí hay un duplicado, aunque nunca fue respondido: http://stackoverflow.com/questions/541477/c-how-can-i-get-owners-name-for-a-mutex –

Respuesta

4

no creo hay una forma trivial de resolver el propietario real de un Mutex, pero el proceso que lo posee puede crear otros elementos secundarios cuyas vidas están ligadas a él. Hay muchos mecanismos que son adecuados para devolver la llamada a través del proceso sin tener una ventana principal.

  1. Registre un objeto en la tabla de objetos ejecutables COM. Los clientes que no pueden tomar posesión del Mutex pueden buscar al propietario a través del ROT y devolver la llamada al propietario. Un File Moniker debería ser adecuado para registrarse aquí.
  2. Crea un fragmento de memoria compartida que contiene detalles de ubicación para el proceso de propietario. A partir de ahí, escriba en el búfer el identificador de proceso y el manejador de subprocesos de un subproceso que puede recibir mensajes de Windows, y luego use PostThreadMessage() para enviar una notificación. Cualquier otro proceso competitivo puede abrir la memoria compartida de solo lectura para determinar dónde enviar un mensaje de Windows.
  3. Escuche en el proceso del propietario en un zócalo o tubería con nombre. Probablemente exagerado y no un buen partido para sus necesidades.
  4. Utilice un archivo compartido con bloqueo. No me gusta esto porque el propietario tendrá que sondear, y no manejará con gracia el potencial de N otros procesos que podrían estar tratando de contactar al propietario al mismo tiempo.

Aquí hay enlaces de referencia para las dos primeras opciones.

  1. IRunningObjectTable @ MSDN, File Monikers @ MSDN
  2. Creating Named Shared Memory @ MSDN
2

Nunca he entendido realmente lo racional detrás de usar un Mutex que no tiene capacidad de señalización. En su lugar, crearía un evento (usando CreateEvent) que tiene las mismas propiedades que crear un mutex (es decir, con un nombre puede devolver que el objeto ya existía) pero puede establecer el indicador de evento en el nuevo proceso, siempre que el original proceso está esperando en el indicador de evento, se puede notificar cuando necesita activarse.

+0

Por supuesto, el proceso original no bloquea la espera de un evento; solo está ejecutando círculos en su ciclo de mensajes. (Podría comprobar el mutex cada vez que pasa el ciclo, pero eso me parece mal). – Thomas

+0

Siempre podría ejecutar otro hilo, hacer que duerma en el evento y publicar en el bucle de mensaje cuando ocurra algo. – Thanatos

+2

También hay MsgWaitForMultipleObjects que le permite esperar a que llegue un mensaje de Windows o un identificador waitable (por ejemplo, evento o mutex) que le permita hacerlo en un hilo. – tyranid

1

Siempre se puede hacer de la manera UNIX y crear un archivo "pid", colocando el ID de proceso de la instancia que se está ejecutando actualmente en ese archivo. Luego haga que la aplicación elimine el archivo cuando salga.

Cuando una nueva instancia se pone en marcha se debe verificar que el proceso en el archivo PID es en realidad viva, así (en el caso de la aplicación sale de manera anormal y el archivo no se borran)

+0

+1. Incluso puede usar un bloqueo de archivo en este archivo en lugar de un Mutex por separado. –

+0

Similar a esto, ¿podría usar una memoria compartida y escribir allí el PID? – Thanatos

+0

No es necesario pasar por el sistema de archivos, en realidad ... una pieza de memoria compartida ('CreateFileMapping') funcionará bien. Gracias por ponerme en esta pista. Aceptaré esto a menos que a alguien se le ocurra una mejor solución. – Thomas

10

Esto debe empezar en la solicitud original para obtener un proceso que posee un mutex.

Está en C#, pero las llamadas de Win32 son las mismas.

class HandleInfo 
{ 
    [DllImport("ntdll.dll", CharSet = CharSet.Auto)] 
    public static extern uint NtQuerySystemInformation(int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, out int ReturnLength); 

    [DllImport("kernel32.dll", SetLastError = true)] 
    internal static extern IntPtr VirtualAlloc(IntPtr address, uint numBytes, uint commitOrReserve, uint pageProtectionMode); 

    [DllImport("kernel32.dll", SetLastError=true)] 
    internal static extern bool VirtualFree(IntPtr address, uint numBytes, uint pageFreeMode); 

    [StructLayout(LayoutKind.Sequential)] 
    public struct SYSTEM_HANDLE_INFORMATION 
    { 
     public int ProcessId; 
     public byte ObjectTypeNumber; 
     public byte Flags; // 1 = PROTECT_FROM_CLOSE, 2 = INHERIT 
     public short Handle; 
     public int Object; 
     public int GrantedAccess; 
    } 

    static uint MEM_COMMIT = 0x1000; 
    static uint PAGE_READWRITE = 0x04; 
    static uint MEM_DECOMMIT = 0x4000; 
    static int SystemHandleInformation = 16; 
    static uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; 

    public HandleInfo() 
    { 
     IntPtr memptr = VirtualAlloc(IntPtr.Zero, 100, MEM_COMMIT, PAGE_READWRITE); 

     int returnLength = 0; 
     bool success = false; 

     uint result = NtQuerySystemInformation(SystemHandleInformation, memptr, 100, out returnLength); 
     if (result == STATUS_INFO_LENGTH_MISMATCH) 
     { 
      success = VirtualFree(memptr, 0, MEM_DECOMMIT); 
      memptr = VirtualAlloc(IntPtr.Zero, (uint)(returnLength + 256), MEM_COMMIT, PAGE_READWRITE); 
      result = NtQuerySystemInformation(SystemHandleInformation, memptr, returnLength, out returnLength); 
     } 

     int handleCount = Marshal.ReadInt32(memptr); 
     SYSTEM_HANDLE_INFORMATION[] returnHandles = new SYSTEM_HANDLE_INFORMATION[handleCount]; 

     using (StreamWriter sw = new StreamWriter(@"C:\NtQueryDbg.txt")) 
     { 
      sw.WriteLine("@ Offset\tProcess Id\tHandle Id\tHandleType"); 
      for (int i = 0; i < handleCount; i++) 
      { 
       SYSTEM_HANDLE_INFORMATION thisHandle = (SYSTEM_HANDLE_INFORMATION)Marshal.PtrToStructure(
        new IntPtr(memptr.ToInt32() + 4 + i * Marshal.SizeOf(typeof(SYSTEM_HANDLE_INFORMATION))), 
        typeof(SYSTEM_HANDLE_INFORMATION)); 
       sw.WriteLine("{0}\t{1}\t{2}\t{3}", i.ToString(), thisHandle.ProcessId.ToString(), thisHandle.Handle.ToString(), thisHandle.ObjectTypeNumber.ToString()); 
      } 
     } 

     success = VirtualFree(memptr, 0, MEM_DECOMMIT); 
    } 
} 
+0

"NtQuerySystemInformation puede estar alterado o no disponible en futuras versiones de Windows. Las aplicaciones deben usar las funciones alternativas enumeradas en este tema". Y SystemHandleInformation está completamente indocumentado. ¡Ay! – Thomas

+8

En realidad, solo estaba tratando de ayudarte.Tuve que desenterrar parte de mi código esotérico de C++ desde hace mucho tiempo, pero en lugar de volver al VC6, decidí portarlo a C# para ti y para los demás (para hacerlo un poco más actual). Supongo que no sabía que no podía usar cosas no documentadas, solo me preguntó si era posible ... – GalacticJello

+0

Desde este código, entonces iría y llamaría 'NtQueryMutant' para obtener el estado del propietario y el PID/ThreadID del propietario. –

2

Crear un área de memoria compartida con el nombre fijo:

http://msdn.microsoft.com/en-us/library/aa366551%28VS.85%29.aspx

entonces usted puede poner cualquier estructura te gusta el interior, incluyendo identificador de proceso, etc. HWND

Hay una opción portátil: crea un socket en un puerto (con un número fijo) y espera (acepta) en él. La segunda instancia de la aplicación fallará ya que el puerto ya está ocupado. Luego, la segunda instancia puede conectarse al socket de la instancia principal y enviar la información deseada.

Espero que esto ayude ...

+0

En general, los enchufes son malos para esto. Requieren que un número de puerto esté codificado; ¿Qué pasa si este número ya estaría ocupado? La creación de una conexión de escucha también hace que una aplicación sea muy sospechosa si sus funciones no lo requieren. Consideraría, por ejemplo, el bloc de notas que acepta conexiones entrantes con un software 100% malicioso. Siempre que se tenga en cuenta el software del usuario final, la comunicación del socket está bien solo para aplicaciones específicas como el servidor de FileZilla y la interfaz. Por supuesto, el software empresarial podría requerir cualquier opción que desee. Recomiendo tubos con nombre para esto. – Fr0sT

Cuestiones relacionadas