2009-02-24 4 views
1

Существует проблема, которую я не могу решить. Я создал два приложения-службы в Delphi и пытался отправлять сообщения в них. Конечно, в таких приложениях нет окон, а PostMessage - параметр дескриптора окна для отправки сообщения.PostMessage в сервисных приложениях

Поэтому я создал дескриптор окна с использованием функции AllocateHWnd (MyMethod: TWndMethod) и передал в качестве параметра «MyMethod» процедуру, которую я хочу вызвать, когда получено сообщение. Если это было оконное приложение, вызов PostMessage() с использованием дескриптора, возвращаемого методом AllocateHWnd, обязательно отправит сообщение, которое затем будет получено процедурой «MyMethod».

Ситуация, однако, в моих сервисных приложениях различна. Я не понимаю, почему, но в одном из них сообщение сообщений таким образом работает нормально, тогда как во втором - нет (сообщения вообще не принимаются). Я вижу, что два сообщения получают «MyMethod»: WM_DESTROY и WM_NCDESTROY. Эти сообщения, которые я отправляю с помощью PostMessage, никогда не получаются этой процедурой. С другой стороны, первая услуга всегда получает все отправленные мной сообщения.

Не могли бы вы дать мне ключ, который поможет мне найти причину, по которой вторая служба не получает мои сообщения? Я не знаю, каким образом они могут отличаться. Я проверил настройки сервисов, и они кажутся одинаковыми. Почему тогда один из них работает нормально, а второй - нет (что касается отправки сообщений)?

Спасибо за любой совет. Mariusz.

ответ

3

Без дополнительной информации вам будет сложно помочь вам отладить это, особенно, почему он работает в одной службе, но не в другой. Однако:

Вместо того, чтобы исправить проблему в вашем коде, вы можете вообще удалить все окна и использовать PostThreadMessage() вместо PostMessage(). Для правильной работы сообщений вам нужен цикл сообщений, но не обязательно получение окон.

Редактировать: Я пытаюсь ответить на все ваши ответы за один раз.

Во-первых - если вы хотите сделать вашу жизнь легкой, вы должны действительно проверить OmniThreadLibrary на gabr. Я не знаю, работает ли он в приложении Windows, я даже не знаю, было ли это еще попыток. Вы можете спросить на форуме. Тем не менее, он имеет множество отличных функций и заслуживает внимания, хотя бы для эффекта обучения.

Но, конечно, вы также можете запрограммировать это для себя, и вам придется иметь дело с версиями Delphi до Delphi 2007. Я просто добавлю некоторые фрагменты из нашей внутренней библиотеки, которая развивалась на протяжении многих лет и работает в нескольких десятках программы. Однако я не утверждаю, что это ошибка. Вы можете сравнить его с вашим кодом, и, если что-нибудь выйдет, не стесняйтесь спрашивать, и я попытаюсь уточнить.

Это упрощенная 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 вызовет сообщение чтобы выйти. Однако существует проблема, заключающаяся в том, что код в классах потомков может полагаться на правильные обращения к сообщениям Windows во время остановки потока, особенно когда используются интерфейсы COM. Вот почему вместо простого 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() метода, потому что все нити должны быть освобождены до деструктора класса контроллера потомков начинает освобождать любые объекты, которые могут использовать потоки.

+0

getMessage() зависает нить, без исключения, просто зависает. Любая идея почему? – Codebeat

+0

@Erwinus: 'GetMessage()' является блокирующим вызовом, поэтому он будет возвращаться только в том случае, если он может вернуть сообщение, или цикл сообщения больше не работает. Вы уверены, что вы вызвали 'PostThreadMessage()', и для правильного потока? – mghie

0

Вот что я хотел бы попробовать:

  • Проверьте возвращаемое значение и GetLastError из PostMessage
  • Является ли это Vista/2008 машина? Если да, проверьте, есть ли у отправляющего приложения достаточные привилегии для отправки сообщения.

У меня должно быть больше информации, чтобы помочь вам дальше.

2

Как уже упоминалось Mghie, вам нужен цикл обработки сообщений. Вот почему PeekMessage правильно возвращает сообщения. Дело не в том, что сообщений нет, это значит, что вы их не обрабатываете. В стандартном приложении Delphi создает класс TApplication и вызывает Application.Run. Это IS-цикл обработки для обычного приложения. Он состоит в основном из:

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

Если вы хотите, чтобы ваше служебное приложение обрабатывало сообщения, вам необходимо выполнить такую ​​же работу.

Приведен пример использования сервиса и обработки сообщений PostThreadMessage here. Имейте в виду, что, как заметил Мик, вы не можете использовать обработку сообщений между приложениями разных контекстов безопасности (особенно в Vista). Вы должны использовать именованные каналы или аналогичные. Microsoft обсуждает это here.

Edit:

Основываясь на фрагмент кода, который вы писали, вы можете просто бороться с проблемы распараллеливания. AllocHWnd не является потокобезопасным. См. here для подробного объяснения проблемы и версии, которая работает правильно в потоках.

Конечно, это все еще возвращает нас к тому, почему вы не используете PostThreadMessage. Способ структурирования вашего кода кода, было бы тривиально сделать сообщение обработчиком функции потока, а затем передать его в класс для удаления.

+0

+1 для психической отладки - я бы никогда не предполагал, что вообще не может быть цикла сообщений. – mghie

1

Спасибо за все ваши ответы. Я думаю, мы можем забыть о проблеме.Я создал новое сервисное приложение и выполнил тесты быстрого сообщения. Сообщения были доставлены правильно, поэтому я надеюсь, что теперь могу сказать, что, как правило, все работает нормально, а что-то не так, только с этой одной услугой, которую я описал. Я знаю, что это глупо, но я просто попытаюсь скопировать один фрагмент кода за другим с «плохого» сервиса на новый. Возможно, это поможет мне найти причину проблемы.

Надеюсь, теперь я могу считать цикл ожидания сообщений ненужным, пока все работает нормально, не так ли?

Если дело доходит до привилегий, Microsoft говорит: «UAC использует WIM, чтобы блокировать сообщения Windows между процессами с разными уровнями привилегий». Мой UAC для Vista отключен, и я не установил никаких привилегий для тех служб, которые я описал. Кроме того, я не отправляю сообщения между различными процессами. Сообщения отправляются в течение одного процесса.

Чтобы дать вам представление о том, что я делаю, я покажу вам фрагмент кода из приложения тестового сервиса.

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() сохраняет данные в текстовый файл. Когда я использую этот код в новом сервисном приложении, все работает нормально. Когда я помещаю его в «сломанную» услугу, это не так. Обратите внимание, что я не использую цикл ожидания сообщений для получения сообщений. Я создал новую службу и просто поместил в нее код, а затем создал объект класса. Это все.

Если я узнаю, почему это не работает в «сломанной» службе, я напишу об этом.

Спасибо за то, что вы меня посвятили.

Mariusz.

0

Я потратил много времени, пытаясь найти причину отсутствия сообщений. Как я показал в своем фрагменте кода, конструктор класса создает дескриптор окна, который я использовал для отправки сообщений. Пока класс был создан основным потоком, все отлично работало для дескриптора окна (если я правильно его понимаю) существовало в контексте основного потока, который по умолчанию ожидает сообщений. В «сломанной» службе, как я ее назвал по ошибке, мой класс был создан другим потоком, поэтому дескриптор должен существовать в контексте этого потока. Поэтому, когда я отправлял сообщения с помощью этого дескриптора окна, они были получены этим потоком, а не основным. Из-за того, что в этом потоке не было цикла ожидания сообщений, мои сообщения вообще не были получены. Я просто не знал, что это сработало. Чтобы решить проблему простым способом, я создаю и уничтожаю класс в основном потоке, хотя я использую его во втором.

Спасибо за ваше время и всю информацию, которую вы мне дали.

+0

Выполнение этого эффективно устраняет преимущества многопоточности, поскольку методы потоков будут выполняться в контексте основного потока графического интерфейса, а не в контексте рабочего потока. По сути, вы выполняете сериализацию всего выполнения вашей программы. Вы должны обязательно пересмотреть дизайн своих услуг. – mghie

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; 

Я делаю это в первый раз, так что, пожалуйста, не могли бы вы проверить код ли это правильно? На самом деле, это мой настоящий фрагмент кода класса (журналы будут заменены реальным кодом). Он обрабатывает перекрываемый коммуникационный порт. Есть два потока, которые отправляют потоковые сообщения в поток выше, сообщая ему, что они написали или получили некоторые данные из коммуникационного порта и т. Д. Когда поток получает такое сообщение, он принимает действие - он получает полученные данные из очереди, где потоки сначала помещают его, а затем вызывает внешний метод, который, скажем, анализирует полученные данные. Я не хочу вдаваться в подробности, потому что это неважно :).Я отправляю потоковые сообщения следующим образом: 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; 

Я надеюсь, что это правильный путь для завершения цикла обработки сообщений.

Большое спасибо за помощь.

BTW, я надеюсь, что мой английский язык понятен, не так ли? :) Извините, если у вас возникли трудности с пониманием меня.

+0

Вам не требуется DispatchMessage в вашем случае. Эта функция обрабатывает сообщения, которые не отправляются непосредственно на ваш поток (главным образом, пользовательский ввод в окна, которые живут в контексте вашего потока). Ваша нить является рабочим, поэтому DispatchMessage здесь бесполезен. – Fr0sT

0

В потоках сообщений есть один трюк в потоках сообщений. Windows не создаст очередь сообщений для потока немедленно, поэтому будет некоторое время, когда сообщение сообщений в поток завершится неудачно. Подробности: here. В моей петле тзда нити я использую технику 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.