1

我正在开发一个 IRC 客户端。我遇到了一个专业的障碍,直到没有我能够解决。我将在下面显示代码。我遇到的问题是在 idIRC 的事件处理程序中创建 MDI 子窗口。

例如,如果我想创建一个新的频道表单(FrmChannel),我可以通过在捕获“/join”命令时调用它的创建过程来轻松完成此操作。

但是,如果我想以正确的方式进行操作,并等到我真正加入频道,并从服务器接收确认(通过在 onjoin 事件处理程序中处理它),那么我对表单创建过程的调用会导致要挂起的应用程序。

状态窗口也是如此。例如,如果我将状态窗口创建过程调用放在 TButton 的 onclick 事件上,那很好。子窗体已创建。但是,如果我在实际收到私人消息时尝试同样的事情,通过检查事件处理程序......应用程序挂起,没有异常,也没有 MDI 子级。

这是相关代码(为了解决这个问题,我将只处理查询窗口)。

首先,实际的 MDI Child 创建过程是这样的。我在这里有一个 TComponentList 来管理此类表单的列表(以防您想知道)。这里还有一些其他的东西也可以跟踪表单,尽管将它们注释掉并不能阻止挂起(我已经尝试过)。

procedure TFrmMain.NewQuery(const Server, MsgFrom: String);
var
Child: TFrmMessage;
TN: TTreeNode;
begin

///
/// Create form, set some data so we can reference it later.
///
///

  Child := TFrmMessage.Create(Application);
//  QueryManager.Add(Child); //TComponent List -- Used to find the Form Later On

  with Child do
  begin
   MyServer := Server; {What server this PM window is on}
   QueryWith := MsgFrom; {nickaname of the other person}
   Caption := MsgFrom; {Asthetic}
  end;

  Child.Echo('*** Conversation with ' + MsgFrom); //Herro World

  ///
  ///  The following code is working.
  ///  I'm pretty sure it's not causing the hangs.
  ///

  TN := GetNodeByText(ChanServTree, Server, True); {Find our parent node}

  with ChanServTree.Items.AddChild(TN, MsgFrom) do
  begin
   Selected := True;
   Tag := 2; {TYPE OF QUERY}
   Data := Pointer(Integer(Child)); //Pointer to Form we created
  end;

end;

这是我的 IRC 组件的事件处理程序:

procedure TFrmMain.IRCPrivateMessage(ASender: TIdContext; const ANicknameFrom,
  AHost, ANicknameTo, AMessage: string);
  var
  CheckVr: String;
  aThread: TNQThread;
begin
  //DEBUG:
(StatusManager[0] as TFrmStatus).Echo('From: ' + ANickNameFrom + 'AMESSAGE: ' + '''' +AMessage + '''');

///
/// Handle Drone Version Requests!
///  This is REQUIRED on servers like irc.blessed.net - or they won't let you join
///  channels! - It's part of the Registration proccess
///

{The Drones on some server's don't follow specifications, so we need to search
hard for their presence}

CheckVr := AMessage;

StringReplace(CheckVr,' ','',[rfReplaceAll, rfIgnoreCase]);
StringReplace(CheckVr,#1,'',[rfReplaceAll, rfIgnoreCase]);
(StatusManager[0] as TFrmStatus).Echo('Message was: ' + '''' + CheckVr + '''');

if Trim(CheckVr) = 'VERSION' then
begin
 IRC.CTCPReply(ANickNameFrom,'VERSION','mIRC v6.01 Khaled Mardam-Bey');
 (StatusManager[0] as TFrmStatus).Echo('*** Sent Version Reply to ' + ANickNameFrom);

 exit; {Because if we don't, this could mess things up}
end;

  ///
  /// The Following code sends the PM to the appropriate window.
  ///  If that window does not exist, we will create one first.
  ///


  if Pos('#',Amessage) = 1 then
   begin
    //Handled Elsewhere
   end else {is PM}
   begin

     if FindQueryFrm(ANickNameTo,IRC.Host) = nil then
    begin

    NewQuery(IRC.Host, ANickNameFrom);
      exit;
     end;

   end;

//  FindChannelFrm(ANickNameTo,IRC.Host).ChannelMessage(ANicknameFrom, AMessage);

end;

我已经尝试注释掉代码的各个部分以试图找出挂起的原因。挂起是由 Child := TFrmMessage.Create(Application); 引起的 专门打电话。是什么赋予了?

我已经尝试实现线程,看看这是否可能是一个问题。如果这就是您认为的问题所在,我将需要线程方面的帮助,因为显然尽管代码正在编译,但我仍然调用错误(因为即使我的线程版本挂起)。

提前致谢。

4

2 回答 2

4

正如我今天早些时候在alt.comp.lang.borland-delphi中告诉您的,问题在于 Indy 在执行阻塞套接字调用的同一线程中运行其事件处理程序,这与您的 GUI 不同。所有 GUI 操作都必须在同一个线程中进行,但是您正在套接字线程中创建一个新窗口。

为了解决这个问题,您的事件处理程序应该向主线程发布一个通知,主线程将在下一次检查消息时异步处理该通知。

如果你有足够新的 Delphi 版本,你可以试试这个TThread.Queue方法,它的工作原理很像Synchronize,除了调用线程不会阻塞等待主线程运行给定的方法。但是,它们在方法参数方面都有相同的限制;他们只接受零参数方法。这使得传输额外信息以供最终调用时使用的方法变得很麻烦。这对于排队的方法尤其不利,因为您为它们提供的任何额外数据都必须在主线程运行它所需的时间内保持不变;调用线程需要确保在调用排队的方法之前它不会覆盖额外的数据。

一个更好的计划可能是只将消息发布到主线程的某个指定窗口。Application.MainForm是一个诱人的目标,但 Delphi 表单可能会在不通知的情况下重新创建,因此您的其他线程使用的任何窗口句柄在他们尝试发布消息时都可能无效。MainForm.Handle而且按需读取属性也不安全,因为如果表单当时没有句柄,它将在套接字线程的上下文中创建,这将导致以后出现各种问题。相反,让主线程创建一个新的专用窗口,用于接收带有AllocateHWnd.

一旦您有了要发送的消息的目标,您就可以安排线程发布和接收它们。定义一个消息值并使用PostMessage.

const
  am_NewQuery = wm_App + 1;

PostMessage(TargetHandle, am_NewQuery, ...);

要发送接收者完全处理事件所需的额外数据,消息有两个参数。如果您只需要两条信息,那么您可以直接在这些参数中传递您的数据。但是,如果消息需要更多信息,那么您需要定义一个记录来保存所有信息。它可能看起来像这样:

type
  PNewQuery = ^TNewQuery;
  TNewQuery = record
    Host: string;
    FromNickname: string;
  end;

准备并发布这样的消息:

procedure NewQuery(const Server, MsgFrom: string);
var
  Data: PNewQuery;
begin
  New(Data);
  Data.Host := Server;
  Data.FromNickname := MsgFrom;
  PostMessage(TargetHandle, am_NewQuery, 0, LParam(Data));
end;

请注意,调用者分配了一个新的记录指针,但并没有释放它。它将被收件人释放。

class procedure TSomeObject.HandleThreadMessage(var Message: TMessage);
var
  NewQueryData: PNewQuery;
begin
  case Message.Msg of
    am_NewQuery: begin
      NewQueryData := PNewQuery(Message.LParam);
      try
        Child := TFrmMessage.Create(NewQueryData.Host, NewQueryData.FromNickname);
        TN := GetNodeByText(ChanServTree, NewQueryData.Host, True); // Find parent node
        with ChanServTree.Items.AddChild(TN, NewQueryData.FromNickname) do begin
          Selected := True;
          Tag := 2; // TYPE OF QUERY
          Data := Child; // reference to form we created
        end;
      finally
        Dispose(NewQueryData);
      end;
    end;
    else
      Message.Result := DefWindowProc(TargetHandle, Message.Msg, Message.WParam, Message.LParam);
  end;
end;

我对您的代码进行了其他一些更改。一是我让子表单的构造函数接受了正确创建自身所需的两条信息。如果表单希望它的标题是昵称,那么只需告诉它昵称并让表单对这些信息做任何需要的事情。

于 2009-11-14T19:23:23.480 回答
1

自从我在 Delphi 中编程并与类似的问题作斗争以来已经有一段时间了......

在 Java 中,套接字信息通知发生在与维护 GUI 的线程完全不同的线程上,并且您实际上被禁止从 GUI 线程外部对 GUI 进行更改(但您被赋予了合法询问 GUI 线程的机制制作模组)。在 Delphi 中,所有事件都来自同一个事件循环,但仍然......我会感到不安,要求进行主要的 GUI 更新,例如基于套接字事件打开的窗口。

我会尝试做的是让 comm 事件在队列或其他东西上留下通知,并让 GUI 线程在onIdle处理程序或类似的东西中处理它。

不过,这是在黑暗中刺伤。用大量的盐来接受我的建议!

于 2009-11-14T16:48:05.853 回答