在新挂接的 LoadResString 方法中读取字符串列表时出现 StackOverflow 异常

Getting StackOverflow exception while reading stringlist in newly hooked LoadResString method

供参考 - 以下是我的代码,其中我在 NewLoadResString 函数中遇到 Whosebug 异常。这种情况就像我创建了两个字符串列表,即 RecStrNameIdMap 和 NewStringValueList。 这里的 RecStrNameIdMap 是 hash string list 来存储 name 和 string Identifier 映射。这样我就可以为其标识符引用资源字符串名称,即 ID.

NewStringValueList 是一个字符串列表,其中包含少数资源字符串的新值。

我已经在 system.LoadResString 方法上连接了 NewLoadResString 方法。在新方法中,我正在检查 NewStringValueList 中给定资源字符串是否有新值,然后获取该值和 return 新值而不是旧声明值。

第 *

行发生堆栈溢出异常

if RecStrNameIdMap.IndexOfName(IntToStr(ResStringRec^.Identifier)) > -1 then

* 任何人都可以检查我为什么会收到此错误。

unit UnitTest;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, IniFiles, StdCtrls;

type
  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;


type
  TMethodHook = class
  private
    aOriginal : packed array[ 0..4 ] of byte;
    pOldProc, pNewProc : pointer;
    pPosition : PByteArray;
  public
    constructor Create( pOldProc, pNewProc : pointer );
    destructor Destroy; override;
  end;


var
  Form2: TForm2;

implementation

{$R *.dfm}

ResourceString
  RS_1 = 'ABC';
  RS_2 = 'XYZ';

procedure TForm2.Button1Click(Sender: TObject);
var
  aMethodHook: TMethodHook;
  RecStrNameIdMap: THashedStringList;
  NewStringValueList: TStringList;

  {Hookup aNewProcedure on aOriginalProcedure}
  procedure RegisterProcedures(aOriginalProcedure, aNewProcedure: pointer);
  begin
    if Assigned(aOriginalProcedure) and Assigned(aNewProcedure) then
      aMethodHook := TMethodHook.Create( aOriginalProcedure, aNewProcedure);
  end;

  {Replacement for System.LoadResString}
  function NewLoadResString(ResStringRec: PResStringRec): String;
  var
    Buffer: array [0..4095] of char;
  begin
    if ResStringRec = nil then Exit;
    if ResStringRec.Identifier >= 64 * 1024 then
    begin
      Result := PChar(ResStringRec.Identifier);
    end
    else
    begin
      if RecStrNameIdMap.IndexOfName(IntToStr(ResStringRec^.Identifier)) > -1 then
      begin
        Result := NewStringValueList.Values[
          RecStrNameIdMap.Values[IntToStr(ResStringRec^.Identifier)]];
      end
      else
      begin
        SetString(Result, Buffer,
          LoadString(FindResourceHInstance(ResStringRec.Module^),
            ResStringRec.Identifier, Buffer, SizeOf(Buffer)));
      end;
    end;
  end;

  procedure CreateNameIdMapping;
  begin
    {This is done to get string name from ID}
    RecStrNameIdMap.CaseSensitive := False;
    RecStrNameIdMap.Add(Inttostr(PResStringRec(RS_2)^.Identifier)+'='+'XYZ');
  end;

begin
  aMethodHook := nil;
  try
    RecStrNameIdMap := THashedStringList.Create;
    NewStringValueList := TStringList.Create;

    CreateNameIdMapping;

    {Create new value list for ResourceStrings}
    NewStringValueList.Add('XYZ'+'='+'new value for ResourceString RS_2');
    RegisterProcedures(@System.LoadResString, @NewLoadResString);

    {This should return 'new value for ResourceString RS_2' instead of 'XYZ'}
    ShowMessage(RS_2);

    {This should return 'ABC' - no change in value}
    ShowMessage(RS_1);
  finally
    aMethodHook.Free;
    RecStrNameIdMap.Free;
    NewStringValueList.Free;
  end;
end;

{ TMethodHook }

constructor TMethodHook.Create(pOldProc, pNewProc: pointer);
var
  iOffset : integer;
  iMemProtect : cardinal;
  i : integer;
begin
  Self.pOldProc := pOldProc;
  Self.pNewProc := pNewProc;

  pPosition := pOldProc;
  iOffset := integer( pNewProc ) - integer( pointer( pPosition ) ) - 5;

  for i := 0 to 4 do aOriginal[ i ] := pPosition^[ i ];

  VirtualProtect( pointer( pPosition ), 5, PAGE_EXECUTE_READWRITE,
    @iMemProtect );

  pPosition^[ 0 ] := $E9;
  pPosition^[ 1 ] := byte( iOffset );
  pPosition^[ 2 ] := byte( iOffset shr 8 );
  pPosition^[ 3 ] := byte( iOffset shr 16 );
  pPosition^[ 4 ] := byte( iOffset shr 24 );
end;

destructor TMethodHook.Destroy;
var
  i : integer;
begin
  for i := 0 to 4 do pPosition^[ i ] := aOriginal[ i ];
  inherited;
end;

end.

替换过程好像不能是嵌套例程
documentation 中所述:

Procedural types allow you to treat procedures and functions as values that can be assigned to variables or passed to other procedures and functions.

...

Nested procedures and functions (routines declared within other routines) cannot be used as procedural values, nor can predefined procedures and functions.

过程类型是指针。虽然嵌套例程不能用作过程类型,但我假设指向嵌套例程的指针不能用作过程参数,否则此操作可能会产生不可预测的结果,如本例所示。
该过程已正确挂钩(您做到了);我提取了过程 NewLoadResString 并且 Whosebug 错误不再发生。
弹出的 resourcestring 始终是旧的,但我没有对 NewLoadResString 程序进行任何更改。
整个编辑单元如下。

unit UnitTest;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, IniFiles, StdCtrls;

type
  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    RecStrNameIdMap: THashedStringList;
    NewStringValueList: TStringList;
  public
    { Public declarations }
  end;


type
  TMethodHook = class
  private
    aOriginal : packed array[ 0..4 ] of byte;
    pOldProc, pNewProc : pointer;
    pPosition : PByteArray;
  public
    constructor Create( pOldProc, pNewProc : pointer );
    destructor Destroy; override;
  end;


var
  Form2: TForm2;

implementation

{$R *.dfm}

ResourceString
  RS_1 = 'ABC';
  RS_2 = 'XYZ';


{Replacement for System.LoadResString}
function NewLoadResString(ResStringRec: PResStringRec): String;
var
  Buffer: array [0..4095] of char;
begin
  if ResStringRec = nil then Exit;
  if ResStringRec.Identifier >= 64 * 1024 then
  begin
    Result := PChar(ResStringRec.Identifier);
  end
  else
  begin
    if RecStrNameIdMap.IndexOfName(IntToStr(ResStringRec^.Identifier)) > -1 then
    begin
      Result := NewStringValueList.Values[
        RecStrNameIdMap.Values[IntToStr(ResStringRec^.Identifier)]];
    end
    else
    begin
      SetString(Result, Buffer,
        LoadString(FindResourceHInstance(ResStringRec.Module^),
          ResStringRec.Identifier, Buffer, SizeOf(Buffer)));
    end;
  end;
end;

procedure TForm2.Button1Click(Sender: TObject);
var
  aMethodHook: TMethodHook;

  {Hookup aNewProcedure on aOriginalProcedure}
  procedure RegisterProcedures(aOriginalProcedure, aNewProcedure: pointer);
  begin
    if Assigned(aOriginalProcedure) and Assigned(aNewProcedure) then
      aMethodHook := TMethodHook.Create( aOriginalProcedure, aNewProcedure);
  end;

  procedure CreateNameIdMapping;
  begin
    {This is done to get string name from ID}
    RecStrNameIdMap.CaseSensitive := False;
    RecStrNameIdMap.Add(Inttostr(PResStringRec(RS_2)^.Identifier)+'='+'XYZ');
  end;

begin
  aMethodHook := nil;
  RecStrNameIdMap := THashedStringList.Create;
  NewStringValueList := TStringList.Create;
  try
    CreateNameIdMapping;

    {Create new value list for ResourceStrings}
    NewStringValueList.Add('XYZ'+'='+'new value for ResourceString RS_2');
    RegisterProcedures(@System.LoadResString, @NewLoadResString);

    {This should return 'new value for ResourceString RS_2' instead of 'XYZ'}
    ShowMessage(RS_2);

    {This should return 'ABC' - no change in value}
    ShowMessage(RS_1);
  finally
    aMethodHook.Free;
    RecStrNameIdMap.Free;
    NewStringValueList.Free;
  end;
end;

{ TMethodHook }

constructor TMethodHook.Create(pOldProc, pNewProc: pointer);
var
  iOffset : integer;
  iMemProtect : cardinal;
  i : integer;
begin
  Self.pOldProc := pOldProc;
  Self.pNewProc := pNewProc;

  pPosition := pOldProc;
  iOffset := integer( pNewProc ) - integer( pointer( pPosition ) ) - 5;

  for i := 0 to 4 do aOriginal[ i ] := pPosition^[ i ];

  VirtualProtect( pointer( pPosition ), 5, PAGE_EXECUTE_READWRITE,
    @iMemProtect );

  pPosition^[ 0 ] := $E9;
  pPosition^[ 1 ] := byte( iOffset );
  pPosition^[ 2 ] := byte( iOffset shr 8 );
  pPosition^[ 3 ] := byte( iOffset shr 16 );
  pPosition^[ 4 ] := byte( iOffset shr 24 );
end;

destructor TMethodHook.Destroy;
var
  i : integer;
begin
  for i := 0 to 4 do pPosition^[ i ] := aOriginal[ i ];
  inherited;
end;

end.