MX4*_*399 5 delphi generics delphi-xe
在使用提议的多播委托方法响应Delphi问题中的信号和槽实现时,代码无法添加多个事件处理程序.
问题与向事件列表中添加方法有关TDelegateImpl<T>.Add(),该TList<T>.IndexOf方法使用Compare方法查找现有方法,结果始终为0 - 意味着对于TMethod,Left和Right是相同的.Equals方法使用TMethod类型转换并显式比较TMethod.Code和TMethod.Data,其中Compare强制转换为始终相同的地址.
为什么Compare使用TList<T>.IndexOf而不是Equals?
我可以重现这一点,这显然是方法的默认比较器中的一个错误.
我已经提交了QC#98942.
这是我的代码:
program TMethodComparer;
{$APPTYPE CONSOLE}
uses
SysUtils, Generics.Collections;
type
TMyMethod = procedure of object;
type
TMyClass = class
published
procedure P1;
procedure P2;
procedure P3;
end;
{ TMyClass }
procedure TMyClass.P1;
begin
end;
procedure TMyClass.P2;
begin
end;
procedure TMyClass.P3;
begin
end;
var
List: TList<TMyMethod>;
MyObject1, MyObject2: TMyClass;
begin
MyObject1 := TMyClass.Create;
MyObject2 := TMyClass.Create;
List := TList<TMyMethod>.Create;
List.Add(MyObject1.P1);
List.Add(MyObject1.P2);
List.Add(MyObject2.P1);
List.Add(MyObject2.P2);
Writeln(List.IndexOf(MyObject1.P1));
Writeln(List.IndexOf(MyObject1.P2));
Writeln(List.IndexOf(MyObject2.P1));
Writeln(List.IndexOf(MyObject2.P2));
Writeln(List.IndexOf(MyObject1.P3));
end.
Run Code Online (Sandbox Code Playgroud)
产量
0
0
0
0
0
Run Code Online (Sandbox Code Playgroud)
预期产出
0
1
2
3
-1
Run Code Online (Sandbox Code Playgroud)
默认比较器的Generics.Defaults实现方式如下:
type
TMethodPointer = procedure of object;
function Compare_Method(Inst: PSimpleInstance; const Left, Right: TMethodPointer): Integer;
begin
if PInt64(@Left)^ < PInt64(@Right)^ then
Result := -1
else if PInt64(@Left)^ > PInt64(@Right)^ then
Result := 1
else
Result := 0;
end;
Run Code Online (Sandbox Code Playgroud)
我可以理解这是在尝试做什么,但它失败了.我仍然无法弄清楚这些演员是如何演出的.
我相信Compare_Method应该以这种方式编写32位版本:
function Compare_Method(Inst: PSimpleInstance; const Left, Right: TMethod): Integer;
begin
if Int64(Left) < Int64(Right) then
Result := -1
else if Int64(Left) > Int64(Right) then
Result := 1
else
Result := 0;
end;
Run Code Online (Sandbox Code Playgroud)
这确实导致了预期的产出.
显然,对于64位目标(即在XE2中),没有基于具有64位整数的别名的方法将起作用.
因此,为了解决该错误,您可以添加以下功能:
function Compare_Method(const Left, Right: TMethod): Integer;
var
LCode, LData: PByte;
RCode, RData: PByte;
begin
LCode := PByte(Left.Code);
LData := PByte(Left.Data);
RCode := PByte(Right.Code);
RData := PByte(Right.Data);
if LData<RData then
Result := -1
else if LData>RData then
Result := 1
else if LCode<RCode then
Result := -1
else if LCode>RCode then
Result := 1
else
Result := 0;
end;
function CompareMyMethod(const Left, Right: TMyMethod): Integer;
begin
Result := Compare_Method(TMethod(Left), TMethod(Right))
end;
Run Code Online (Sandbox Code Playgroud)
然后像这样创建列表:
List := TList<TMyMethod>.Create(
TComparer<TMyMethod>.Construct(CompareMyMethod)
);
Run Code Online (Sandbox Code Playgroud)
问题是这个功能:
function Compare_Method(Inst: PSimpleInstance; const Left, Right: TMethodPointer): Integer;
begin
if PInt64(@Left)^ < PInt64(@Right)^ then
Result := -1
else if PInt64(@Left)^ > PInt64(@Right)^ then
Result := 1
else
Result := 0;
end;
Run Code Online (Sandbox Code Playgroud)
这将方法与Int64进行比较.这不起作用,因为@可能在这里没有效果.
CPU视图确认了这一点:
System.Generics.Defaults.pas.1089: begin
00447690 55 push ebp
00447691 8BEC mov ebp,esp
System.Generics.Defaults.pas.1090: if PInt64(@Left)^ < PInt64(@Right)^ then
00447693 8B4510 mov eax,[ebp+$10]
00447696 8B5004 mov edx,[eax+$04]
00447699 8B00 mov eax,[eax]
0044769B 8B4D08 mov ecx,[ebp+$08]
0044769E 3B5104 cmp edx,[ecx+$04]
004476A1 7506 jnz $004476a9
004476A3 3B01 cmp eax,[ecx]
004476A5 7309 jnb $004476b0
004476A7 EB02 jmp $004476ab
004476A9 7D05 jnl $004476b0
System.Generics.Defaults.pas.1091: Result := -1
004476AB 83C8FF or eax,-$01
004476AE EB21 jmp $004476d1
System.Generics.Defaults.pas.1092: else if PInt64(@Left)^ > PInt64(@Right)^ then
004476B0 8B4510 mov eax,[ebp+$10]
etc...
Run Code Online (Sandbox Code Playgroud)
要将两个TM方法作为Int64进行比较,这应该是:
System.Generics.Defaults.pas.1090: if PInt64(@Left)^ < PInt64(@Right)^ then
00447693 8B4510 lea eax,[ebp+$10] // not MOV
00447696 8B5004 mov edx,[eax+$04]
00447699 8B00 mov eax,[eax]
0044769B 8B4D08 lea ecx,[ebp+$08] // not MOV
0044769E 3B5104 cmp edx,[ecx+$04]
004476A1 7506 jnz $004476a9
004476A3 3B01 cmp eax,[ecx]
etc...
Run Code Online (Sandbox Code Playgroud)
这清楚地表明这PInt64(@Left)^被解释为PInt64(Left)^.
对于Delphi 32和Delphi 64,正确的实现应该或多或少看起来像这样:
function Compare_Method(Inst: PSimpleInstance; const Left, Right: TMethodPointer): Integer;
var
LCode, LData: PByte;
RCode, RData: PByte;
begin
LCode := PByte(TMethod(Left).Code);
LData := PByte(TMethod(Left).Data);
RCode := PByte(TMethod(Right).Code);
RData := PByte(TMethod(Right).Data);
if LData < RData then
Result := -1
else if LData > RData then
Result := 1
else if LCode < RCode then
Result := -1
else if LCode > RCode then
Result := 1
else
Result := 0;
end;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1389 次 |
| 最近记录: |