12

System.Classes.pas の既知の醜いパフォーマンス制限を回避しようとしています。この制限には、次のような 1980 年代の一定のバッファー制限 ($F000) があります。

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

これにより、Delphi アプリケーションのパフォーマンスが大幅に低下します。Delphi XE2 から XE5 では、これを変更して、次のアプローチのいずれかを使用することができました。

  • Delphi ソースを変更し、バッチ ファイルから dcc32.exe を呼び出して、Delphi ライブラリ フォルダ内の System.Classes.dcu ファイルを再構築できます。私はこれが醜いことを認識しており、これを行うのは好きではありませんでしたが、RTL でのこの醜いパフォーマンスの問題も好きではなく、ユーザーはそれが引き起こすパフォーマンスの頭痛に耐えられません。

  • 変更した system.classes.pas ファイルをプロジェクトの検索パスのどこかに置くことができます。

おそらく内部コンパイラの変更のおかげで、上記のアプローチのどちらもDelphi XE6で機能していません。uses 句に 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 以前では問題になりません。

System.Classes.pas または System.SysUtils.pas または他の非常に低レベルのユニットの変更されたコピーを使用して、基本的な RTL または VCL の制限を絶対に回避する必要がある場合に推奨される方法は何ですか? (はい、必要がないのであれば、これを行うべきではないことはわかっています。講義を気にしないでください。)

上記のアプリケーション例と適切にリンクする変更された DCU を生成するために、コマンド ラインで「dcc32.exe」を介して使用できるコマンド ライン パラメータの魔法のセットはありますか?

二次的な質問として、これを行おうとすると破損するソースが存在しない .dcu ファイルはありますか? RTL では、あなたは不運です」?

考えられる回避策の 1 つは、プロジェクトの検索パス (またはライブラリ パス) に "$(BDS)\source\rtl\common" を含めて、壊れた (再コンパイルが必要な) DCU を毎回強制的に再構築することですが、これは見苦しく間違っているように思えます。

4

2 に答える 2

10

迂回を使用してこの制限を克服できます。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);
于 2014-06-10T16:17:40.620 に答える
6

更新 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+}

で十分です。

于 2014-06-10T15:50:46.507 に答える