Re[2]: Корректно отработать завершение своего интерфейса
От: Voxifer Россия  
Дата: 08.02.10 10:38
Оценка:
Здравствуйте, Vi2, Вы писали:

Vi2>Ответ на твой вопрос заключается в том, какой доступ к форме (немодальной) она позволяет. Если экземпляр доступен по указателю, то определи метод завершения CloseControlWindow и вызывай при закрытии клиента. В VB (судя по коду в другом посте) есть события на закрытие формы — QueryUnload и Unload, в которых можно оповестить этот экземпляр. Если экземпляр не доступен, то его как бы фактически нет, и поэтому то, что он жив, является простым недоразумением.


Да, твоя правда. Действительно, проблема была в том, что я не до конца представлял себе механизм работы собственного компонента и он был как раз "простым недоразумением". Собственно, покопавшись немного глубже, я вышеобозначенную проблему решил. Не так как хотелось бы, но зато понял, что иначе нельзя. Но тут объявилась другая проблема.
Вкратце, происходит следующее: внешнее приложение вызывает мой интерфейс, что приводит к созданию формы. Форма порождает дополнительный поток, который уже запускает другой интерфейс вызвавшего форму внешнего приложения и работает с ним, периодически посылая информацию форме на отображение. При освобождении моего интерфейса форма закрывается, в её деструкторе вызывается Terminate для потока и вот тут происходят его зависания (попытки выяснить с помощью MessageBox'ов, где конкретно — привели меня к CoUninitialize). Принудительный TerminateThread помогает частично, форма закрывается, но вызывающее приложение все равно зависает до тех пор, пока я не попытаюсь вызвать его системное меню на панели задач (тогда, видимо, происходит выход из какого-то дедлока). Причем вставка MessageBox'а сразу после команды Terminate почему-то сразу снимает всю проблему — абсолютно все отрабатывает корректно, сколько я ни дергал это окно. Есть подозрение, что я что-то напутал с потоковой моделью COM, но что именно — не знаю. Между потоками интерфейсы не передаю, поэтому маршаллинг тут не использую. (Де)Инициализировать COM пытался где можно и не можно. Надоело тыкаться, как слепому котенку, уже мозг и гугл сломал, может кто подскажет где я налажал?
Вот краткие выдержки из моего кода:

...
initialization
  TAutoObjectFactory.Create(ComServer, TControlWindow, Class_ControlWindow,
    ciMultiInstance, tmBoth);  // Последние два параметра менял в разных вариациях
...

  TUptateThread = class(TThread)
  ...
  public
    FEvent:THandle;
  ...
  end;
  TfmWin = class(TForm)
    ...
    FUpdateThread:TUptateThread;
    ...
  end;
...
constructor TUptateThread.Create(...);
begin
 inherited Create(true);
 ...
 FreeOnTerminate:=false;
 FEvent:=CreateEvent(nil, TRUE, FALSE, 'MyCustomEvent');
 Resume;
end;

destructor TUptateThread.Destroy;
begin
  CloseHandle(FEvent);
  inherited;
end;

procedure TUptateThread.Execute;
var
 FApplication:OleVariant;
 ...
 hr:HRESULT;
 WValue,LValue: integer;
begin
  hr:=CoInitializeEx(nil,COINIT_MULTITHREADED);  // Пытался и COINIT_APARTMENTTHREADED

  FApplication := GetActiveOleObject('Интерфейс вызвавшего приложения');
  
  ... // Инициализация внутренних переменных на основе методов FApplication 
  { Place thread code here }
  while not Terminated do
   begin
    // Работа с FApplication 
    ...
    if (Условие) then PostMessage(FMainForm,WM_USER+NNN,WValue,LValue);  // Форма только отображает полученные от потока значения, сама с интерфейсом не взаимодействует
    Sleep(50);
   end;
  ... // Деинициализация внутренних переменных
  FApplication:=Unassigned;
  SetEvent(FEvent);          // Вот тут сигнализирую об окончании потока, потому что ждать сам поток бессмысленно, ибо зависает
  SetErrorInfo(0,nil);       // Где-то слышал звон, что может помочь... нифига...
  if (hr=S_OK)or(hr=S_FALSE) then CoUninitialize;   // Как показало вскрытие - вот тут происходит зависание
end;

constructor TfmWin.Create(AIndex:integer);
begin
 inherited Create(nil);
 FUpdateThread:=TUptateThread.Create(...);
end;

destructor TfmWin.Destroy;
begin
 try
  ResetEvent(FUpdateThread.FEvent);
  FUpdateThread.Terminate;         // Вот после этого поток может зависнуть, а может и нет
  // MessageBox(0,'Удаление потока','Ахтунг!',0); // Раскомментирование этой магической строчки снимает проблему, но само является проблемой.
  if WaitForSingleObject(FUpdateThread.FEvent ,5000)<>WAIT_OBJECT_0 then 
    TerminateThread(FUpdateThread.Handle,0);
  FUpdateThread.Free;
 finally
  FUpdateThread:=nil;
 end;
 inherited;
end;
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.