2013-06-04 64 views
9

我正在嘗試動態調用駐留在函數表中的過程或函數的功能。特定的應用程序是一個DLL,它將一個指向函數表的指針與關於參數和類型數量的信息一起導出。宿主應用程序然後有能力詢問DLL並調用函數。如果它們是對象方法,我可以使用Rtti來調用它們,但它們是正常的過程和函數。該DLL擁有自營出口正常的函數指針不是對象,因爲DLL可以寫成包括C,德爾福等任何語言如何動態調用Delphi中的命名過程或函數

例如,我有一個創紀錄的聲明,並在DLL中填寫:

TAPI = record 
     add : function (var a, b : double) : double; 
     mult : function (var a, b : double) : double; 
end; 
PAPI = ^TAPI; 

我檢索指向此記錄,聲明如下:

apiPtr : PAPI; 

假設我也有機會獲得本程序的名字,論點和論據類型記錄中的每個條目數量。

假設我想調用add函數。函數指針增加將是:

@apiPtr^.add // I assume this will give me a pointer to the add function 

我假設有沒有其他的方法只需要使用一些ASM推棧所需的參數並檢索結果?

第一個問題,聲明該過程的最佳調用約定是什麼,cdecl?看起來最容易在通話之前組裝堆棧。

第二個問題,是否有任何實際的在線實例呢?我碰到http://www.swissdelphicenter.ch/torry/showcode.php?id=1745(DynamicDllCall),這是接近我想要什麼,但我簡化爲以下,現在返回一個指針(EAX)的結果:

function DynamicDllCall(proc : pointer; const Parameters: array of Pointer): pointer; 
var x, n: Integer; 
    p: Pointer; 
begin 
n := High(Parameters); 
if n > -1 then begin 
    x := n; 
    repeat 
    p := Parameters[x]; 
    asm 
     PUSH p 
    end; 
    Dec(x); 
    until x = -1; 
end; 
asm 
    CALL proc 
    MOV p, EAX <- must be changed to "FST result" if return value is double 
end; 
result := p; 

末;

但我不能得到它的工作,它返回的第一個參數,而不是結果的值。也許我的調用約定錯了,或者我誤解了如何在EAX中檢索結果。

我打電話DynamicDllCall如下:

var proc : pointer; 
    parameters: array of Pointer; 
    x, y, z : double; 
    p : pointer; 
begin 
    x:= 2.3; y := 6.7; 
    SetLength(parameters, 2); 
    parameters[0] := @x; parameters[1] := @y; 
    proc := @apiPtr^.add; 
    p := DynamicDllCall(proc, Parameters); 
    z := double (p^); 

任何建議感激地接受。我明白,有些人可能會覺得這不是我們應該這樣做的方式,但如果至少有可能,我仍然很好奇。

更新1我可以確認add函數獲得正確的值來進行添加。

更新2如果我改變增加的簽名:

add : function (var a, b, c : double) : double; 

,我把結果賦給C內添加,然後我可以檢索參數數組中的正確答案(假設我想補充一個更多元素,3而不是2)。因此,問題是我誤解了函數返回值的方式。任何人都可以解釋函數如何返回值以及如何最好地檢索它們?

更新3我有我的答案。我應該猜到了。 Delphi通過不同的寄存器返回不同類型。例如整數通過EAX返回,另一方面通過ST(0)返回。要將ST(0)複製到結果變量,我必須使用「FST結果」而不是「MOV p,EAX」。至少我現在知道原則上可以這樣做。無論是明智的做法,還是我現在必須思考的另一個問題。

+0

在德爾福ASM網站www.delphi3000.com(articles/article_3766.asp)中曾經有過不錯的介紹,但是整個網站現在都不存在了... –

+1

也許關於[Assembly Procedures and Functions](http ://docwiki.embarcadero.com/RADStudio/XE4/en/Assembly_Procedures_and_Functions)會有所幫助。請參閱「功能結果」主題。 –

+0

這裏沒有太多的東西,官方文檔也不是很糟糕。然而,我拼湊在一起足夠的信息: http://stackoverflow.com/questions/15786404/fld-instruction-x64-bit 和 http://www.guidogybels.eu/docs/Using%20Assembler%20in% 20Delphi.pdf – rhody

回答

1

這是解決一個困難的問題。在運行時動態訪問DLL中的方法的一種方法是使用外部函數接口庫,如libffidyncallDynaCall()。但是這些都沒有被移植到Delphi環境中。

如果應用程序將DLL中的一組方法與DLL提供的Rtti信息一起接口並將它們暴露給Python等腳本語言,則可以選擇一種方法來編寫檢查DLL並寫出的Delphi代碼一個ctypes兼容的腳本,可以在運行時加載到嵌入式Python解釋器中。只要預先定義DLL方法可以處理的有限但足夠的類型集合,這是一個實際的解決方案。

9

這是一個XY的問題:你想幹什麼X,而且,不管出於什麼原因,你決定Ÿ是解決方案,但你有麻煩Ÿ工作。在你的情況下,X通過指針調用外部功能Y手動推送堆棧上的參數。但要完成X,你並不真正需要做ÿ

表達@apiPtr^.add不會給你一個函數指針。它會給你一個指向TAPI記錄的add字段的指針。 (由於add是該記錄的第一個成員,該字段的地址將等於apiPtr中保存的地址;代碼爲Assert(CompareMem(@apiPtr, @apiPtr^.add, SizeOf(Pointer))。)add字段保存了指向該函數的指針,所以如果這就是你想要的,只需使用apiPtr^.add(並注意在Delphi中^是可選的)。

最好的調用約定使用的是stdcall。任何支持導出DLL函數的語言都將支持該調用約定。

你不需要組裝或任何其他棘手的堆棧操作調用你的函數。你已經知道函數的類型,因爲你用它來聲明add。調用函數由現場指出,簡單地使用相同的語法調用普通函數:

z := apiPtr.add(x, y); 

編譯器知道聲明的類型的add場的,所以它會安排棧爲您服務。

+0

你是正確的,我可以使用apiPtr.add(x,y),這就是我一直在使用的。但這意味着我需要編譯時添加函數的細節。我想根據運行時信息調用add。我的dll可以導出其他方法來爲每個函數提供rtti信息。這些信息應該允許我在運行時即時構建一個調用。爲什麼?我的特殊應用是允許這些方法在運行時自動提供給腳本引擎。腳本引擎不應該關心dll中可用的功能,只需將它們傳遞給用戶即可。 – rhody

相關問題