(Delphi 2009)idIRC,MDI和挂起问题 [英] (Delphi 2009) idIRC, MDI, and problems with hanging

查看:96
本文介绍了(Delphi 2009)idIRC,MDI和挂起问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用IRC客户端。我遇到了专业障碍,直到无法解决为止。我将在下面显示代码。我遇到的问题是在idIRC的事件处理程序中创建MDI子窗口。

I'm working on an IRC client. I've hit a majors snag which, up until not I've been able to work around. I'll show code below. What's I'm having a problem with is creating MDI child windows within the event handlers of idIRC.

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

For example, if I want to create a new channel form (FrmChannel), I can accomplish this easily by calling it's create procedure when I catch the '/join' command.

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

However, if I want to do it the right way, and wait until I've actually joined the channel, and receive confirmation of this from the server (by handling it in the onjoin event handler) then my call to my form creation procedure causes the application to hang.

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

The same goes for status windows. For example, if I put my status window creation procedure call on a TButton's onclick event, fine. Child form created. However, if I try the same thing when I actually receive a private message, by checking the event handler... Application hangs, no exception, and no MDI Child.

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

Here's the relevant code (for the sake of solving this I'll deal with the query window only).

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

First, the actual MDI Child creation goes like this. I have a TComponentList in here to manage a list of this class of form (in case you're wondering). There are some other things in here that keep track of the form as well, though commenting them out doesn't prevent the hang (I've tried).

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组件的事件处理程序:

Here's the event handler for my IRC component:

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);引起的。具体打电话。

I've tried commenting out various parts of the code to try to track down the cause of the hanging. The hang is caused by the Child := TFrmMessage.Create(Application); call specifically. What gives?

我尝试实现线程以查看是否可能是一个问题。如果那是您在考虑的问题,我将需要我的线程帮助,因为尽管代码正在编译,但显然我仍在调用错误消息(因为即使我的线程版本都挂起了)。

I've tried implementing threads to see if that might be an issue. If that's what you're thinking the problem is, I'll need help with my threading because apparently though the code is compiling, I'm still calling something wrong (because even my threaded version hangs).

谢谢。

推荐答案

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

As I told you in alt.comp.lang.borland-delphi earlier today, the problem is that Indy runs its event handlers in the same thread that does the blocking socket calls, which is not the same thread as your GUI. All GUI operations must take place in the same thread, but you are creating a new window in the socket thread.

要解决此问题,您的事件处理程序应将通知发布到

To solve it, your event handler should post a notification to the main thread, which the main thread will handle asynchronously whenever it happens to next check for messages.

如果您的Delphi版本最近,可以尝试使用< a href = http://docwiki.embarcadero.com/VCL/zh-CN/Classes.TThread.Queue rel = nofollow noreferrer> TThread.Queue 方法,其工作原理与 同步 ,只是调用线程不会阻塞等待主线程运行给定方法。不过,它们的方法参数都有相同的限制;他们只接受零参数方法。这使得转移额外的信息变得麻烦,以至于该方法在最终调用时使用。对于排队方法而言,这尤其糟糕,因为您提供给它们的任何额外数据都必须保持完整,直到主线程运行它。

If you have a recent-enough Delphi version, you could try the TThread.Queue method, which works a lot like Synchronize, except the calling thread doesn't block waiting for the main thread to run the given method. They both have the same limitation regarding their method parameters, though; they only accept a zero-parameter method. That makes it cumbersome to transfer extra information for the method to use when it's eventually called. It's particularly bad for queued methods since whatever extra data you provide for them must remain intact for as long as it takes for the main thread to run it; the calling thread needs to make sure it doesn't overwrite the extra data before the queued method gets called.

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

A better plan is probably to just post a message to some designated window of the main thread. Application.MainForm is a tempting target, but Delphi forms are liable to be re-created without notice, so whatever window handle your other threads use might not be valid at the time they try to post a message. And reading the MainForm.Handle property on demand isn't safe, either, since if the form has no handle at the time, it will get created in the socket thread's context, which will cause all sorts of problems later. Instead, have the main thread create a new dedicated window for receiving thread messages with AllocateHWnd.

一旦您确定了要发送邮件的目标,就可以安排帖子的发送和接收他们。定义一条消息值,并用 PostMessage 发布。

Once you have a target for messages to go to, you can arrange for threads to post and receive them. Define a message value and post them with PostMessage.

const
  am_NewQuery = wm_App + 1;

PostMessage(TargetHandle, am_NewQuery, ...);

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

To send the extra data the recipient will need to fully handle the event, messages have two parameters. If you only need two pieces of information, then you can pass your data directly in those parameters. If the messages need more information, though, then you'll need to define a record to hold it all. It could look something like this:

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;

请注意,调用方分配了一个新的记录指针,但没有释放它。

Note that the caller allocates a new record pointer, but it does not free it. It will get freed by the recipient.

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;

我对代码进行了其他一些更改。一种是让子窗体的构造函数接受正确创建自身所需的两条信息。如果表单希望其标题为昵称,则只需告诉它昵称,然后让表单利用该信息执行所需的操作即可。

I've made a couple of other changes to your code. One is that I made the child form's constructor accept the two pieces of information it needs to create itself properly. If the form wants its caption to be the nickname, then just tell it the nickname and let the form do whatever it needs to with that information.

这篇关于(Delphi 2009)idIRC,MDI和挂起问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆