(Delphi 2009) idIRC、MDI 和挂起问题
我正在开发一个 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); 引起的具体打电话。什么给?
我尝试过实现线程来看看这是否是一个问题。如果这就是您认为问题所在,我将需要线程方面的帮助,因为显然虽然代码正在编译,但我仍然调用了错误的东西(因为即使我的线程版本也挂起)。
提前致谢。
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.
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.
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.
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).
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;
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;
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).
Thanks in advance.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
正如我在 alt.comp 中告诉您的那样。 lang.borland-delphi 今天早些时候,问题是 Indy 在执行阻塞套接字调用的同一线程中运行其事件处理程序,该线程与 GUI 不是同一线程。所有 GUI 操作必须在同一个线程中进行,但您正在套接字线程中创建一个新窗口。
为了解决这个问题,您的事件处理程序应该向主线程发布一个通知,每当下次检查消息时,主线程都会异步处理该通知。
如果您有足够新的 Delphi 版本,您可以尝试
TThread .Queue
方法,其工作方式很像 < code>Synchronize,但调用线程不会阻塞等待主线程运行给定方法。不过,它们在方法参数方面都有相同的限制;他们只接受零参数方法。这使得在最终调用该方法时传输额外信息以供使用变得很麻烦。这对于排队方法来说尤其糟糕,因为无论您为它们提供什么额外数据,只要主线程运行它,就必须保持不变;调用线程需要确保在调用排队方法之前不会覆盖额外的数据。更好的计划可能是只将消息发布到主线程的某个指定窗口。
Application.MainForm
是一个诱人的目标,但 Delphi 表单可能会在没有通知的情况下重新创建,因此其他线程使用的任何窗口句柄在它们尝试发布消息时可能无效。按需读取 MainForm.Handle 属性也不安全,因为如果表单当时没有句柄,它将在套接字线程的上下文中创建,这将导致各种以后出现问题。相反,让主线程创建一个新的专用窗口,用于使用AllocateHWnd 接收线程消息
。一旦您确定了消息的发送目标,您就可以安排线程来发布和接收消息。定义消息值并使用
PostMessage
发布它们。为了发送接收者需要完全处理事件的额外数据,消息有两个参数。如果您只需要两条信息,那么您可以直接在这些参数中传递数据。但是,如果消息需要更多信息,那么您需要定义一个记录来保存所有信息。它可能看起来像这样:
准备并发布消息,如下所示:
请注意,调用者分配一个新的记录指针,但不会释放它。它将被接收者释放。
我对您的代码做了一些其他更改。一是我让子窗体的构造函数接受它正确创建自身所需的两条信息。如果表单希望其标题为昵称,则只需告诉它昵称,然后让表单使用该信息执行所需的任何操作。
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.
If you have a recent-enough Delphi version, you could try the
TThread.Queue
method, which works a lot likeSynchronize
, 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.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 theMainForm.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 withAllocateHWnd
.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
.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:
Prepare and post the message like this:
Note that the caller allocates a new record pointer, but it does not free it. It will get freed by the recipient.
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 中编程并解决类似问题以来已经有一段时间了......
在 Java 中,套接字信息通知发生在与维护 GUI 的线程非常不同的线程上,并且实际上禁止您从外部对 GUI 进行更改GUI 线程(但你有合法要求 GUI 线程制作 mod 的机制)。在 Delphi 中,所有事件都来自同一个事件循环,但是...如果要求进行主要的 GUI 更新(例如基于套接字事件打开的窗口),我会有一种恶心的感觉。
我会尝试做的是让 comm 事件在队列或其他东西上留下通知,并让 GUI 线程在 onIdle 处理程序或类似的东西中处理该通知。
不过,这是在黑暗中刺伤。接受我的建议,多加盐!
It's been a while since I programmed in Delphi and battled similar problems...
In Java, socket info notifications happen on a very different thread from the one that maintains the GUI, and you're practically forbidden from making changes to the GUI from outside the GUI thread (but you're given mechanisms to legally ask the GUI thread to make the mod). In Delphi, all events are coming from the same event loop, but still... I'd get a queasy feeling asking for a major GUI update like a window open based on a socket event.
What I would try doing is getting the comm event to leave a notification on a queue or something, and getting the GUI thread to process that in the
onIdle
handler or something like that.This is a stab in the dark, though. Take my recommendation with lots of salt!