2013-06-04 13 views
9

Eksperymentuję z możliwością dynamicznego wywoływania procedur lub funkcji znajdujących się w tabeli funkcji. Konkretną aplikacją jest biblioteka DLL, która eksportuje wskaźnik do tabeli funkcji wraz z informacją o liczbie argumentów i typów. Aplikacja hosta ma następnie możliwość przesłuchania biblioteki DLL i wywołania funkcji. Gdyby były to metody obiektowe, mógłbym użyć Rtti do ich wywołania, ale są to normalne procedury i funkcje. DLL musi eksportować normalnych wskaźników funkcji obiektów, ponieważ nie DLL można zapisać w dowolnym języku, w tym C, Delphi itpJak dynamicznie wywoływać nazwaną procedurę lub funkcję w Delphi

Na przykład, mam rekord zadeklarowane i wypełnione w DLL:

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

odzyskać wskaźnik do tego rekordu, zadeklarowane jako:

apiPtr : PAPI; 

Przyjmijmy również mieć dostęp do nazw procedur, liczba argumentów i typów argumentów dla każdego wpisu w rejestrze.

Załóżmy, że chcę wywołać funkcję dodawania. Wskaźnik funkcji, aby dodać będą:

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

Zakładam, nie ma innej drogi inne niż korzystać z niektórych asm naciskać niezbędnych argumentów na stosie i pobrać wynik?

Pierwsze pytanie, jaka jest najlepsza konwencja wywołująca, aby zadeklarować procedurę jako cdecl? Najłatwiej jest złożyć stos przed połączeniem.

Drugie pytanie, czy istnieją przykłady online, które faktycznie to robią? Natknąłem http://www.swissdelphicenter.ch/torry/showcode.php?id=1745 (DynamicDllCall), który jest zbliżony do tego, co chcę, ale uproszczone, jak poniżej, to teraz zwraca wskaźnik (EAX) w wyniku:

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; 

końcowego;

ale nie mogę go uruchomić, zwraca wartość dla pierwszych parametrów zamiast wyniku. Może mam błędną konwencję wywoływania lub może nie rozumiem, jak odzyskać wynik w EAX.

Wzywam DynamicDllCall następująco:

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^); 

Wszelkie porady wdzięcznością odbierane. Rozumiem, że niektórzy mogą czuć, że nie jest to sposób, w jaki należy to robić, ale nadal jestem ciekawy, czy jest to co najmniej możliwe.

Aktualizacja 1 Mogę potwierdzić, że funkcja dodawania otrzymuje prawidłowe wartości do dodania.

Aktualizacja 2 Jeśli zmienię podpis dodać do:

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

i przypisać wynik do c wewnątrz dodać, to mogę odzyskać prawidłową odpowiedź w tablicy parametrów (zakładając, że dodasz więcej elementu, 3 zamiast 2). Problem polega na tym, że źle rozumiem sposób zwracania wartości z funkcji. Czy ktoś może wyjaśnić, w jaki sposób funkcje zwracają wartości i jak je odzyskać?

Aktualizacja 3 Mam swoją odpowiedź. Powinienem się domyślić. Delphi zwraca różne typy za pośrednictwem różnych rejestrów. np. całkowite zwrócenie przez EAX, podwójne z drugiej strony zwraca przez ST (0). Aby skopiować ST (0) do zmiennej wyniku, muszę użyć "FST result" zamiast "MOV p, EAX". Teraz przynajmniej wiem, że w zasadzie można to zrobić. Niezależnie od tego, czy jest to sensowne, to inna sprawa, o której muszę teraz myśleć.

+0

To było miłe wprowadzenie do Delphi ASM na stronie www.delphi3000.com (articles/article_3766.asp), ale ta strona już nie istnieje ... –

+1

Być może dokumentacja w [Procedurach montażu i funkcjach] (http : //docwiki.embarcadero.com/RADStudio/XE4/en/Assembly_Procedures_and_Functions) pomogłoby. Zobacz temat "Wyniki funkcji". –

+0

Nie ma zbyt wiele i oficjalni doktorzy nie są strasznie przydatni.Jednak zebrałem wystarczającą ilość informacji od: http://stackoverflow.com/questions/15786404/fld-instruction-x64-bit i http://www.guidogybels.eu/docs/Using%20Assembler%20in% 20Delphi.pdf – rhody

Odpowiedz

1

Jest to trudny problem do rozwiązania. Jednym ze sposobów dynamicznego dostępu do metod w bibliotece DLL w środowisku wykonawczym byłoby użycie biblioteki interfejsu funkcji obcych, takiej jak libffi, dyncall lub DynaCall(). Jednak żadne z nich nie zostało jeszcze przeniesione do środowiska Delphi.

Jeśli aplikacja ma interfejsować zestaw metod w bibliotece DLL wraz z informacjami Rtti dostarczonymi przez bibliotekę DLL i narazić je na język skryptowy, taki jak Python, jedną z opcji jest napisanie kodu Delphi, który sprawdza bibliotekę DLL i wypisuje Skrypt kompatybilny z ctypes, który można załadować do wbudowanego interpretera języka Python w środowisku wykonawczym. Tak długo, jak definiujemy przed ręką ograniczony, ale wystarczający zestaw typów obsługiwanych przez metody DLL, jest to praktyczne rozwiązanie.

9

Jest to problem, XY: Chcesz zrobić X, i, z jakiegoś powodu zdecydowałeś Y jest rozwiązaniem, ale masz problemy z dokonywania Y pracy. W twoim przypadku, X jest wywoływanie funkcji zewnętrznych za pomocą wskaźników i Y jest ręcznie popchnij parametry na stosie. Jednak aby wykonać X, naprawdę nie musisz wykonywać Y.

Wyrażenie @apiPtr^.add nie daje wskaźnika do funkcji. Da ci to wskaźnik do pola add rekordu . (Ponieważ add jest pierwszym elementem rekordu, adres tego pola będzie równy adresowi przechowywanemu w apiPtr, w kodzie Assert(CompareMem(@apiPtr, @apiPtr^.add, SizeOf(Pointer)).) add pole posiada wskaźnik do funkcji, więc jeśli tego chcesz , po prostu użyj apiPtr^.add (i pamiętaj, że ^ jest opcjonalny w Delphi).

Najlepszą konwencją wywoływania jest stdcall. Każdy język obsługujący eksportujące funkcje DLL będzie obsługiwał tę konwencję wywoływania.

Nie potrzebujesz asemblera ani żadnej innej manipulowanej sztucznej stosu do wywoływania twoich funkcji. Znasz już typ funkcji, ponieważ użyłeś jej do zadeklarowania add. Aby wywołać funkcję wskazał przez tej dziedzinie, wystarczy użyć tej samej składni jak wywołanie zwykłej funkcji:

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

Kompilator wie deklarowany typ pola add, więc będzie zorganizować stos dla Ciebie.

+0

Masz rację, że mogę użyć apiPtr.add (x, y) i to właśnie używałem. Ale to oznacza, że ​​potrzebuję szczegółów funkcji dodawania w czasie kompilacji. Chcę wywołać add na podstawie informacji o środowisku wykonawczym. Moje biblioteki DLL mogą eksportować dodatkowe metody w celu dostarczenia informacji rtti o każdej funkcji. Te informacje powinny umożliwić mi nawiązanie połączenia w locie w czasie wykonywania. Czemu? Moją szczególną aplikacją jest umożliwienie automatycznego udostępnienia takich metod silnikowi skryptów w środowisku wykonawczym. Mechanizm skryptowy nie powinien przejmować się, jakie funkcje są dostępne w bibliotece dll, wystarczy przekazać je użytkownikowi. – rhody

Powiązane problemy