Question

Il y a un problème, je suis incapable de résoudre. J'ai créé deux applications de service dans Delphi et essayé de poster des messages en leur sein. Bien sûr, il n'y a pas de fenêtres dans de telles applications et PostMessage a besoin d'un paramètre de poignée de fenêtre pour envoyer un message.

Par conséquent, je créé une poignée de fenêtre en utilisant la AllocateHWnd (MyMethod: TWndMethod) fonction et passé, comme le paramètre « MyMethod », une procédure que je veux être appelé lorsqu'un message est reçu. Si elle était une application fenêtrée, PostMessage () appelé en utilisant la poignée renvoyée par la méthode AllocateHWnd certainement envoyer un message qui serait ensuite reçu par la procédure « NomMéthode ».

La situation est cependant différente dans mes applications de service. Je ne comprends pas pourquoi, mais dans l'un d'eux l'affichage de messages de cette façon fonctionne très bien, alors que dans le second, il n'a pas (les messages ne sont pas reçus du tout). Seulement lorsque le service est arrêté dois-je remarque que deux messages sont reçus par « MyMethod »: WM_DESTROY et WM_NCDESTROY. Les messages envoyés à l'aide PostMessage ne sont jamais reçus par cette procédure. D'autre part, le premier service reçoit toujours tous les messages envoyés.

Pourriez-vous s'il vous plaît me donner un indice qui me aider à trouver la raison du deuxième service ne reçois pas mes messages? Je ne sais pas de quelle façon ils peuvent différer. J'ai vérifié les paramètres des services et ils semblent être identiques. Pourquoi alors l'un d'eux fonctionne très bien et le second n'a pas (pour autant que l'envoi de messages est concerné)?

Merci pour tout conseil. Mariusz.

Était-ce utile?

La solution

Sans plus d'informations, il sera difficile de vous aider à résoudre ce, en particulier pourquoi il travaille dans un service, mais pas dans l'autre. Cependant:

Au lieu d'essayer de résoudre le problème dans votre code, vous pouvez supprimer les fenêtres, et utilisent PostThreadMessage () au lieu de PostMessage (). Pour l'affichage des messages fonctionne correctement, vous avez besoin d'une boucle de message, mais pas nécessairement fenêtres de réception.

Edit:. Je suis en train de répondre à toutes vos réponses en une seule fois

D'abord - si vous voulez vous rendre la vie facile, vous devriez vraiment consulter OmniThreadLibrary par Gabr . Je ne sais pas si cela ne fonctionne dans une application de service Windows, je ne sais même pas si cela a encore été jugé. Vous pouvez demander au forum. Il a cependant beaucoup de fonctionnalités, et mérite d'être étudiée, si seulement pour l'effet d'apprentissage.

Mais bien sûr, vous pouvez également programmer par vous-même, et vous devrez pour les versions Delphi avant Delphi 2007. Je vais simplement ajouter quelques extraits de notre bibliothèque interne, qui a évolué au fil des années et travaille dans plusieurs dizaines de programmes . Je ne prétends pas que ce soit sans bug cependant. Vous pouvez le comparer avec votre code, et si quelque chose dépasse, ne hésitez pas à demander et je vais essayer de clarifier.

Ceci est la procédure simplifiée Execute () méthode de la classe de base de thread de travail:

procedure TCustomTestThread.Execute;
var
  Msg: TMsg;
begin
  try
    while not Terminated do begin
      if (integer(GetMessage(Msg, HWND(0), 0, 0)) = -1) or Terminated then
        break;
      TranslateMessage(Msg);
      DispatchMessage(Msg);

      if Msg.Message = WM_USER then begin
        // handle differently according to wParam and lParam
        // ...
      end;
    end;
  except
    on E: Exception do begin
      ...
    end;
  end;
end;

Il est important de ne pas laisser des exceptions se unhandled, donc il est un gestionnaire d'exceptions de haut niveau autour de tout. Ce que vous faites à l'exception est votre choix et dépend de l'application, mais toutes les exceptions doivent être pris, sinon l'application se termine. Dans un service votre seule option est probablement les log.

Il existe une méthode spéciale pour provoquer l'arrêt de fil, car le fil doit être réveillé quand il est à l'intérieur de GetMessage () :

procedure TCustomTestThread.Shutdown;
begin
  Terminate;
  Cancel; // internal method dealing with worker objects used in thread
  DoSendMessage(WM_QUIT);
end;

procedure TCustomTestThread.DoSendMessage(AMessage: Cardinal;
  AWParam: integer = 0; ALParam: integer = 0);
begin
  PostThreadMessage(ThreadID, AMessage, AWParam, ALParam);
end;

Affichage WM_QUIT provoquera la boucle de message pour quitter. Il y a cependant le problème que le code dans les classes descendant pouvait compter sur les messages Windows correctement traités lors de l'arrêt du fil, en particulier lorsque les interfaces COM sont utilisées. Voilà pourquoi au lieu d'un simple, WaitFor () le code suivant est utilisé pour libérer tous les threads en cours d'exécution:

procedure TCustomTestController.BeforeDestruction;
var
  i: integer;
  ThreadHandle: THandle;
  WaitRes: LongWord;
  Msg: TMsg;
begin
  inherited;
  for i := Low(fPositionThreads) to High(fPositionThreads) do begin
    if fPositionThreads[i] <> nil then try
      ThreadHandle := fPositionThreads[i].Handle;
      fPositionThreads[i].Shutdown;
      while TRUE do begin
        WaitRes := MsgWaitForMultipleObjects(1, ThreadHandle, FALSE, 30000,
          QS_POSTMESSAGE or QS_SENDMESSAGE);
        if WaitRes = WAIT_OBJECT_0 then begin
          FreeAndNil(fPositionThreads[i]);
          break;
        end;
        if WaitRes = WAIT_TIMEOUT then
          break;

        while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do begin
          TranslateMessage(Msg);
          DispatchMessage(Msg);
        end;
      end;
    except
      on E: Exception do
        // ...
    end;
    fPositionThreads[i] := nil;
  end;
end;

Ceci est dans la substitution BeforeDestruction () méthode parce que tous les fils doivent être libérés avant la Destructeur de la classe de contrôleur descendant commence à libérer des objets les fils pourraient utiliser.

Autres conseils

Je vous suggère de vous envisager d'utiliser les canaux nommés pour IPC. C'est ce qu'ils sont conçus pour faire:

Recherche une alternative aux messages de fenêtres utilisées dans la communication inter-processus

Comme Mghie mentionné, vous avez besoin d'une boucle de traitement des messages. Voilà pourquoi PeekMessage renvoie les messages correctement. Ce n'est pas que les messages ne sont pas là, il est que vous n'êtes pas les traiter. Dans une application standard, Delphi crée une classe TApplication et appelle Application.Run. C'est la boucle de traitement de message pour une application normale. Il se compose essentiellement de:

  repeat
    try
      HandleMessage;
    except
      HandleException(Self);
    end;
  until Terminated;

Si vous voulez que votre application de service pour gérer les messages, vous devez effectuer le même genre de travail.

Il y a un exemple d'utilisation d'un service de manutention et PostThreadMessage envoie ici . Gardez à l'esprit, comme Mick mentionné, vous ne pouvez pas utiliser le traitement des messages entre les applications des différents contextes de sécurité (en particulier dans Vista). Vous devez utiliser des canaux nommés ou similaires. Microsoft discute cette .

Edit:

Basé sur le extrait de code que vous avez publié, vous pouvez juste combattrez un problème de filetage. AllocHWnd est pas sûre. Voir pour vraiment détaillée explication de la question et une version qui fonctionne correctement dans les discussions.

Bien sûr, cela nous amène toujours revenir à la raison pour laquelle vous n'utilisez pas PostThreadMessage à la place. La façon dont votre exemple de code est structuré, il serait trivial de faire le traitement des messages en fonction du fil et de passer ensuite vers le bas dans la classe de disposition.

Merci pour toutes vos réponses. Je pense que nous pouvons oublier le problème. J'ai créé une nouvelle application de service et a effectué des tests post rapides de message. Les messages ont été livrés correctement, donc j'espère que je peux maintenant l'état qui, normalement, tout fonctionne bien et quelque chose qui ne va pas seulement celui-service que je décrit. Je sais qu'il est stupide, mais je vais juste essayer de copier un fragment de code après l'autre du service « mauvais » à un nouveau. Peut-être cela me aidera à trouver la raison du problème.

J'espère que je peux maintenant considérer la boucle de message en attente inutile aussi longtemps que tout fonctionne bien sans elle, Je ne peux pas?

Si ce qui concerne les privilèges, Microsoft dit: « utilise UAC WIM pour bloquer les messages Windows à partir d'être envoyés entre les processus de différents niveaux de privilège. ». UAC Ma Vista est éteint et je ne mis aucun privilège pour les services que je décrits. En dehors de cela, je ne pas envoyer des messages entre les différents processus. Les messages sont envoyés au sein d'un processus.

Pour vous donner une idée de ce que je fais, je vais vous montrer un extrait de code à partir d'une application de service de test.

uses ...;

type
  TMyThread = class;
  TMyClass = class
  private
    FThread: TMyThread;
    procedure ReadMessage(var Msg: TMessage);
  public
    FHandle: HWND;
    constructor Create;
    destructor Destroy; override;
  end;

  TMyThread = class(TThread)
  private
    FMyClass: TMyClass;
  protected
    procedure Execute; override;
    constructor Create(MyClass: TMyClass); reintroduce;
  end;

implementation

{ TMyClass }

constructor TMyClass.Create;
begin
  inherited Create;
  FHandle := AllocateHWnd(ReadMessage);
  FThread := TMyThread.Create(Self);
end;

destructor TMyClass.Destroy;
begin
  FThread.Terminate;
  FThread.WaitFor;
  FThread.Free;
  DeallocateHWnd(FHandle);
  inherited Destroy;
end;

procedure TMyClass.ReadMessage(var Msg: TMessage);
begin
  Log.Log('message read: ' + IntToStr(Msg.Msg));
end;

{ TMyThread }

constructor TMyThread.Create(MyClass: TMyClass);
begin
  inherited Create(True);
  FMyClass := MyClass;
  Resume;
end;

procedure TMyThread.Execute;
begin
  while not Terminated do
  begin
    //do some work and
    //send a message when finished
    if PostMessage(FMyClass.FHandle, WM_USER, 0, 0) then
      Log.Log('message sent')
    else
      Log.Log('message not sent: ' + SysErrorMessage(GetLastError));
    //do something else...
    Sleep(1000);
  end;
end;

Ceci est seulement un exemple, mais le fonctionnement de mes bases réelles de code sur la même idée. Lorsque vous créez un objet de cette classe, il va créer un thread qui commencera à envoyer des messages à cette classe. Log.log () enregistre les données dans un fichier texte. Lorsque j'utilise ce code dans une application de service, tout fonctionne bien. Quand je l'ai mis dans le service « cassé », il ne fonctionne pas. S'il vous plaît noter que je n'utilise pas la boucle de messages en attente de recevoir des messages. J'ai créé un nouveau service et vient de mettre le code ci-dessus dans, puis créé un objet de la classe. C'est tout.

Si je savoir pourquoi cela ne fonctionne pas au service cassé, je vais écrire à ce sujet.

Merci pour le temps que vous me as consacré.

Mariusz.

Voici ce que je voudrais essayer:

  • Vérifiez la valeur de retour et GetLastError de PostMessage
  • Est-ce une Vista / machine à 2008? Si oui, vérifiez si l'application d'envoi ont priviliges suffisantes pour faire envoyer le message.

Je dois avoir plus d'informations pour vous aider.

J'ai passé de longues heures à essayer de trouver la raison des messages non reçus. Comme je l'ai montré dans mon extrait de code, le constructeur de la classe crée une poignée de fenêtre que je l'habitude d'envoyer des messages. Tant que la classe a été construit par le fil conducteur, tout a bien fonctionné pour la poignée de fenêtre (si je comprends bien) existait dans le contexte du thread principal qui, par défaut, attend des messages. Dans le service « cassé », comme je l'ai appelé par erreur, ma classe a été créé par un autre thread, de sorte que la poignée doit exister dans le contexte de ce fil. Par conséquent, quand j'ai envoyé des messages à l'aide de cette poignée de fenêtre, ils ont été reçus par ce fil, et non par le principal. En raison du fait que ce fil n'avait pas de boucle de messages en attente, mes messages ne sont pas reçus du tout. Je ne savais pas travaillé de cette façon. Pour résoudre le problème de manière simple, créer et détruire la classe dans le thread principal, même si je l'utilise dans le second.

Merci pour votre temps et toutes les informations que vous me donniez.

Mghie, je pense que vous êtes tout à fait raison. Je mis en œuvre un message boucle d'attente de cette façon:

procedure TAsyncSerialPort.Execute;
var
  Msg: tagMSG;
begin
  while GetMessage(Msg, 0, 0, 0) do
  begin
    {thread message}
    if Msg.hwnd = 0 then
    begin
      case Msg.message of
        WM_DATA_READ: Log.Log('data read');
        WM_READ_TIMEOUT: Log.Log('read timeout');
        WM_DATA_WRITTEN: Log.Log('data written');
        WM_COMM_ERROR: Log.Log('comm error');
      else
        DispatchMessage(Msg);
      end;
    end
    else
      DispatchMessage(Msg);
  end;
end;

Je le fais pour la première fois, alors s'il vous plaît, pourriez-vous vérifier le code que ce soit correct? En fait, ceci est mon vrai extrait de code de classe (les journaux seront remplacés par un code réel). Il gère le port de comm chevauchée. Il y a deux fils qui envoient des messages de fil au fil ci-dessus, l'informant qu'ils ont écrit ou reçu des données du port de communication, etc. Lorsque le thread reçoit un tel message, il faut une action - il obtient les données reçues à partir d'une file d'attente, où les fils d'abord mis, puis appelle une méthode externe, permet de dire, analyse les données reçues. Je ne veux pas entrer dans les détails car il est sans importance :). J'envoyer des messages de fil comme celui-ci. PostThreadMessage (MyThreadId, WM_DATA_READ, 0, 0)

Ce code fonctionne correctement comme je l'ai vérifié, mais je voudrais être sûr que tout est correct, alors je vous demande à ce sujet. Je vous serais reconnaissant si vous avez répondu.

Pour libérer le fil que je fais ce qui suit:

destructor TAsyncSerialPort.Destroy;
begin
  {send a quit message to the thread so that GetMessage returns false and the loop ends}
  PostThreadMessage(ThreadID, WM_QUIT, 0, 0);

  {terminate the thread but wait until it finishes before the following objects 
  (critical sections) are destroyed for the thread might use them before it quits}
  Terminate;
  if Suspended then
    Resume;
  WaitFor;

  FreeAndNil(FLock);
  FreeAndNil(FCallMethodsLock);
  inherited Destroy;
end;

J'espère que cela est la bonne façon de mettre fin à la boucle de message.

Merci pour votre aide.

BTW, j'espère que ma langue anglaise est compréhensible, non? :) Désolé si vous avez des difficultés à me comprendre.

Il y a un truc dans le message des boucles dans les discussions. Windows ne crée pas une file d'attente de messages pour un fil immédiatement donc il y aura un peu de temps lors de l'envoi de messages à un fil échouera. Les détails sont . Dans mon fil boucle msg J'utilise la technique MS propose:

constructor TMsgLoopThread.Create;
begin
  inherited Create(True);
  FEvMsgQueueReady := CreateEvent(nil, True, False, nil);
  if FEvMsgQueueReady = 0 then
    Error('CreateEvent: '+LastErrMsg);
end;

procedure TMsgLoopThread.Execute;
var
  MsgRec: TMsg;
begin
  // Call fake PeekMessage for OS to create message queue for the thread.
  // When it finishes, signal the event. In the main app execution will wait
  // for this event.
  PeekMessage(MsgRec, 0, WM_USER, WM_USER, PM_NOREMOVE);
  SetEvent(FEvMsgQueueReady);

  ...
end;

// Start the thread with waitinig for it to get ready
function TMsgLoopThread.Start(WaitInterval: DWORD): DWORD;
begin
  inherited Start;
  Result := WaitForSingleObject(FEvMsgQueueReady, WaitInterval);
end;

Mais dans votre cas, je vous recommande fortement d'utiliser d'autres moyens de l'IPC.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top