1

解決できない問題があります。Delphi で 2 つのサービス アプリケーションを作成し、それらにメッセージを投稿しようとしました。もちろん、そのようなアプリケーションにはウィンドウがなく、PostMessage はメッセージを送信するためにウィンドウ ハンドル パラメータを必要とします。

そのため、AllocateHWnd(MyMethod: TWndMethod) 関数を使用してウィンドウ ハンドルを作成し、'MyMethod' パラメーターとして、メッセージを受信したときに呼び出したいプロシージャを渡しました。ウィンドウ化されたアプリケーションの場合、AllocateHWnd メソッドによって返されたハンドルを使用して呼び出された PostMessage() は、'MyMethod' プロシージャによって受信されるメッセージを確実に送信します。

ただし、私のサービス アプリケーションでは状況が異なります。理由はわかりませんが、そのうちの 1 つはこの方法でメッセージを投稿するとうまくいきますが、2 番目の投稿ではそうではありません (メッセージがまったく受信されません)。サービスが停止しているときにのみ、'MyMethod' が WM_DESTROY と WM_NCDESTROY という 2 つのメッセージを受信して​​いることに気付きます。PostMessage を使用して送信したメッセージは、この手順では受信されません。一方、最初のサービスは、私が送信したすべてのメッセージを常に受信します。

2 番目のサービスでメッセージが受信されない理由を見つける手がかりを教えてください。それらがどのように異なるかはわかりません。サービスの設定を確認しましたが、同じようです。では、(メッセージの送信に関する限り) そのうちの 1 つが正常に機能し、2 つ目が機能しないのはなぜですか?

アドバイスをありがとう。マリウス。

4

8 に答える 8

3

より多くの情報がなければ、これをデバッグするのを助けるのは難しいでしょう、特にそれが一方のサービスでは機能するがもう一方のサービスでは機能しない理由。でも:

コードの問題を修正しようとする代わりに、ウィンドウを完全に削除し、PostMessage()の代わりにPostThreadMessage()を使用することをお勧めします。メッセージの投稿が正しく機能するには、メッセージループが必要ですが、必ずしもウィンドウを受信する必要はありません。

編集:私はあなたのすべての答えに一度に返信しようとしています。

まず、生活を楽にしたい場合は、gabrのOmniThreadLibraryを実際にチェックする必要あります。それがWindowsサービスアプリケーションで機能するかどうかはわかりません。それがまだ試されているかどうかさえわかりません。あなたはフォーラムで尋ねることができます。ただし、多くの優れた機能があり、学習効果のためだけに検討する価値があります。

ただし、もちろん、これを自分でプログラムすることもできます。Delphi2007より前のDelphiバージョンをプログラムする必要があります。長年にわたって進化し、数十のプログラムで機能する内部ライブラリからスニペットを追加するだけです。しかし、バグがないとは言いません。あなたはそれをあなたのコードと比較することができます、そして何かが目立つならば、遠慮なく尋ねてください、そして私は明確にするように努めます。

これは、ワーカースレッドの基本クラスの簡略化されたExecute()メソッドです。

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;

例外が処理されないようにしないことが重要であるため、すべての周りにトップレベルの例外ハンドラーがあります。例外を使用して行うことは選択であり、アプリケーションによって異なりますが、すべての例外をキャッチする必要があります。そうしないと、アプリケーションが終了します。サービスでは、おそらくあなたの唯一のオプションはそれらをログに記録することです。

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;

WM_QUITを投稿すると、メッセージループが終了します。ただし、子孫クラスのコードは、特にCOMインターフェイスが使用されている場合、スレッドのシャットダウン中にWindowsメッセージが適切に処理されることに依存する可能性があるという問題があります。そのため、単純なWaitFor()の代わりに、実行中のすべてのスレッドを解放するために次のコードが使用されます。

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;

子孫コントローラークラスのデストラクタがスレッドが使用する可能性のあるオブジェクトの解放を開始する前に、すべてのスレッドを解放する必要があるため、これはオーバーライドされたBeforeDestruction()メソッドにあります。

于 2009-02-24T15:14:41.510 に答える
2

Mghie が述べたように、メッセージ処理ループが必要です。そのため、PeekMessage はメッセージを正しく返します。メッセージが存在しないのではなく、メッセージを処理していないのです。標準アプリケーションでは、Delphi は TApplication クラスを作成し、Application.Run を呼び出します。これは、通常のアプリのメッセージ処理ループです。それは基本的に次のもので構成されています。

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

サービス アプリケーションでメッセージを処理する場合は、同じ種類の作業を実行する必要があります。

サービスを使用して PostThreadMessage ディスパッチを処理する例がここにあります。Mick が述べたように、異なるセキュリティ コンテキストのアプリケーション間でメッセージ処理を使用することはできません (特に Vista では)。名前付きパイプなどを使用する必要があります。Microsoft はこれについてここで説明しています。

編集:

投稿したコード スニペットに基づいて、スレッドの問題と戦っている可能性があります。AllocHWnd はスレッドセーフではありません。この問題の詳細な説明と、スレッドで正しく動作するバージョンについては、こちらを参照してください。

もちろん、これは、代わりに PostThreadMessage を使用しない理由に私たちを引き戻します。コード サンプルが構造化されている方法では、スレッドの関数を処理するメッセージを作成し、処理のためにクラスに渡すのは簡単です。

于 2009-02-24T17:38:23.257 に答える
2

IPC に名前付きパイプを使用することを検討することをお勧めします。それが彼らがするように設計されていることです:

プロセス間通信で使用される Windows メッセージの代替を探しています

于 2009-02-24T15:48:29.947 に答える
1

ご回答ありがとうございます。私たちはその問題を忘れることができると思います。新しいサービス アプリケーションを作成し、簡単なポスト メッセージ テストを実行しました。メッセージは正しく配信されたので、通常はすべて正常に動作し、説明したこの 1 つのサービスだけに問題があると言えます。ばかげていることはわかっていますが、「悪い」サービスから新しいサービスにコードの断片を次々とコピーしようとします。たぶん、これは問題の原因を見つけるのに役立ちます。

メッセージ待機ループがなくてもすべてが正常に機能する限り、メッセージ待機ループは不要であると考えることができれば幸いです。

特権に関しては、マイクロソフトは次のように述べています。「UAC は WIM を使用して、異なる特権レベルのプロセス間で Windows メッセージが送信されるのをブロックします。」. 私の Vista の UAC はオフになっており、説明したサービスに対して権限を設定していません。それとは別に、異なるプロセス間でメッセージを送信しません。メッセージは 1 つのプロセス内で送信されます。

私が何をしているのかを理解していただくために、テスト サービス アプリケーションのコード スニペットをお見せします。

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;

これは単なる例ですが、実際のコードの機能は同じ考えに基づいています。このクラスのオブジェクトを作成すると、そのクラスへのメッセージの送信を開始するスレッドが作成されます。Log.Log() は、データをテキスト ファイルに保存します。このコードを新しいサービス アプリケーションで使用すると、すべて正常に動作します。「壊れた」サービスに入れても、そうではありません。メッセージを受信するためにメッセージ待機ループを使用していないことに注意してください。新しいサービスを作成し、上記のコードをその中に入れてから、クラスのオブジェクトを作成しました。それで全部です。

これが「壊れた」サービスで機能しない理由がわかったら、それについて書きます。

時間を割いてくれてありがとう。

マリウス。

于 2009-02-24T18:48:28.830 に答える
0

これが私が試すことです:

  • PostMessageの戻り値とGetLastErrorを確認してください
  • これはVista/2008マシンですか?はいの場合、送信アプリケーションにメッセージを送信するのに十分な権限があるかどうかを確認します。

私はあなたをさらに助けるためにもっと多くの情報を持っている必要があります。

于 2009-02-24T15:15:52.520 に答える
0

スレッド内のメッセージ ループには、1 つのトリックがあります。Windows は、スレッドのメッセージ キューをすぐには作成しないため、スレッドへのメッセージの投稿が失敗する場合があります。詳細はこちら。私のメッセージ ループ スレッドでは、MS が提案する手法を使用します。

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;

しかし、あなたの場合、他の IPC 手段を使用することを強くお勧めします。

于 2016-03-24T06:34:24.097 に答える
0

メッセージが受信されない理由を探すのに何時間も費やしました。コード スニペットで示したように、クラスのコンストラクターは、メッセージの送信に使用したウィンドウ ハンドルを作成します。クラスがメイン スレッドによって構築されている限り、デフォルトでメッセージを待機するメイン スレッドのコンテキストに存在するウィンドウ ハンドル (正しく理解している場合) に対してすべて正常に機能しました。「壊れた」サービスでは、誤って呼び出したように、クラスが別のスレッドによって作成されたため、ハンドルはそのスレッドのコンテキストに存在していたに違いありません。したがって、このウィンドウ ハンドルを使用してメッセージを送信すると、メイン スレッドではなく、そのスレッドによって受信されました。このスレッドにはメッセージ待機ループがないため、メッセージがまったく受信されませんでした。私はそれがこのように機能することを知りませんでした。

あなたの時間とあなたが私にくれたすべての情報に感謝します.

于 2009-02-25T14:18:26.287 に答える
0

Mghie、あなたは絶対に正しいと思います。この方法でメッセージ待機ループを実装しました。

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;

初めてやるので、コードが合っているか確認していただけますか?実際、これは私の実際のクラス コード スニペットです (ログは実際のコードに置き換えられます)。重複した通信ポートを処理します。上記のスレッドにスレッド メッセージを送信する 2 つのスレッドがあり、comm ポートなどから何らかのデータを書き込んだり受信したことを通知します。スレッドがそのようなメッセージを取得すると、アクションを実行します。つまり、キューから受信データを取得します。スレッドが最初にそれを置き、次に受信したデータを分析する外部メソッドを呼び出します。重要ではないため、詳細には入りたくありません:)。次のようなスレッド メッセージを送信します: PostThreadMessage(MyThreadId, WM_DATA_READ, 0, 0)。

このコードは私が確認したとおりに正しく動作しますが、すべてが正しいことを確認したいので、それについて質問します。お答えいただければ幸いです。

スレッドを解放するには、次のようにします。

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;

これがメッセージ ループを終了する適切な方法であることを願っています。

ご助力ありがとうございます。

ところで、私の英語が理解できることを願っていますね。:) 私を理解するのが難しい場合は申し訳ありません.

于 2009-02-25T16:31:05.373 に答える