2009-12-22 5 views
7

Je travaille sur une application dont une seule instance doit exister à un moment donné. Il existe plusieurs possibilités pour y parvenir:Win32: Comment obtenir le processus/thread qui possède un mutex?

  • Vérifiez les processus en cours d'exécution pour un correspondant à notre nom EXE (peu fiable)
  • Trouver la fenêtre principale (peu fiable, et je n'ai pas toujours une fenêtre principale)
  • Créer un mutex avec un nom unique (GUID)

L'option mutex me semble la plus fiable et élégante.

Toutefois, avant la fin de ma seconde instance, je souhaite publier un message sur l'instance en cours d'exécution. Pour cela, j'ai besoin d'un handle pour le thread (ou le processus) qui possède le mutex.

Cependant, il ne semble pas y avoir de fonction API pour obtenir le créateur/propriétaire d'un mutex donné. Suis-je juste en train de le négliger? Y a-t-il un autre moyen d'accéder à ce fil/processus? Y a-t-il un autre moyen d'y parvenir?

Mise à jour: This guy simplement diffuser un message à tous les processus en cours d'exécution. Je suppose que c'est possible, mais je ne l'aime pas vraiment ...

+0

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

+0

Pas vraiment. Aucune des réponses ne me dit comment gérer le processus. – Thomas

+0

Voici un doublon, mais il n'a jamais été répondu: http://stackoverflow.com/questions/541477/c-how-can-i-get-owners-name-for-a-mutex –

Répondre

4

Je ne pense pas Il existe un moyen trivial de résoudre le propriétaire actuel d'un Mutex, mais le processus qui le possède peut créer d'autres éléments secondaires dont la durée de vie est liée à celui-ci. Il existe de nombreux mécanismes qui permettent de rappeler des processus croisés sans avoir de fenêtre principale.

  1. Enregistrez un objet dans la table COM Running Object. Les clients qui ne sont pas en mesure de prendre possession du Mutex peuvent rechercher le propriétaire via le ROT et rappeler le propriétaire. Un fichier Moniker devrait être approprié pour l'enregistrement ici.
  2. Créez un morceau de mémoire partagée contenant les détails d'emplacement pour le processus propriétaire. De là, écrivez dans le tampon le handle de processus et le handle de thread d'un thread pouvant recevoir des messages Windows, puis utilisez PostThreadMessage() pour envoyer une notification. Tout autre processus concurrent peut ouvrir la mémoire partagée en lecture seule pour déterminer où envoyer un message Windows.
  3. Écoutez le processus du propriétaire sur un socket ou un tube nommé. Probablement trop et pas un bon match pour vos besoins.
  4. Utilisez un fichier partagé avec verrouillage. Je ne suis pas friand de ceci parce que le propriétaire devra interroger, et il ne manipulera pas gracieusement N potentiels d'autres processus qui pourraient essayer de contacter le propriétaire en même temps.

Voici les liens de référence pour les deux premières options.

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

Je n'ai jamais vraiment compris la raison derrière l'utilisation d'un Mutex qui n'a pas de capacité de signalisation. Je créerais plutôt un événement (using CreateEvent) qui a les mêmes propriétés que créer un mutex (c.-à-d. Avec un nom qu'il peut retourner que l'objet existait déjà) mais vous pouvez placer l'indicateur d'événement dans le nouveau processus, aussi longtemps que l'original le processus attend sur l'indicateur d'événement qu'il peut être averti quand il doit se réveiller.

+0

Bien sûr, le processus d'origine ne bloque pas l'attente d'un événement; c'est juste des cercles dans sa boucle de messages. (Je pourrais vérifier le mutex chaque fois à travers la boucle, mais cela semble mal.) – Thomas

+0

Vous pouvez toujours exécuter un autre thread, le faire dormir sur l'événement, et poster à la boucle de message quand quelque chose se passe. – Thanatos

+2

Il y a aussi MsgWaitForMultipleObjects qui vous permet d'attendre qu'un message Windows arrive ou qu'un descripteur (wait et event ou mutex) soit signalé vous permettant de le faire dans un thread. – tyranid

1

Vous pouvez toujours le faire de la manière UNIX et créer un fichier "pid", en plaçant l'ID de processus de l'instance en cours d'exécution dans ce fichier. Ensuite, demandez à l'application de supprimer le fichier à sa fermeture.

Lorsqu'une nouvelle instance démarre, il doit vérifier que le processus dans le fichier PID est en fait vivant aussi bien (dans le cas où l'application se termine anormalement et le fichier ne soit pas supprimé)

+0

+1. Vous pouvez même utiliser un verrou de fichier sur ce fichier au lieu d'un Mutex distinct. –

+0

Similaire à cela, pourriez-vous utiliser une mémoire partagée, et y écrire le PID? – Thanatos

+0

Pas besoin de passer par le système de fichiers, en fait ... un morceau de mémoire partagée ('CreateFileMapping') fera très bien l'affaire. Merci de m'avoir mis sur cette piste. Je vais accepter cela à moins que quelqu'un ne propose une meilleure solution. – Thomas

10

Cela devrait vous aider à démarrer sur la demande initiale pour obtenir un processus qui est propriétaire d'un mutex.

Il est en C#, mais les appels Win32 sont les mêmes.

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 peut être modifié ou indisponible dans les futures versions de Windows Les applications doivent utiliser les autres fonctions répertoriées dans cette rubrique." Et SystemHandleInformation est entièrement non documenté. Yikes! – Thomas

+8

En fait, j'essayais juste de vous aider.J'ai dû remonter un peu de mon code C++ ésotérique depuis longtemps, mais au lieu de retourner dans VC6, j'ai décidé de le porter en C# pour vous et les autres (pour le rendre un peu plus courant). Je suppose que je ne savais pas que vous ne pouviez pas utiliser de documents non documentés, vous avez juste demandé si c'était possible ... – GalacticJello

+0

De ce code, vous iriez alors appeler 'NtQueryMutant' pour obtenir le statut de propriétaire et le PID/ThreadID du propriétaire. –

2

Créer une zone de mémoire partagée avec le nom fixe:

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

Ensuite, vous pouvez mettre une structure que vous aimez à l'intérieur, y compris l'ID de processus, HWND etc.

Il existe une option portable: créer une socket sur un port (avec un nombre fixe) et attendre (accepter) sur elle. La deuxième instance de l'application échouera puisque le port est déjà pris. Ensuite, la seconde instance peut se connecter au socket de l'instance principale et envoyer toute information désirée. Je souhaite que cela aide ...

+0

En général, les prises sont mauvaises pour cela. Ils nécessitent un numéro de port à coder en dur; Et si ce numéro était déjà occupé? Créer une connexion d'écoute rend également une application très suspecte si ses fonctions ne l'exigent pas. Je pense, par exemple, au bloc-notes qui accepte les connexions entrantes d'un logiciel malveillant à 100%. Tant que le logiciel de l'utilisateur final est pris en compte, la communication par socket est OK uniquement pour des applications spécifiques comme le serveur et l'interface FileZilla. Bien sûr, les logiciels d'entreprise peuvent nécessiter n'importe quelle option. Je recommanderais des tuyaux nommés pour cela. – Fr0sT

Questions connexes