Подтвердить что ты не робот

Могу ли я изменить константу в классе RTL System.Classes.TStream и перестроить ее во время выполнения в Delphi XE6?

Я пытаюсь работать с известным уродливым ограничением производительности в System.Classes.pas, который имеет ограничение буфера постоянной эры 1980-х ($ F000), которое выглядит следующим образом:

function TStream.CopyFrom(const Source: TStream; Count: Int64): Int64;
const
  MaxBufSize = $F000;
....

Это приводит к серьезным нарушениям производительности в нашем приложении Delphi. В delphi XE2 - XE5 мы смогли изменить это и использовать один из следующих подходов:

  • Я мог бы изменить источники Delphi, а затем, вызвав dcc32.exe из командного файла, перестроит файл System.Classes.dcu в папке библиотеки Delphi. Я понимаю, что это некрасиво, и мне это не нравилось, но мне не нравится эта уродливая проблема с производительностью в RTL, и наши пользователи не могут жить с головными болями производительности, которые она вызывает.

  • Я мог бы попытаться поместить модифицированный файл system.classes.pas где-нибудь в пути поиска проекта.

Ни один из вышеперечисленных подходов не работает для меня в Delphi XE6, теперь, возможно, благодаря некоторым внутренним изменениям компилятора. Ошибка, которую я получаю в приложении с минимальной командной строкой, которое включает в себя условие System.Contnrs в своем примере использования:

[dcc32 Fatal Error] System.Classes.pas(19600): F2051 Unit System.Contnrs was compiled with a different version of System.Classes.TComponent

Образец программы для воспроизведения этой проблемы (если вы изменили System.Classes.pas и изменили константу MaxBufSize), показан здесь:

program consoletestproject;

{$APPTYPE CONSOLE}

{$R *.res}

uses
   System.Contnrs,
   System.SysUtils;

var
  List:System.Contnrs.TObjectList;
begin
   WriteLn('Hello world');
end.

Опять же, эта проблема легко воспроизводится в Delphi XE6, но не является проблемой в XE5 или ранее.

Какова рекомендуемая практика, когда вы абсолютно ДОЛЖНЫ работать с фундаментальным ограничением RTL или VCL с использованием модифицированной копии System.Classes.pas или System.SysUtils.pas или какого-либо другого устройства с очень низким уровнем? (Да, я знаю, что вы НЕ должны этого делать, если вам это не нужно, не беспокойтесь о лекции.)

Есть ли волшебный набор параметров командной строки, которые вы можете использовать с помощью "dcc32.exe" в командной строке, чтобы создать измененное DCU, которое будет правильно связываться с примером приложения выше?

В качестве второстепенного вопроса существуют файлы .dcu, для которых не существует источника, который будет ломаться, когда кто-то пытается это сделать, и в этом случае ответ на все вышеперечисленное есть: "вы не можете исправить это, и если там ошибка в RTL, вам не повезло"?

Одним из возможных способов решения проблемы является включение "$ (BDS)\source\rtl\common" в путь поиска по проекту (или путь к библиотеке), заставляющий каждый разбитый (нуждающийся в перекомпиляции) DCU перестраивать КАЖДОЕ время, но это кажется уродливым и неправильно.

4b9b3361

Ответ 1

Вы можете преодолеть это ограничение с помощью обхода, попробуйте этот образец, который использует Delphi Detours Library

Сначала определите сигнатуру метода для привязки

var
 Trampoline_TStreamCopyFrom : function (Self : TStream;const Source: TStream; Count: Int64): Int64 = nil;

затем выполните обход

function Detour_TStreamCopyFrom(Self : TStream;const Source: TStream; Count: Int64): Int64;
const
  MaxBufSize = 1024*1024; //use 1 mb now :)
var
  BufSize, N: Integer;
  Buffer: TBytes;
begin
  if Count <= 0 then
  begin
    Source.Position := 0;
    Count := Source.Size;
  end;
  Result := Count;
  if Count > MaxBufSize then BufSize := MaxBufSize else BufSize := Count;
  SetLength(Buffer, BufSize);
  try
    while Count <> 0 do
    begin
      if Count > BufSize then N := BufSize else N := Count;
      Source.ReadBuffer(Buffer, N);
      Self.WriteBuffer(Buffer, N);
      Dec(Count, N);
    end;
  finally
    SetLength(Buffer, 0);
  end;
end;

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

  Trampoline_TStreamCopyFrom     := InterceptCreate(@TStream.CopyFrom,   @Detour_TStreamCopyFrom);

И чтобы выпустить крючок, вы можете использовать

 if Assigned(Trampoline_TStreamCopyFrom) then
   InterceptRemove(@Trampoline_TStreamCopyFrom);

Ответ 2

Обновление 1: Ниже приведенное ниже предложение не работает для блока Classes в XE6. Базовая техника звучит и решает подобные проблемы. Но для XE6, по крайней мере, для модуля Classes, не сразу видно, как его перекомпилировать.

Это, по-видимому, является ошибкой, введенной в XE6, потому что этот метод предназначен для работы и официально одобрен Embarcadero: http://blog.marcocantu.com/blog/2014_august_buffer_overflow_bitmap.html

Обновление 2:

В XE7 эта проблема больше не существует. Казалось бы, все, что было нарушено в XE6, было исправлено.


Вам нужны параметры компилятора, чтобы они соответствовали тем, которые использовались, когда блок был скомпилирован Embarcadero. Причина, по которой ваша секция реализации изменяется только сбой, кажется, что она должна быть успешной.

Запустите проект по умолчанию и используйте CTRL + O + O, чтобы сгенерировать эти параметры. Я получаю

{$A8,B-,C+,D+,E-,F-,G+,H+,I+,J-,K-,L+,M-,N-,O+,P+,Q-,R-,S-,T-,U-,V+,W-,X+,Y+,Z1}

когда я делаю это в XE6.

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

{$R-,T-,H+,X+}

достаточно.