понедельник, 24 февраля 2014 г.

Ошибка загрузки в RichEdit большого блока данных

Давеча пришлось дорабатывать одну из утилит сбора информации о системе и как-то неожиданно для меня от отдела тестирования пришел багрепорт плана:
Утилита формирует и сохраняет данные правильно, но загружает их в искаженном виде.


Ну точнее не то, чтобы неожиданно...
С чем-то подобным я уже ранее встречался, когда писал компаратор карт памяти процесса, вот для этой утилиты, но тогда, если честно, не сильно обратил на этот нюанс внимание, ибо тогда мне нужна была скорость загрузки информации в RichEdit.

Но та работа была в виде хобби, а тут собственно рабочий проект.


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

Такой код загружает файл большого объема в RichEdit с ошибками как на верхней картинке:

RichEdit1.Lines.LoadFromFile('updates.rtf');

А вот такой, грузит правильно:

function RichEditStreamCallBack(Cookie: TMemoryStream; pbBuff: PByte;
  cb: Longint; var pcb: Longint): Longint; stdcall;
begin
  Result := 0;
  try
    pcb := Cookie.Read(pbBuff^, cb);
  except
    Result := 1;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  M: TMemoryStream;
  Param: TEditStream;
begin
  M := TMemoryStream.Create;
  try
    M.LoadFromFile('updates.rtf');
    {$IFDEF WIN64}
    Param.dwCookie := DWORD_PTR(M);
    {$ELSE}
    Param.dwCookie := LongInt(M); // LongInt для совместимости со старыми версиями Delphi
    {$ENDIF}
    Param.dwError := 0;
    param.pfnCallback := @RichEditStreamCallBack;
    SendMessage(RichEdit2.Handle, EM_STREAMIN, SF_RTF, LPARAM(@Param));
  finally
    M.Free;
  end;
end;

Сравнительный результат:



Сам код: http://rouse.drkb.ru/blog/rtf.zip

Судя по всему ошибка внутри метода загрузки в VCL обертке.

15 комментариев:

  1. А там нету ещё Борландового IStream.CopyTo? Там помнится были проезды по памяти.

    ОтветитьУдалить
  2. Надо в QC запостить.

    ОтветитьУдалить
  3. В QC смысла постить большого не имеет, т.к. причина такого поведения в том что после отправки сообщения EM_STREAMIN, возвращается ошибка STG_E_MEDIUMFULL в EditStream.dwError, после чего данные грузятся как PlainText.
    Этот код выставляется внутри RICHED20.DLL, а почему - не понятно.
    Да, кстати, под ХЕ4 ошибка загрузки в таком виде не воспроизводится, нужно грузить данные немного по другому.

    ОтветитьУдалить
  4. В XE3 все работает как часы
    http://i011.radikal.ru/1402/7a/4f7ac5fd472b.jpg

    ОтветитьУдалить
    Ответы
    1. Во всех юникодных работает нормально, но если данные приготовить немного не так, то будет ошибка. Завтра выложу демку. Причем там так же причины поведения понять не могу - тот-же массив данных (что со стрима что с файла), но либо ошибка если грузить из памяти, либо все нормально если с диска - прям беда :)

      Удалить
    2. * ошибка - во всех "юникодных" работает нормально, если грузить именно с диска.
      Хотя разница не понятна - там по любому преобразования на юникод нет.

      Удалить
    3. Ок, жду думку... И комментарии о том, что значит "данные приготовить немного не так", а я завтра на работе на D7 гляну... не то, чтобы не доверяю, просто я с такой проблемой не сталкивался, хотя rtf в richedit загружал не однократно :)

      Удалить
    4. Там загрузка стрима из ZIP архива, я завтра постараюсь это воспроизвести на стандартом наборе классов, ну а если не получится (хоты врятли) тогда уже покажу вариант через свой FWZipReader.
      Соль даже не в этом, Delphi7 гарантированно воспроизводит ошибку, хотя откуда ей взяться то?

      Удалить
    5. Ан нет, я был не прав что грешил на ZIP, не в этом оказалось дело.
      Под ХЕ4 такое поведение воспроизводится в том случае, если изначально (в самом DFM) параметр Lines был очищен. Если же он содержит хотя бы одну строку - такого поведения не наблюдается.
      Обновил исходный код примера (с правленым DFM) можно проверять.

      Удалить
    6. Да, теперь ошибка воспроизводится стабильно. Спасибо за пример решения, положу в копилку, пригодится :)

      Удалить
    7. Я его чуть-чуть еще раз обновил :)
      Писал на скорую руку и забыл про совместимость с 64 битами, пардон...

      Удалить
    8. в прошлом десятилетии еще ковырялся - невнятных глюков все равно много остаётся(встречаются неперевариваимые документы)
      через Text Services Framework - вроде стабильнее работает...
      ( DllName: 'MSFTEDIT.DLL'; WndClass: MSFTEDIT_CLASS; Version: -1 )
      вместо
      ( DllName: 'RICHED20.DLL'; WndClass: RICHEDIT_CLASSW; Version: -1 ),


      http://flocke.vssd.de/prog/code/pascal/rtflabel/
      Copyright (C) 2006 Volker Siebert

      {$IFDEF USE_TOM}
      if(RichEdit_GetOleInterface(Handle, FOle))then begin
      QueryThiscallInterface(FOle, IID_ITextServices, FServices);
      end;
      {$ENDIF}
      ...
      {$IFDEF USE_TOM}
      FServices.TxSendMessage(EM_STREAMIN, TextType, LongWord(@ems), res);
      {$ENDIF}

      Удалить
    9. Угу, спасибо - возьму на заметочку.

      Удалить
  5. Такой способ грузит только часть текста, а не весь.

    ОтветитьУдалить
    Ответы
    1. http://delphimaster.net/view/3-1160110659

      RichEdit1.MaxLength:= 2147483632; //Рішення проблеми
      RichEdit1.Lines.LoadFromFile(OpenDialog1.FileName);

      Удалить