سؤال

هناك مشكلة أنا غير قادر على حلها. لقد أنشأت تطبيقين خدمة في دلفي وحاولوا نشر الرسائل داخلها. بالطبع، لا توجد نوافذ في مثل هذه التطبيقات وتحتاج PostMessage معلمة مقبض النافذة لإرسال رسالة.

لذلك، قمت بإنشاء مقبض نافذة باستخدام وظيفة allocehwnd (mymethod: twndmethod) ومرت، مثل المعلمة "mymethod"، إجراءات أريد أن يتم استدعاؤها عند استلام الرسالة. إذا كان ذلك تطبيقا نافذة، فسيتم استدعاء PostMessage () المسمى باستخدام المقبض الذي تم إرجاعه بواسطة طريقة Allocehwnd بالتأكيد رسالة ستلقى بعد ذلك من خلال إجراء "MyMethod".

ومع ذلك، فإن الوضع مختلف في تطبيقات الخدمة الخاصة بي. أنا لا أفهم السبب، ولكن في أحدهم نشر الرسائل بهذه الطريقة تعمل بشكل جيد، بينما في المرتبة الثانية لا يوجد (لا يتم استلام الرسائل على الإطلاق). فقط عند إيقاف الخدمة، لاحظت أن يتم استلام رسالتين من قبل "MyMethod": WM_DESTROY و WM_NCDESTROY. لا يتم استلام الرسائل التي أرسلتها باستخدام PostMessage بواسطة هذا الإجراء مطلقا. من ناحية أخرى، فإن الخدمة الأولى تتلقى دائما جميع الرسائل التي أرسلتها.

هل يمكن أن تعطيني فكرة من شأنها أن تساعدني في العثور على سبب الخدمة الثانية التي لا تتلقى رسائلي؟ أنا لا أعرف بأي طريقة يمكن أن تختلف. راجعت إعدادات الخدمات ويبدو أنها متطابقة. لماذا بعد ذلك واحد منهم يعمل بشكل جيد والثاني لا (بقدر ما يتعلق الأمر بالرسائل بالقلق)؟

شكرا لأي نصيحة. ماريوس.

هل كانت مفيدة؟

المحلول

بدون مزيد من المعلومات، سيكون من الصعب مساعدتك في تصحيح هذا، وخاصة لماذا يعمل في خدمة واحدة ولكن ليس في الآخر. ومع ذلك:

بدلا من محاولة حل المشكلة في التعليمات البرمجية، قد ترغب في إزالة Windows تماما، واستخدام posthreadmessage () بدلا من postmessage (). لنشر الرسائل للعمل بشكل صحيح، تحتاج إلى حلقة رسالة، ولكن ليس بالضرورة تلقي النوافذ.

يحرر: أحاول الرد على جميع إجاباتك في إحالة واحدة.

أولا - إذا كنت ترغب في جعل حياتك سهلة يجب أن تحقق حقا OmnithreadLibrary. بواسطة غبر. وبعد لا أعرف ما إذا كان يعمل في تطبيق خدمة Windows، وأنا لا أعرف حتى ما إذا كان ذلك قد حاول حتى الآن. يمكنك أن تسأل في المنتدى. ومع ذلك، فقد كان هناك الكثير من السمات الرائعة ويستحق النظر إلى تأثير التعلم فقط.

ولكن بالطبع يمكنك أيضا برمجة هذا بنفسك، وستحتاج إلى إصدارات Delphi قبل دلفي 2007. سأضيف ببساطة بعض القصاصات من مكتبةنا الداخلية، التي تطورت على مر السنين ويعمل في عدة عشرات. أنا لا أدعي أن يكون خاليا من الأخطاء. يمكنك مقارنته برمزك، وإذا كان أي شيء يتمسك به، فلا تتردد في السؤال وسأحاول التوضيح.

هذا هو المبسط نفذ - اعدم() طريقة فئة قاعدة موضوع العمال:

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. لهذا السبب بدلا من بسيطة أنتظر لأجل() يتم استخدام التعليمات البرمجية التالية لتحرير جميع مؤلفات التشغيل:

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 () الطريقة لأن جميع المواضيع تحتاج إلى تحرير قبل أن يبدأ تدمير فئة وحدة تحكم الدرسات في تحرير أي كائنات قد تستخدم المواضيع.

نصائح أخرى

أقترح عليك التفكير في استخدام الأنابيب المسماة ل IPC. هذا هو ما صمموه للقيام به:

تبحث عن بديل لرسائل Windows المستخدمة في الاتصالات بين العملية

كما ذكر mghie، تحتاج إلى حلقة معالجة الرسائل. لهذا السبب إرجاع peekmessage الرسائل بشكل صحيح. ليس الأمر كذلك أن الرسائل ليست موجودة، فهذا غير معالجتها. في تطبيق قياسي، يقوم Delphi بإنشاء فئة TAGPLICATION واستدعاء Application.Run. هذه هي حلقة معالجة الرسائل للحصول على تطبيق عادي. يتكون أساسا من:

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

إذا كنت تريد تطبيق خدمةك للتعامل مع الرسائل، فستحتاج إلى أداء نفس النوع من العمل.

هناك مثال على استخدام الخدمة والتعامل مع إرسال posthreadmessage هنا. وبعد ضع في اعتبارك، كما ذكر ميكس، لا يمكنك استخدام معالجة الرسائل بين تطبيقات سياقات الأمان المختلفة (خاصة في نظام التشغيل Vista). يجب عليك استخدام الأنابيب المسماة أو ما شابه ذلك. تناقش مايكروسوفت هذا هنا.

يحرر:

بناء على مقتطف التعليمات البرمجية التي نشرتها، قد تقاتل فقط مشكلة خيوط. Allochwnd ليس مؤمنا مؤمنا. يرى هنا للحصول على شرح مفصل حقا للقضية والإصدار الذي يعمل بشكل صحيح في المواضيع.

بالطبع، هذا لا يزال يؤدينا إلى السبب في أنك لا تستخدم posthreadmessage بدلا من ذلك. الطريقة التي يتم بها منظم نموذج التعليمات البرمجية، سيكون تافهة لجعل الرسالة تعالج وظيفة الخيط ثم نقلها إلى الفصل للتصرف.

شكرا لجميع إجاباتك. أعتقد أننا ننسى المشكلة. لقد قمت بإنشاء تطبيق خدمة جديد وأداء اختبارات رسائل سريعة النشر السريعة. تم تسليم الرسائل بشكل صحيح، لذلك آمل أن أتمكن الآن من أن أذكر أن كل شيء عادة ما يعمل بشكل جيد وشيء خاطئ فقط مع هذه الخدمة الوحيدة التي وصفتها. أعلم أنه من الغباء، لكنني سأحاول فقط نسخ جزء واحد من التعليمات البرمجية بعد آخر من الخدمة "السيئة" إلى واحدة جديدة. ربما هذا سيساعدني في العثور على سبب المشكلة.

آمل أن أتمكن من التفكير الآن في حلقة انتظار الرسائل لا لزوم لها طالما أن كل شيء يعمل بشكل جيد بدونها، لا أستطيع؟

إذا يتعلق الأمر بالامتيازات، تقول Microsoft: "UAC يستخدم WIM لحظر رسائل Windows من يتم إرسالها بين العمليات ذات مستويات امتياز مختلفة.". بلدي UAC فيستا هو خارج وأنا لم أضع أي امتيازات لتلك الخدمات التي وصفتها. بصرف النظر عن ذلك أنا لا أرسل رسائل بين العمليات المختلفة. يتم إرسال الرسائل في غضون عملية واحدة.

لتعطيك فكرة ما أقوم به، سأريكم مقتطف رمز من تطبيق خدمة الاختبار.

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 () يحفظ البيانات في ملف نصي. عندما أستخدم هذا الرمز في تطبيق خدمة جديد، كل شيء يعمل بشكل جيد. عندما أضعها في الخدمة "المكسورة"، فإنها لا. يرجى ملاحظة أنني لا أستخدم أي حلقة انتظار الرسائل لتلقي الرسائل. لقد أنشأت خدمة جديدة ووضع التعليمات البرمجية أعلاه، ثم أنشأ كائن من الفصل. هذا كل شئ.

إذا تعرفت على لماذا هذا لا يعمل في الخدمة "المكسورة"، سأكتب عن ذلك.

شكرا لوقت كرستني.

ماريوس.

إليك ما سأحاوله:

  • تحقق من قيمة الإرجاع و getlastextror من postmessage
  • هل هذه آلة فيستا / 2008؟ إذا كانت الإجابة بنعم، تحقق مما إذا كان تطبيق الإرسال لديهما امتيازات كافية للقيام بإرسال الرسالة.

يجب أن أحصل على مزيد من المعلومات لمساعدتك على أبعد من ذلك.

قضيت ساعات طويلة في محاولة للعثور على سبب عدم استلام الرسائل. كما أظهرت في مقتطف التعليمات البرمجية، فإن منشئ الفصل يخلق مقبض نافذة اعتدت إرسال رسائل إليه. طالما تم إنشاء الفصل من خلال الخيط الرئيسي، كل شيء يعمل بشكل جيد لمقبض النافذة (إذا فهمت ذلك بشكل صحيح) موجود في سياق الرسائل الرئيسية التي، بشكل افتراضي، ينتظر الرسائل. في الخدمة "المكسورة"، كما اتصلت به عن طريق الخطأ، تم إنشاء صفي مؤشر ترابط آخر، لذلك يجب أن يكون المقبض موجودا في سياق هذا الموضوع. لذلك، عندما أرسلت رسائل باستخدام مقبض النافذة هذا، تم استلامها بواسطة هذا الموضوع، وليس من قبل الواحدة الرئيسية. بسبب حقيقة أن هذا الموضوع لم يكن لديه أي حلقة انتظار الرسائل، لم يتم استلام رسائلي على الإطلاق. أنا فقط لم أكن أعرف أنه عمل بهذه الطريقة. لحل المشكلة بطريقة سهلة، أقوم بإنشاء وتدمير الفصل في الخيط الرئيسي على الرغم من أنني استخدمه في المرتبة الثانية.

شكرا لوقتك وجميع المعلومات التي أعطيتها لي.

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;

أنا أفعل ذلك لأول مرة، لذا يرجى التحقق من الكود سواء كان ذلك صحيحا؟ في الواقع، هذا هو مقتطفات رمز الطبقة الحقيقية (سيتم استبدال السجلات برمز حقيقي). انها تتناول ميناء التكنولوجيا المتداخلة. هناك اثنين من الخيوط التي ترسل رسائل ترسل رسائل الخيط إلى الخيط أعلاه، وإبلاغها بأنها كتبت أو تلقيت بعض البيانات من منفذ العميل، وما إلى ذلك عندما يحصل الخيط على رسالة مثل هذه الرسالة، فإنه يتطلب إجراءات - فهو يحصل على البيانات المستلمة من قائمة انتظار، حيث وضعت مؤشرات الترابط أولا ثم تستدعي طريقة خارجية، دعنا نقول، تحلل البيانات المستلمة. لا أريد أن أذهب إلى تفاصيله غير مهم :). أرسل رسائل موضوعية مثل هذا: posthreadmessage (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;

آمل أن تكون هذه هي الطريقة الصحيحة لإنهاء حلقة الرسالة.

شكرا جزيلا لك على مساعدتك.

راجع للشغل، آمل أن تكون لغتي الإنجليزية مفهومة، أليس كذلك؟ :) آسف إذا كنت تواجه صعوبات في فهم لي.

هناك خدعة واحدة في حلقات الرسائل في المواضيع. لن يقوم Windows بإنشاء قائمة انتظار الرسائل للحصول على مؤشر فورا، لذا سيكون هناك بعض الوقت عند نشر الرسائل إلى مؤشر ترابط. التفاصيل هنا. وبعد في MSG Loop Loop، أستخدم تقنية 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 الأخرى.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top