Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Для новичков > Утечка памяти на стыке приложение - WebBrowser


Автор: kami 11.11.2012, 20:37
Здравствуйте, дамы и господа!

Имею приложение, содержащее только TWebBrowser.
В него загружается локальная страничка (подгружающая карту из интернета), после чего - начинают выполняться JavaScript, динамически генерируемые программой (машинка передвигается по карте).
Память начинает кушаться (смотрю ProcessExplorer-ом, Private bytes). Причем, это заметно даже на одном объекте. А их предполагается около полутора тысяч. По моим подсчетам, чуть меньше, чем через сутки приложение свалится с Out of memory. А оно должно работать 24х7. На этом тестовом примере за 8 часов работы размер программы (VirtualSize) возрастает до 1 Гб.

FastMM молчит.

если убрать всего одну строчку, а именно - передачу скрипта в TWebBrowser, то потребление памяти не увеличивается:
Код

procedure TForm4.ExecScript(Script: string);
var
  Doc: IHTMLDocument2; // current HTML document
  HTMLWin: IHTMLWindow2; // parent window of current HTML document
begin
  Doc := IHTMLDocument2(wb1.ControlInterface.Document);
  HTMLWin := Doc.parentWindow;
  if Assigned(HTMLWin) then
    begin
      try
        HTMLWin.ExecScript(Script, 'JavaScript');// убирание этой строки приводит к нормальной работе
      except
      end;
    end;
end;

Если использовать этот же код (в смысле - тот, который передается в работающий TwebBrowser) непосредственно на странице, открытой в IE, то память так же не возрастает.

Просмотрел в интернете, что пишется по этому поводу, нашел только одно: рекомендуют не пользоваться свойством TWebBrowser.Document. Перешел на использование TWebBrowser.ControlInterface - не помогло.
Пытался использовать TEmbeddedWB - проблема та же.

Помогите решить...

Примечание: во вложении тестовое приложение и файлы: 2GISMap.html предназначен для работы в составе приложения, "2GISMap - самостоятельная работа.html " - для отображения непосредственно в браузере.

Автор: MetalFan 12.11.2012, 07:11
Ну так правильно, скрипт то какой? видимо в нем ресурсы какие-то выделяются браузером. В жаваскрипте ж нет необходимости очищать память за собой, все по идее сделает уборщик... отсюда и возможны утечки. Это вопрос скорее в раздел по жаваскрипту.
Предлагаю var myMarker объявить в html файле, а в скрипте в pas файле убрать var, т.е. просто работать с этой переменной. для начала. далее наверное в раздел веб-программирования таки. имхо.

кстати, и еще, если планируется работа в оффлайне, то в html файле неплохо бы перелинковать 
Код

<script type="text/javascript" src="http://maps.api.2gis.ru/1.0"></script> 

на локальный файл.

Автор: kami 12.11.2012, 09:03
Цитата(MetalFan @  12.11.2012,  07:11 Найти цитируемый пост)
кстати, и еще, если планируется работа в оффлайне, то в html файле неплохо бы перелинковать 

Не планируется, к сожалению. Более того - это запрещено договорными обязательствами моей компании с 2ГИС.

Цитата(MetalFan @  12.11.2012,  07:11 Найти цитируемый пост)
 далее наверное в раздел веб-программирования таки. имхо.

В том-то и дело, что я уже думал туда обратиться. Но - там мне скажут что-то типа : 
Цитата

родной, в standalone-скрипте всё работает? Работает. "Чего ж тебе еще надо, собака" (с) smile Разбирайся со своим Delphi.


Цитата(MetalFan @  12.11.2012,  07:11 Найти цитируемый пост)
все по идее сделает уборщик... отсюда и возможны утечки

Вот оно!
Моей благодарности нет предела ! smile 

По всей видимости, IE при выполнении динамически "засланного" скрипта просто не закидывает информацию в сборщик мусора. В том числе - не помогают и вынесение переменных в глобальные с обnullением (а так же не обnullением) после использования. Возможно, это связано с достаточно большой частотой выполнения скриптов.
Выход - сотворить необходимые функции непосредственно в html файле с нужными параметрами и вызывать их через TembeddedWB.ExecScriptEx. Почему-то в этом случае GC у IE работает без замечаний.

Правда, в этом случае всплыл один нюанс с обрезанием строк при передаче в качестве параметров... Буду копать исходники EWB, гуглить, если не найду - тогда опять на форум smile

Автор: Illusion Dolphin 12.11.2012, 09:08
Цитата

а в скрипте в pas файле убрать var, т.е. просто работать с этой переменной. для начала.

Это не тру для Javascript, тем более для IE.

Цитата

за 8 часов работы размер программы (VirtualSize) возрастает до 1 Гб.

А что с Private Bytes? 

А что за скрипт выполняется? Я ркомендую статью на learn.javascript.ru по утечкам памяти в JavaSctipt http://learn.javascript.ru/optimize - там много идей можно подсмотреть, где могут быть утечки.

Цитата

кстати, и еще, если планируется работа в оффлайне, то в html файле неплохо бы перелинковать 
Выделить всёРазметка HTML
1:
    
<script type="text/javascript" src="http://maps.api.2gis.ru/1.0"></script> 

на локальный файл.


Хихи smile Не надо. Это онлайн карта.

Автор: kami 12.11.2012, 09:18
Цитата(Illusion Dolphin @  12.11.2012,  09:08 Найти цитируемый пост)
А что с Private Bytes? 

Не помню уже, но с примерно 24 мегабайт выросло до... чтоб не соврать, где-то на порядок, т.е. где-то до 200

Цитата(Illusion Dolphin @  12.11.2012,  09:08 Найти цитируемый пост)
А что за скрипт выполняется?

В тестовом примере - только один, выполняющийся по таймеру (однократно выполняемый скрипт при загрузке странички рассматривать не будем). Выглядит так:
Код

var myMarker = staticGroup.get(index);
myMarker.setPosition(new DG.GeoPoint(NewLon, NewLat)); // при работе непосредственно в IE утечки нет.


Цитата(Illusion Dolphin @  12.11.2012,  09:08 Найти цитируемый пост)
Я ркомендую статью на learn.javascript.ru по утечкам памяти

Читал, увы - не помогло...

Автор: kami 12.11.2012, 10:16
Цитата(kami @  12.11.2012,  09:03 Найти цитируемый пост)
всплыл один нюанс с обрезанием строк при передаче в качестве параметров...

Поборол с помощью гугла.
Код

EmbeddedWB.ExecScriptEx('myMethod', [WideString(strParam)]); // передача без преобразования, как UnicodeString обрезает строку. Применимо для D2009up, 
// как с этим обстоит в до-юникодных версиях - не знаю, возможно такой проблемы и нет.
// почему так - не разбирался, нет времени копать исходники EWB.

Автор: MetalFan 12.11.2012, 22:35
Цитата(Illusion Dolphin @  12.11.2012,  09:08 Найти цитируемый пост)
Это не тру для Javascript, тем более для IE.

почему?

Автор: MetalFan 12.11.2012, 23:01
kami, кстати, поменял твой код немного.
Похоже, что проблемы то с "утечкой" при выполнении то и нет...
Код

  if FFlag then
  begin
    FLatitude := FLatitude + 0.001;
    FLongitude := FLongitude + 0.001;
  end
  else
  begin
    FLatitude := FLatitude - 0.001;
    FLongitude := FLongitude - 0.001;
  end;
  FFlag := not FFlag;

Если дергаем ее "на месте", то память практически не "утекает".
Похоже, что просто динамическая подгрузка карты кушает почуть память...

Автор: Illusion Dolphin 13.11.2012, 00:10
Цитата

почему? 

Для элементов с id некотоные ие автоматически создают переменные (с именем как id) и если не писать var то можно натолкнуться на странные ошибки. 

Автор: MetalFan 13.11.2012, 07:03
Illusion Dolphin, так я предлагал перенести объявление переменной (var)  в тело скрипта на самой странице... а не вообще отказаться от var.

Автор: kami 13.11.2012, 07:34
Цитата(MetalFan @  12.11.2012,  23:01 Найти цитируемый пост)
Похоже, что проблемы то с "утечкой" при выполнении то и нет...

Может, и нет.
Но более реальный код, чем в моем вопросе (300 объектов, на нем как раз-таки и была обнаружена проблема роста памяти), работающий через EmbeddedWB.ExecScriptEx (вместо ExecScript) за те же 8 часов дал прирост PrivateBytes с 33 до 34 Мб. В то время, как работа ExecScript... ну, об этом я писал уже smile .

Еще раз - огромное спасибо за вариант решения проблемы, над которой я бился 3 дня..

Добавлено @ 07:36
Цитата(MetalFan @  12.11.2012,  23:01 Найти цитируемый пост)
Похоже, что просто динамическая подгрузка карты кушает почуть память...

Карта, вообще-то должна была загрузиться 1 раз и всё. Подгрузка данных должна была производиться только при передвижении карты мышкой или изменении ее масштаба.

Upd: кстати, возможно сыграла свою роль и небольшая правка исходников как WB, так и EWB - замена property Document с index ххх на Get_Document. Но - это было нагуглено первым делом до постановки вопроса.

Автор: MetalFan 13.11.2012, 13:24
Цитата(kami @  13.11.2012,  07:34 Найти цитируемый пост)
небольшая правка исходников как WB, так и EWB - замена property Document с index ххх на Get_Document

а можно по-подробнее, так сказать, для анналов истории)

Автор: kami 13.11.2012, 16:30
MetalFan, конечно. Собственно, это не является секретом, Яндекс по запросу "WebBrowser утечка памяти" сразу выдает кучу тем, где это обсуждается.
Цитата

 Вы попытаетесь использовать TWebBrowser.Document:IDispatch, то Вы рано или поздно заметите довольно странную "утечку" памяти в процессе навигации. В чем же дело? После анализа ситуации, удалось определить, что для любой интерфейсной ссылки на документ, которая получена через этот свойство, счетчик использования "необоснованно" увеличивается на 1 и соответствующий COM-обьект никогда не будет освобожден. При более детальном изучении нашлась и создательница этой проблемы - function TOleControl.GetIDispatchProp(Index: Integer): IDispatch;, через которую и работает TWebBrowser.Document:IDispatch. При этом, несмотря на наличие функции Get_Document, она нигде не используется.

Подводя итог (все-таки, форум для начинающих):
для TEmbeddedWB - открываем файл SHDocVw_EWB, ищем в классе TEWB строчку
Код
property Document: IDispatch index 203 read GetIDispatchProp;

и меняем ее на
Код
property Document: IDispatch read Get_Document; // index 203 read GetIDispatchProp;

После этого - перебилд пакета, в котором TEmbeddedWB сидит.

Аналогично меняется для TWebBrowser, но думаю, что в этом случае нужно будет перезакинуть новую dcu-шку в папку Lib.
Не мой, правда, случай - у меня навигация по страницам не ведется, так что Document: IHtmlDocument2(3,4,5) будет уничтожаться только вместе с приложением

Кроме того, как я уже писал в вопросе, всезнающий интернет рекомендует использовать как WB, так и EWB исключительно через их свойство ControlInterface. Но, если при использовании WB еще можно с этим смириться, то заниматься копипастом плюшек EWB, как, например, ExecScriptEx считаю излишним.

Автор: gonan 9.5.2017, 16:16
Цитата(kami @  13.11.2012,  16:30 Найти цитируемый пост)
После этого - перебилд пакета, в котором TEmbeddedWB сидит.

Аналогично меняется для TWebBrowser, но думаю, что в этом случае нужно будет перезакинуть новую dcu-шку в папку Lib.


Подскажите как сделать "перебилд пакета", Что то не могу сообразить. Сам файл SHDocVw.pas (C:\Program Files (x86)\Embarcadero\Studio\16.0\source\internet) подправил.
Компонент TWebBrowser
Делфи XE8,   Win 8.1 x64

Автор: kami 14.5.2017, 20:06
Цитата(gonan @  9.5.2017,  16:16 Найти цитируемый пост)
Подскажите как сделать "перебилд пакета", Что то не могу сообразить.

Вам нужно сделать что-то из нижеперечисленного:
1. просто закинуть измененный pas-файл в папку с dproj-файлом вашего проекта. Этого будет достаточно. Учтите, что в этом случае вы привязываетесь к конкретной версии Delphi, при компиляции на другой версии потребуется удалить этот файл из папки с проектом, чтобы он не подхватывался автоматом.
2. Включить папку, в которой лежит измененный pas, в Project-Options-LibraryPath. Кажется, этого тоже будет достаточно, но можно попробовать поставить брейкпоинт в этом (обращаем внимание на пути!) файле (например - в конструкторе TWebBrowser) и убедиться, что остановка происходит. Т.е. берется именно измененный файл.
3. Если есть dcu измененного pas-файла, то можно заменить его в library-папке Delphi (нежелательно).

Самый предпочтительный вариант, на мой взгляд - первый. Не стоит без очень весомого основания менять генофонд (т.е. исходные исходники).

Автор: gonan 15.5.2017, 21:44
Цитата(kami @  14.5.2017,  20:06 Найти цитируемый пост)
Самый предпочтительный вариант, на мой взгляд - первый. Не стоит без очень весомого основания менять генофонд (т.е. исходные исходники). 


Сделал по первому варианту. Исправленный файл SHDocVw.pas положил в папку с dproj-файлом проекта. Рядом с EXE появился файл SHDocVw.dcu. 
Но к сожалению не помогло. Память растёт. После 600-700 раз вызова метода WebBrowser1.Navigate и 1 ГБ уже обеспечен. А дальше вылетать начинает программа с ошибками.

RAD Studio 10.1 Berlin

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)