2009-11-14 3 views
1

Я работаю над клиентом IRC. Я ударил майоры, которые до тех пор, пока я не смог обойтись. Я покажу код ниже. У меня возникла проблема с созданием дочерних окон MDI в обработчиках событий idIRC.(Delphi 2009) idIRC, MDI и проблемы с подвеской

Например, если я хочу создать новую форму канала (FrmChannel), я могу сделать это легко, вызвав процедуру создания, когда я поймаю команду '/ join'.

Однако, если я хочу сделать это правильно, и дождитесь, пока я действительно присоединился к каналу, и получите подтверждение этого с сервера (обработав его в обработчике событий onjoin), то мой вызов на мой процедура создания формы заставляет приложение зависать.

То же самое касается окон состояния. Например, если я положу вызов процедуры создания окна состояния в событии onclick TButton, отлично. Создана детская форма. Однако, если я попытаюсь сделать то же самое, когда на самом деле получаю личное сообщение, проверив обработчик событий ... Приложение зависает, никаких исключений и нет MDI Child.

Вот соответствующий код (для решения этого я имею дело только с окном запроса).

Во-первых, фактическое создание ребенка MDI происходит следующим образом. У меня есть 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; 

Я попытался закомментировав различные части кода, чтобы попытаться отследить причину подвешивания. Повреждение вызвано Ребенком: = TFrmMessage.Create (Application); звоните специально. Что дает?

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

Заранее спасибо.

+0

Имеет ли TfrmMessage что-либо в своих событиях OnCreate или OnShow? Я говорю OnShow, потому что у MDIChildren может быть только свойство Visible равным True, поэтому они будут отображаться, как только они будут созданы. –

ответ

4

As I told you in alt.comp.lang.borland-delphi earlier today проблема заключается в том, что Indy запускает обработчики событий в том же потоке, который вызывает вызовы блокирующего сокета, который не является тем же потоком, что и ваш графический интерфейс. Все операции с графическим интерфейсом должны выполняться в одном потоке, но вы создаете новое окно в потоке сокета.

Чтобы решить эту проблему, ваш обработчик событий должен опубликовать уведомление по основному потоку, которое основной поток будет обрабатывать асинхронно, когда это произойдет при следующей проверке сообщений.

Если у вас есть достаточно современная версия 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; 

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

+0

Привет, Роб, я не видел ответ в новостной группе, спасибо за ответ, дважды! Я собираюсь потратить некоторое время, чтобы попытаться выполнить эти ответы. Я вернусь! – Commodianus

+0

В соответствии с тем, что я читаю, AllocateHWnd не является потокобезопасным. Проверьте это: http://17slon.com/blogs/gabr/2007/06/allocatehwnd-is-not-thread-safe.html, все еще смотрящий на эту ссылку и пытающийся примирить тот факт, что я на неизведанной территории (лично) и ваш код с кодом на этой странице. – Commodianus

+0

Ничего себе, я потерян. Я буду спать на этом и вернуться. – Commodianus

1

Это было время, так как я запрограммировал в Delphi и боролись с подобными проблемами ...

В Java сокет информация уведомления происходят на совершенно другом потоке от той, которая поддерживает графический интерфейс пользователя, и вы практически запрещено от внесения изменений в графический интерфейс из-за потока GUI (но вам даны механизмы, чтобы законно просить поток GUI создавать мод). В Delphi все события происходят из одного и того же цикла событий, но все же ... Я получаю тошнотворное чувство, требующее большого обновления графического интерфейса пользователя, например, открытого окна на основе события сокета.

То, что я хотел бы попробовать, - это получить событие comm, чтобы оставить уведомление в очереди или что-то в этом роде, и получить поток GUI для обработки этого в обработчике onIdle или что-то в этом роде.

Это удар в темное место. Примите мою рекомендацию с большим количеством соли!

Смежные вопросы