1.0 WSTĘP
Moduł uConvention służy do dynamicznego wywoływania funkcji/procedur, z wnętrza programu lub bibliotek DLL.
Moduł ten jest z założenia pomocą w języku skryptowym do wywoływania procedur funkcji, których deklarację poznajemy dopiero w trakcie działania programu.
2.0 UŻYWANIE
Całość działa w oparciu o jeden moduł uConvenction.pas.
UWAGA!!! Za pomocą uConvention można wywoływać tylko i wyłącznie funkcje o
konwencji stdcall.
2.1 Przykład pobrania adresu procedury z DLL
Dodaj do sekcji uses uConvention:
1 2 3 |
uses uConvention; |
Przed wywołaniem procedury z DLL musisz ją wczytać wraz z całą biblioteką. Moduł jest odporny na wielokrotne wczytywanie tej samej funkcji, aczkolwiek zaleca się ze względów na wydajność jednorazowe wczytywanie procedury. Robi się to za pomocą funkcji:
1 |
function GetProc(ALibrary, AProcName: string): pointer; |
Pierwszy parametr ALibrary to nazwa DLL, z którego chcemy adres funkcji. W drugim parametrze AProcName, należy wpisać nazwę żądanej procedury/funkcji. Funkcja zwraca doń wskaźnik, który należy zapamiętać w zmiennej. Przykładowo:
1 2 3 4 5 6 7 |
var MyFuncPtr: pointer; begin MyFuncPtr := GetProc('user32.dll', 'MessageBoxA'); |
Skoro mamy już adres funkcji to możemy przystąpić do jej wywoływania. Zakładam oczywiście, że znasz deklarację funkcji, którą wywołujesz (np. została zadeklarowana w skrypcie i znasz jej parametry). Deklaracja funkcji, której wskaźnik pobraliśmy, przez GetProc() wygląda w Delphi tak:
1 2 3 |
function MessageBoxA(hWnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall; |
2.2.0 Procedury „dzwoniące” na podstawie adresu
A my dzięki uConvention możemy ją wywoływać bez wcześniejszego deklarowania w Delphi dzięki procedurze stdcall, która występuje w 2 wersjach. Jedna wersja dla procedur, natomiast 2 dla funkcji:
1 2 3 |
procedure stdcall(AProcAddr: pointer; APar: array of TPar); overload; procedure stdcall(AProcAddr: pointer; APar: array of TPar; AResult: TPar); overload; |
Pierwszy parametr to adres procedury, którą chcemy wywołać, drugi parametr to
parametry dla procedury, natomiast trzeci parametr (tylko dla funkcji) to wynik funkcji.
2.2.1 Przekazywanie parametrów dla procedur
Pierwszy parametr nie stanowi problemu, wpisujemy w niego przykładowo MyFuncProc, zmienną której wartość zainicjowaliśmy wcześniej. Nieco gorzej jest zaś z drugim parametrem. Jest to otwarta tablica którą trzeb specjalnie uzupełnić. Przykładowo:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// możemy wywoływać funkcję jak procedurę bez większych konsekwencji // jedynym minusem jest to, że nie poznamy jej wyniku ;) stdcall(MyFuncPtr, [Par(0, dtInteger), Par('Treść komunikatu', dtPCHAR), Par('Cześć!', dtPCHAR), Par(Variant(MB_OK or MB_IconInformation), dtInteger)]); |
Aby wygenerować parametry musimy użyć funkcji Par. Występuje ona w 2 wersjach
(powyżej używamy drugiej wersji):
1 2 3 |
function Par(ADataPtr: pointer; ADataType: TDataType): TPar; overload; function Par(AVal: variant; ADataType: TDataType): TPar; overload; |
Pierwsza wersja służy do przekazywania wskaźnika zmiennej. Przydatne, gdy chcemy wyciągnąć wynik funkcji lub ominąć drugą funkcję Par. Powiedzmy, że mamy jakąś gotową zmienną i chcemy ją przekazać jako parametr:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
var h_Wnd: HWND; Text: PCHAR; Caption: PCHAR; _Type: Cardinal; begin h_Wnd := 0; Text := 'Treść komunikatu!'; Caption := 'Cześć!'; _Type := MB_OK or MB_IconInformation; stdcall(MyFuncPtr, [Par(@h_Wnd, dtInteger), Par(@Text, dtPCHAR), Par(@Caption, dtPCHAR), Par(@_Type, dtInteger)]); |
Zaprezentowana wyżej technika jest szybsza, bo nie ma potrzeby konwersji typu Variant na zadany typ. Poeksperymentuj z typami :). Sprawdź, co się stanie, gdy użyjesz przykładowo zamiast:
1 |
Par(@Text, dtPCHAR) |
Wpiszesz:
1 |
Par(Text, dtPCHAR) |
Drugim parametrem (ADataType) jest typ zmiennej. Zawsze musimy podawać zgodny typ z typem danego parametru.
2.2.2 Typy danych – TDataType
Dostępne Typy to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
dtVar - parametr var <- UWAGA!!! Gdy parametr jest parametrem var podajemy jako typ dtVar dt8 - 8 bitowa zmienna dt16 - 16 bitowa zmienna dt32 - 32 bitowa zmienna (np. PCHAR) dtString - napis AnsiString dt64 - 64 bitowa zmienna dt80 - 80 bitowa zmienna dt32f - 32 bitowa zmienna (działa tak jak dt32) dt64f - 64 bitowa zmienna (działa tak jak dt64) dt80f - 80 bitowa zmienna (działa tak jak dt80) |
Nazwy te mogą się wydawać zbyt skomplikowane, dlatego też wprowadziłem nazwy
alternatywne podobne do nazw typów zmiennych w Delphi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
{ 32 bitowe dane } dtLongInt = dt32 dtInteger = dt32 dtLongWord = dt32 dtCardinal = dt32 dtClass = dt32 dtArray = dt32 dtDynamicArray = dt32 dtStaticArray = dt32 dtRecord = dt32 dtVariant = dt32 dtPCHAR = dt32 dtInterface = dt32 //dtString = dt32 dtWideString = dt32 dtPointer = dt32 dtProcedure = dt32 dtFunction = dt32 { 64 bitowe dane } dtInt64 = dt64 dtProcedureOfObject = dt64 dtFunctionOfObject = dt64 { 80 bitowe dane } dtTByte = dt80 { zmiennoprzecinkowe dane } dtSingle = dt32f dtDouble = dt64f dtExtended = dt80f |
2.2.3 Przekazywanie parametrów dla funkcji
Wygląda analogicznie jak przekazywanie parametrów dla procedur, z drobną różnicą oczywiście ;). Aby w jakiś sposób zainkasować wynik funkcji musimy użyć:
1 |
function Par(ADataPtr: pointer; ADataType: TDataType): TPar; overload; |
Oraz:
1 2 3 |
procedure stdcall(AProcAddr: pointer; APar: array of TPar; AResult: TPar); overload; |
Aby urozmaicić kod wywołamy funkcję zadeklarowaną w naszym programie, wywołamy ją i pobierzemy wynik:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
procedure Test; {$APPTYPE CONSOLE} uses uConvention; // dodając na końcu dyrektywę stdcall zmieniamy // konwencję wywoływania funkcji na stdcall function Add(A, B: single): single; stdcall; begin Result := A + B; end; var Result: single; begin { wywołanie dowolnej funkcji z konwencją wywołania stdcall } stdcall(@Add, // parametry [Par(9.5, dtSingle), Par(25.5, dtSingle)], // wynik (!!!koniecznie @ i poprawny typ!!!) Par(@Result, dtSingle)); WriteLn(Result: 2: 4, #13#10'Wcisnij [ENTER]'); ReadLn; end. |
2.2.4 Przekazywanie parametru var
Gdy w deklaracji procedury wystąpi zapis z var:
1 |
procedure ProcWithVar(var A: extended); stdcall; |
Procedurę taką należy wywoływać w następujący sposób:
1 2 3 4 5 6 7 8 9 |
var VarPar: Extended; begin // dla parametrów var typ ustawiamy jako dtVar stdcall(@ProcWithVar, [Par(@VarPar, dtVar)]); |
2.3 Podsumowanie
To wszystko, co należy wiedzieć o uConvention. Może kiedyś napiszę do tego modułu procedury „dzwoniące” do procedur/funkcji w konwencji pascal, register i cdecl.
Wiedz drogi czytelniku, że stdcall jest najważniejszą konwencją używaną w systemie
windows :P.
3.0 PODZIĘKOWANIA
www.remobjects.com?ps. – Pascal Script. Dzięki temu językowi skryptowemu powstał moduł uConvention.