失敗的大牛事件委托,與我的委托

来源:http://www.cnblogs.com/IDELPHI/archive/2016/11/12/DELPHI.html
-Advertisement-
Play Games

看了網上大牛的DELPHI事件委托,實際用起來是有BUG的。代碼如下: unit faDelegate; interface uses Generics.collections, TypInfo, ObjAuto, SysUtils;type Event = class private FMetho ...


看了網上大牛的DELPHI事件委托,實際用起來是有BUG的。代碼如下:

unit faDelegate;

interface

uses
Generics.collections, TypInfo, ObjAuto, SysUtils;
type
Event = class
private
FMethods : TList<TMethod>;
FInternalDispatcher: TMethod;
//悲催的是泛型類的方法不能內嵌彙編,只能通過一個非泛型的父類來實現
procedure InternalInvoke(Params: PParameters; StackSize: Integer);
public
constructor Create;
destructor Destroy; override;
end;

Event<T> = class(Event)
private
FObj:TObject;
FProName:string;

FEntry : T;
function ConvertToMethod(var Value):TMethod;
procedure SetEntry(var AEntry);
public
constructor Create(Obj:TObject;ProName:String );
destructor Destroy; override;
procedure Add(AMethod : T);
procedure Remove(AMethod : T);
function IndexOf(AMethod: T): Integer;

// property Invok : T read FEntry;
end;

implementation

{ Event<T> }

procedure Event<T>.Add(AMethod: T);
var
m : TMethod;
begin
m := ConvertToMethod(AMethod);
if ((m.Code<>nil) and (FMethods.IndexOf(m) < 0)) then
FMethods.Add(m);
end;

function Event<T>.ConvertToMethod(var Value): TMethod;
begin
Result := TMethod(Value);
end;

constructor Event<T>.Create(Obj:TObject;ProName:String );
var
MethInfo: PTypeInfo;
TypeData: PTypeData;
m:TMethod;
p:Pointer;
begin
MethInfo := TypeInfo(T);
if MethInfo^.Kind <> tkMethod then //檢測T的類型
raise Exception.Create('T only is Method(Member function)!');

TypeData := GetTypeData(MethInfo);

Inherited Create();
FInternalDispatcher := CreateMethodPointer(InternalInvoke, TypeData); //把InternalInvoke的函數地址轉為TMethod
SetEntry(FEntry); //FEntry是入口地址,設為FInternalDispatcher

FObj:=Obj;
FProName:=ProName;

m:=GetMethodProp(FObj,FProName);
p:=@m;
Add(T(p^)); //先添加對象原有的方法
SetMethodProp(FObj,FProName,FInternalDispatcher); //設定對象的入口
end;

destructor Event<T>.Destroy;
begin
ReleaseMethodPointer(FInternalDispatcher); //和CreateMethodPointer是一對的,正好相反

inherited Destroy;
end;

function Event<T>.IndexOf(AMethod: T): Integer;
begin
Result := FMethods.IndexOf(ConvertToMethod(AMethod));
end;

procedure Event<T>.Remove(AMethod: T);
begin
FMethods.Remove(ConvertToMethod(AMethod));
end;

procedure Event<T>.SetEntry(var AEntry);
begin
TMethod(AEntry) := FInternalDispatcher;
end;

{ Event }

constructor Event.Create;
begin
FMethods := TList<TMethod>.Create;
end;

destructor Event.Destroy;
begin
FMethods.Free;
inherited Destroy;
end;

procedure Event.InternalInvoke(Params: PParameters; StackSize: Integer);
var
LMethod: TMethod;
begin
for LMethod in FMethods do
begin
//如果用到了棧(也就是Register約定參數大於2或者stdcall,cdecl約定)就把棧內所有數據都拷貝參數棧裡面
if StackSize > 0 then
asm
MOV ECX,StackSize //Move的第三個參數,同時為下一步Sub ESP做準備
SUB ESP,ECX //把棧頂 - StackSize(棧是負向的)
MOV EDX,ESP //Move的第二個參數
MOV EAX,Params
LEA EAX,[EAX].TParameters.Stack[8] //Move的第一個參數
CALL System.Move
end;
//Register協議填寫三個寄存器,EAX肯定是Self,如果是其他協議寄存器被填寫也沒啥影響
asm
MOV EAX,Params //把Params讀到EAX
MOV EDX,[EAX].TParameters.Registers.DWORD[0] //EDX
MOV ECX,[EAX].TParameters.Registers.DWORD[4] //EAX

MOV EAX,LMethod.Data//把Method.Data給到EAX,如果是Register約定就是Self.否則也沒影響
CALL LMethod.Code//調用Method.Data
end;
end;
end;

 

 

BUG體驗在對TDBGridEh中的列的事件OnupdateData做委托時,對Value參數賦值會有錯誤!暈,不知道怎麼辦好!所以只好用自己的方法解決!

我的事件委托:

Delegate<T>=class
private
i:integer;
FEntrance:TMethod;
protected
Delegates:array of TMethod;
procedure AddMethod(m:TMethod);
function GetRunEof():Boolean;
function GetRun():T;
public
constructor Create(C: TObject;ProName:string);virtual;
destructor Destroy; override;
procedure Add(Delegate:T);

end;

DeNotify=class(Delegate<TNotifyEvent>)
published
procedure DoRun(Sender:TObject);
end;

 

implementation

 


procedure Delegate<T>.Add(Delegate: T);
var m:TMethod;
p:Pointer;
begin
p:=@Delegate;
m:=Tmethod(p^);
AddMethod(Tmethod(p^));
end;

procedure Delegate<T>.AddMethod(m: TMethod);
begin
if ((m.Code=nil) or (m.Data=nil)) then exit;
if (m.Code<>FEntrance.Code) then begin
SetLength(Delegates,High(Delegates)+2);
Delegates[High(Delegates)]:=m;
end;
end;

constructor Delegate<T>.Create(C: TObject; ProName: string);
begin
FEntrance.Data:=Self;
FEntrance.Code:=MethodAddress('DoRun');

AddMethod(GetMethodProp(c,ProName));
SetMethodProp(c,ProName,FEntrance);
i:=0;

// if Assigned(lstDelegates)=false then begin
// lstDelegates:=TList.Create;
lstDelegates.Add(Self);
// end;
end;


destructor Delegate<T>.Destroy;
begin
Dec(iTotal);
// if lstDelegates.Count=0 then
// lstDelegates.Free
// else
lstDelegates.Delete(lstDelegates.IndexOf(self));

inherited;
end;

 

function Delegate<T>.GetRun: T;
var m:TMethod;
p:Pointer;
begin
m:=Delegates[i-1];
p:=@m;
Result:=T(p^);
end;

function Delegate<T>.GetRunEof: Boolean;
begin
Result:=not (i<=High(delegates));
if Result=false then
Inc(i)
else
i:=0;
end;


procedure DeNotify.DoRun(Sender: TObject);
begin
while not GetRunEof() do
GetRun()(Sender);
end;

這個方法有很大的缺點,就是一種事件類型要派生一個類!但實在,沒有什麼問題。

看來事物都有兩面性,濃縮很大的代碼,做起來很有技巧,很高難度,而且會比較容易出錯。

如果濃縮不大的代碼,所需要的技巧不多,容易理解,但是冗餘又比較多。不爽。

不過,無論如何,正確是第一的。技巧再高,不正確也沒有用。第一種方法好象很強大,但有BUG了,都不知道如何改,因為太高級了。。。。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...