经过我在网上的大量搜索,答案是汇编器在调用InternalSetDispatcher时假定存在堆栈帧。
似乎在调用InternalSetDispatcher时没有生成堆栈帧。
因此,修复方法很简单,只需使用{$stackframes on}编译器指令打开堆栈帧并重新构建即可。
感谢Mason帮助我找到这个答案。 :)
编辑2012-08-08:如果您想使用此代码,您可能需要查看
Delphi Sping Framework中的实现。虽然我没有测试过,但它看起来比这段代码更好地处理了不同的调用约定。
编辑:根据要求,以下是我对Alan代码的解释。除了需要打开堆栈帧之外,还需要在项目级别打开优化才能使其正常工作:
unit MulticastEvent;
interface
uses
Classes, SysUtils, Generics.Collections, ObjAuto, TypInfo;
type
{$stackframes on}
{$ifopt O-}
{$message Fatal 'optimisation _must_ be turned on for this unit to work!'}
{$endif}
TMulticastEvent = class
strict protected
type TEvent = procedure of object;
strict private
FHandlers: TList<TMethod>;
FInternalDispatcher: TMethod;
procedure InternalInvoke(Params: PParameters; StackSize: Integer);
procedure SetDispatcher(var AMethod: TMethod; ATypeData: PTypeData);
procedure Add(const AMethod: TEvent); overload;
procedure Remove(const AMethod: TEvent); overload;
function IndexOf(const AMethod: TEvent): Integer; overload;
protected
procedure InternalAdd;
procedure InternalRemove;
procedure InternalIndexOf;
procedure InternalSetDispatcher;
public
constructor Create;
destructor Destroy; override;
end;
TMulticastEvent<T> = class(TMulticastEvent)
strict private
FInvoke: T;
procedure SetEventDispatcher(var ADispatcher: T; ATypeData: PTypeData);
public
constructor Create;
procedure Add(const AMethod: T); overload;
procedure Remove(const AMethod: T); overload;
function IndexOf(const AMethod: T): Integer; overload;
property Invoke: T read FInvoke;
end;
implementation
procedure TMulticastEvent.Add(const AMethod: TEvent);
begin
FHandlers.Add(TMethod(AMethod))
end;
constructor TMulticastEvent.Create;
begin
inherited;
FHandlers := TList<TMethod>.Create;
end;
destructor TMulticastEvent.Destroy;
begin
ReleaseMethodPointer(FInternalDispatcher);
FreeAndNil(FHandlers);
inherited;
end;
function TMulticastEvent.IndexOf(const AMethod: TEvent): Integer;
begin
result := FHandlers.IndexOf(TMethod(AMethod));
end;
procedure TMulticastEvent.InternalAdd;
asm
XCHG EAX,[ESP]
POP EAX
POP EBP
JMP Add
end;
procedure TMulticastEvent.InternalIndexOf;
asm
XCHG EAX,[ESP]
POP EAX
POP EBP
JMP IndexOf
end;
procedure TMulticastEvent.InternalInvoke(Params: PParameters; StackSize: Integer);
var
LMethod: TMethod;
begin
for LMethod in FHandlers do
begin
if StackSize > 0 then
asm
MOV ECX,StackSize
SUB ESP,ECX
MOV EDX,ESP
MOV EAX,Params
LEA EAX,[EAX].TParameters.Stack[8]
CALL System.Move
end;
asm
MOV EAX,Params
MOV EDX,[EAX].TParameters.Registers.DWORD[0]
MOV ECX,[EAX].TParameters.Registers.DWORD[4]
MOV EAX,LMethod.Data
CALL LMethod.Code
end;
end;
end;
procedure TMulticastEvent.InternalRemove;
asm
XCHG EAX,[ESP]
POP EAX
POP EBP
JMP Remove
end;
procedure TMulticastEvent.InternalSetDispatcher;
asm
XCHG EAX,[ESP]
POP EAX
POP EBP
JMP SetDispatcher;
end;
procedure TMulticastEvent.Remove(const AMethod: TEvent);
begin
FHandlers.Remove(TMethod(AMethod));
end;
procedure TMulticastEvent.SetDispatcher(var AMethod: TMethod;
ATypeData: PTypeData);
begin
if Assigned(FInternalDispatcher.Code) and Assigned(FInternalDispatcher.Data) then
ReleaseMethodPointer(FInternalDispatcher);
FInternalDispatcher := CreateMethodPointer(InternalInvoke, ATypeData);
AMethod := FInternalDispatcher;
end;
procedure TMulticastEvent<T>.Add(const AMethod: T);
begin
InternalAdd;
end;
constructor TMulticastEvent<T>.Create;
var
MethInfo: PTypeInfo;
TypeData: PTypeData;
begin
MethInfo := TypeInfo(T);
TypeData := GetTypeData(MethInfo);
inherited Create;
Assert(MethInfo.Kind = tkMethod, 'T must be a method pointer type');
SetEventDispatcher(FInvoke, TypeData);
end;
function TMulticastEvent<T>.IndexOf(const AMethod: T): Integer;
begin
InternalIndexOf;
end;
procedure TMulticastEvent<T>.Remove(const AMethod: T);
begin
InternalRemove;
end;
procedure TMulticastEvent<T>.SetEventDispatcher(var ADispatcher: T;
ATypeData: PTypeData);
begin
InternalSetDispatcher;
end;
end.