使用Delphi XE4,请尝试以下代码:
procedure TForm3.Button1Click(Sender: TObject);
var
myStr: string;
begin
Edit1.Text := TPerlRegEx.EscapeRegExChars('test');
end;
结果(Edit1.Text
)为空。
这是一个错误还是我错过了什么?我以前使用regular-expressions.info之前的DelphiXE版本的TPerlRegEx.EscapeRegExChars
函数没问题。
更新2 :只是升级在D2010中编写的应用并遇到此错误,但只是想知道这么长的错误是如何存在的......现在我正在认真考虑制作我的错误代码与 Free Pascal 兼容,但我真的很喜欢这种反义词...
更新1:我使用的是Delphi XE4 Update 1.
答案 0 :(得分:2)
这似乎是一个错误。如果是这种情况,XE4和XE5版本都包含它。我打开了一个QC report来报告XE4..XE6。
问题似乎与函数的最后一行有关:
Result.Create(Tmp, 0, J);
在调试器中单步执行显示Tmp
(一个TCharArray)在此时正确包含't','e','s','t', #0, #0, #0, #0
,但当函数实际返回时Result
包含''
,在该行之后的end;
上设置断点表示该点的结果包含''
(以及函数返回时)。
在类助手中提供替换版本,并进行微小更改以实际存储从Create
调用的返回值,从而解决问题:
type
TPerlRegExHelper = class helper for TPerlRegEx
public
class function EscapeRegExCharsEx(const S: string): string; static;
end;
class function TPerlRegExHelper.EscapeRegExCharsEx(const S: string): string;
var
I, J: Integer;
Tmp: TCharArray;
begin
SetLength(Tmp, S.Length * 2);
J := 0;
for I := Low(S) to High(S) do
begin
case S[I] of
'.', '[', ']', '(', ')', '?', '*', '+', '{', '}', '^', '$', '|', '\':
begin
Tmp[J] := '\';
Inc(j);
Tmp[J] := S[I];
end;
#0:
begin
Tmp[J] := '\';
Inc(j);
Tmp[J] := '0';
end;
else
Tmp[J] := S[I];
end;
Inc(J);
end;
{ Result.Create(Tmp, 0, J); } // The problem code from the original
Result := String.Create(Tmp, 0, J);
end;
XE3(以及你提到的开源版本)完全不同地实现逻辑,使用Result
的更标准操作从函数的第一行开始Result := S;
,然后使用根据需要System.Insert
为转义字符添加空间。
答案 1 :(得分:0)
这是XE4版本中引入的一个仍然存在于XE6中的错误。以前的版本很好。看起来这些更改是为了将来切换到不可变字符串而做好准备。
相反具有讽刺意味的是,错误是由字符串永远不会被分配值引起的。设置不改变字符串是一回事,但是永远不要初始化它!
所以要分析一下这个bug。 TPerlRegEx.EscapeRegExChars
单元中定义的System.RegularExpressionsCore
中的相关方法。这是一个返回字符串的类函数。它的签名是:
class function EscapeRegExChars(const S: string): string;
XE4实现仅对结果变量进行一次引用。如下:
Result.Create(Tmp, 0, J);
此处,Tmp
是一个char
数组,其中包含要返回的转义文本,J
是该文本的长度。
因此,似乎很明显,作者希望此代码分配给函数返回变量Result
。可悲的是,这不会发生。为什么不?好吧,被调用的Create
方法是在string
的帮助器中定义的。这是TStringHelper
单位中定义的System.SysUtils
。有三个Create
重载,这里有一个重载:
class function Create(const Value: array of Char; StartIndex: Integer;
Length: Integer): string; overload; static;
请注意,这是一个类静态函数。这意味着它不是一个实例方法,并且没有Self
指针。所以当这样调用时:
Result.Create(Tmp, 0, J);
它只是一个函数调用,其返回值被忽略。可能看起来会设置结果变量,但请记住,此Create
是一个类静态方法。因此它没有实例。编译器只使用Result
类型来解析方法。代码相当于:
string.Create(Tmp, 0, J);
没有什么比调用一个简单忽略返回值的函数更令人兴奋的了。由扩展语法击败,允许我们忽略函数返回值。
对代码的修复很简单。用
替换最后一行Result := string.Create(Tmp, 0, J);
您可以将修复程序应用于单元的副本,并在代码中包含该单元。另一种选择,我的首选方法是使用代码钩子。像这样:
unit FixTPerlRegExEscapeRegExChars;
interface
implementation
uses
System.SysUtils, Winapi.Windows, System.RegularExpressionsCore;
procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
OldProtect: DWORD;
begin
if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
begin
Move(NewCode, Address^, Size);
FlushInstructionCache(GetCurrentProcess, Address, Size);
VirtualProtect(Address, Size, OldProtect, @OldProtect);
end;
end;
type
PInstruction = ^TInstruction;
TInstruction = packed record
Opcode: Byte;
Offset: Integer;
end;
procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
NewCode: TInstruction;
begin
NewCode.Opcode := $E9;//jump relative
NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;
function EscapeRegExChars(Self: TPerlRegEx; const S: string): string;
var
I, J: Integer;
Tmp: TCharArray;
begin
SetLength(Tmp, S.Length * 2);
J := 0;
for I := Low(S) to High(S) do
begin
case S[I] of
'.', '[', ']', '(', ')', '?', '*', '+', '{', '}', '^', '$', '|', '\':
begin
Tmp[J] := '\';
Inc(j);
Tmp[J] := S[I];
end;
#0:
begin
Tmp[J] := '\';
Inc(j);
Tmp[J] := '0';
end;
else
Tmp[J] := S[I];
end;
Inc(J);
end;
Result := string.Create(Tmp, 0, J);
end;
initialization
RedirectProcedure(@TPerlRegEx.EscapeRegExChars, @EscapeRegExChars);
end.
将此单元添加到您的项目中,对TPerlRegEx.EscapeRegExChars
的调用将重新开始工作。
{$APPTYPE CONSOLE}
uses
System.RegularExpressionsCore,
FixTPerlRegExEscapeRegExChars in 'FixTPerlRegExEscapeRegExChars.pas';
begin
Writeln(TPerlRegEx.EscapeRegExChars('test'));
Readln;
end.
<强>输出强>
test