Могу ли я изменить константу в классе 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 перестраивать КАЖДОЕ время, но это кажется уродливым и неправильным.
2 ответа
Вы можете преодолеть это ограничение, используя объезд, попробуйте этот пример, который использует 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);
Обновление 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+}
достаточно.