DmLam
Junior Member | Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору Ну, раз ссылки на оф.сайт не засчитываются, то рассказываю какие баги нашлись при запуске FR Enterprise, точнее при попытке его реально использовать в качестве сервера отчетов. Для начала повторю то, что писал здесь Ситуация: на предприятии существует некоторое количество приложений разного рода, работающих с БД (Oracle). Отчеты всех приложений лежат в одной БД, в специально под них отведенной таблице в BLOB-поле. Берем стандартный пример сервера из поставки FRE 3.20.25, пишем обработчик OnGetReport в котором загружаем отчет из BLOB-а. Все хорошо, кроме того, что отчеты в которых присутствует форма запроса не работают - сервер выдает exception - System Error, Code 5 - "Отказано в доступе". Расследование показывает, что проблема возникает в frxEngine в TfrxEngine.RunDialogs на Код: { refresh the border style - it was bsSizeable in the designer } p.DialogForm.BorderStyle := p.BorderStyle; | Дальнейшее расследование показывает, что собственно проблема заключается в том, что при изменении BorderStyle формы происходит RecreateWnd - т.е. сначала окно разрушается, а потом... потом не возникает, потому что система ругается на попытку разрушить окно, созданое другим потоком. Едем дальше и обнаруживаем, что отчет загружается сервером в TfrxReportSession.Execute следующим образом: Код: if Assigned(TfrxReportServer(FParentReportServer).OnGetReport) then Synchronize(DoOnGetReport) // Lam else FReport.LoadFromFile(FName); | Тут становится понятно, где порылась собака - если отчет загружать из файла, то он грузится в том же потоке, где потом будет строиться, а если его загружать по событию OnGetReport, то он почему-то грузится в основном потоке. BugFix: убрать Synhronize, обнаружить, что формы запроса условий отчета стали работать и обрадоваться наступившему счастью. Порадовавшись, заполним форму, нажмем кнопку OK и, застыв в предвкушении премии за запуск корпоративной информационной системы, получим ошибку сервера 403 - нет доступа. Упс. Разбирательства приводят к следующему куску кода в модуле frxServer в TfrxServerSession.ParseHTTPHeader: Код: RepName := ParseParam('report'); if (Length(RepName) > 0) then begin ... end else if i > 0 then FErrorCode := 403; | i здесь при построении отчета всегда >0, поэтому проблема в отсутствии (точнее, пустоте) параметра report в строке запроса, генерируемой формой запроса. Едем дальше и выясняем, что этот параметр задается как одно из hidden полей в форме, генерируемой в модуле frxServerReports методом TfrxReportSession.DoSaveForm: Код: WebForm := TfrxWebForm.Create(CurPage, FSessionId); try WebForm.ReportName := ExtractReportName(FReport.FileName); // заветная строка ! WebForm.Prepare; ... | Отсюда вывод - у отчета c формой запроса должен быть прописан FileName. BugFix: варианта два - еще чуть-чуть подправить TfrxReportSession.Execute в части вызова события для загрузки отчета, примерно так: Код: if Assigned(TfrxReportServer(FParentReportServer).OnGetReport) then begin // Lam // Synchronize(DoOnGetReport) // Lam DoOnGetReport; // Lam if FReport.FileName='' then // Lam FReport.FileName := FName; // Lam end // Lam else FReport.LoadFromFile(FName); | хотя это, возможно, и не очень правильно, видимо с идеологической точки зрения Вариант второй - прописать в обработчике этого события загруки отчета Report.FileName например так: Код: Report.FileName := ReportName; | Ура, теперь у нас все работает. Простые отчеты строятся, бежим к начальству показывать только что сделанный отчет с красивым RichEdit, в котором много текста разными шрифтами и цветами... и обламываемся. Блин, что ж мы все по-хорошему-то не проверили, прежде чем бигбоссу показывать... Но ведь работал же, работал! как же так... Возвращаемся разбираться. Посидев минут пять понимаем, что отчет с frxRichView строится один раз и все, больше никак. Если убрать RichView из отчета - все работает без проблем. Хм... хм... Начинаем отладку. Выясняем, что сервер при повторном построении отчета с RichView вылетает с ошибкой 1400 - неверный дескриптор окна. Фигассе!.. Копаемся в коде, мучаем отладчик, ругаемся на всех и вся, но в конце-концов понимаем, что проблема возникает при создании TfrxRichView, причем возникает именно во второй раз, а конкретно в TfrxRichView.Create в строке Код: SetWindowLong(FRichEdit.Handle, GWL_EXSTYLE, GetWindowLong(FRichEdit.Handle, GWL_EXSTYLE) or WS_EX_TRANSPARENT); | а конкретно проблема возникает при получении FRichEdit.Handle, когда создается собственно окно FRichEdit. Единственным окном (хэндлом окна, точнее), которое передается в CreateWindow является родительское. Вспомнив о самой первой описаной в данном опусе проблеме с разрушением окна, созданного не своим потоком, логично предполагаем, что проблема примерно такая же - пытаемся создать окно, у которого родитель не создан нашим потоком. (Забегаяя вперед скажу, что предположение оказалось верным, хотя в MSDN от октября 2005 об этом ничего не сказано Посмотрев, откуда берется родительское окно, обращаем внимание на строку FRichEdit.Parent := frxParentForm; все в том же конструкторе frxRichView и начинаем разбираться что такое frxParentForm и как нам с ней жить. Недолгие разбирательства в коде функции frxParentForm (модуль frxClass) еще раз убеждают нас в верности нашего предположения - она просто создает пустую форму для использования в качестве родительской во внутренних компонентах у TfrxRichView, TfrxOLEView и т.д. А если таковая форма была уже создана ранее, то она и возвращается. И тут приходит озарение! Так вот почему отчет работал один раз, а следующий уже не работал! Родительская форма для frxRichView создавалась при первом его вызове, а при втором возвращалась она же. Это бы работало (почему, собственно "бы"? - это работает!), если бы это был обычный локальный отчет, каждый раз строящийся в одном и том же потоке. Но у нас (будем справедливы! - у Fast Reports Inc. ) каждый отчет строится в своем потоке (конечно! это же сервер!) а родительское окно каждый поток получает одно и то же. Чуть чуть подумав, переписываем frxParentForm так, чтобы она каждому потоку выдавала свою родительскую форму и убивала родительские формы завершившихся потоков, для этого правим модуль frxClass следующим образом: изменяем frxParentForm следующим образом (обратите внимание - нам понадобятся два списка для потоков и созданных ими форм: Код: var // Lam ThreadsList, ParentFormsList: TList; // lists for saving IDs of thread // Lam // that created parent window // Lam function frxParentForm: TForm; var // Lam Index: integer; // Lam begin // Lam Index := ThreadsList.IndexOf(pointer(GetCurrentThreadID)); // Lam if Index=-1 then // Lam begin // Lam ThreadsList.Add(pointer(GetCurrentThreadID)); // Lam Index := ParentFormsList.Add(TForm.Create(nil)); // Lam end; // Lam Result := TForm(ParentFormsList[Index]); // Lam end; | В initialization модуля добавляем Код: ThreadsList := TList.Create; // Lam ParentFormsList := TList.Create; // Lam | и, соответственно в finalization Код: if ParentFormsList.Count>0 then // Lam EmptyParentForm; // Lam ThreadsList.Free; // Lam ParentFormsList.Free; // Lam | Исправляем процедуру frxEmptyParentForm как показано ниже Код: procedure EmptyParentForm; var // Lam i: integer; // Lam begin for i:=0 to ParentFormsList.Count-1 do // Lam while TForm(ParentFormsList[i]).ControlCount>0 do // Lam TForm(ParentFormsList[i]).Controls[0].Parent := nil; // Lam end; | после чего, в принципе, все упоминания переменной FParentForm из модуля можно спокойно убрать. Далее, в интерфейс frxClass там, где описана frxParentForm добавляем еще одну функцию Код: procedure frxDestroyParentForm(ThreadID: integer = 0); // Lam | (честно говоря, параметр ей в общем-то не нужен, но пусть он будет... на будущее и в implementation Код: procedure frxDestroyParentForm(ThreadID: integer = 0); // Lam var // Lam Index: integer; // Lam begin // Lam if ThreadID=0 then // Lam ThreadID := GetCurrentThreadID; // Lam // Lam Index := ThreadsList.IndexOf(pointer(ThreadID)); // Lam if Index<>-1 then // Lam begin // Lam TForm(ParentFormsList[Index]).Free; // Lam ThreadsList.Delete(Index); // Lam ParentFormsList.Delete(Index); // Lam end; // Lam end; // Lam | Ну и надо кого-то заставить вызвать эту функцию по окончании работы потока, строящего отчет. Сделаем это в TfrxReportSession.Destroy (модуль frxServerReports): Код: if Assigned(FReport) then try FReport.Free; frxDestroyParentForm; // Lam except ... | Все. Сага о багах закончена. Компилируем, запускаем, бежим к начальнику показывать как работают отчеты на корпоративном сайте и в очередной раз уговаривать официально купить FastReport Enterprise Edition Надеюсь, ничего не забыл. | Всего записей: 34 | Зарегистр. 13-01-2003 | Отправлено: 09:41 27-04-2006 | Исправлено: DmLam, 12:35 22-05-2006 |
|