Biblioteka DLL zawiera kod obiektowy dla funkcji, które są częścią biblioteki, wraz z dodatkowymi informacjami umożliwiającymi korzystanie z biblioteki DLL.
Jednak biblioteka DLL nie zawiera faktycznych informacji o typie potrzebnych do określenia listy argumentów i typów dla funkcji zawartych w bibliotece DLL. Główne informacje w bibliotece DLL to: (1) lista funkcji eksportowanych przez bibliotekę DLL wraz z informacjami adresowymi, które połączą wywołanie funkcji z rzeczywistym kodem binarnym funkcji oraz (2) listą wymaganych bibliotek DLL używane funkcje w bibliotece DLL.
Możesz otworzyć bibliotekę DLL w edytorze tekstu, sugeruję mały i skanować tajemne symbole kodu binarnego, aż dojdziesz do sekcji zawierającej listę funkcji biblioteki DLL, jak również inne wymagane biblioteki DLL.
Biblioteka DLL zawiera minimalną ilość informacji potrzebnych do (1) znalezienia określonej funkcji w bibliotece DLL, aby można było ją wywołać, oraz (2) listy innych potrzebnych bibliotek DLL, których funkcje w bibliotece DLL zależą na.
Różni się to od obiektu COM, który zwykle posiada informacje o typie, w celu umożliwienia wykonania tego, co stanowi w zasadzie odbicie i eksplorację usług obiektu COM oraz sposobu dostępu do tych usług. Możesz to zrobić za pomocą Visual Studio i innych IDE, które generują listę zainstalowanych obiektów COM i pozwalają załadować obiekt COM i eksplorować go. Visual Studio ma również narzędzie, które wygeneruje pliki kodu źródłowego, które dostarczają kody pośredniczące i zawierają plik umożliwiający dostęp do usług i metod obiektu COM.
Jednak biblioteka DLL różni się od obiektu COM, a wszystkie dodatkowe informacje dostarczone z obiektem COM nie są dostępne z biblioteki DLL. Zamiast tego pakiet bibliotek DLL składa się zazwyczaj z (1) samej biblioteki DLL, (2) pliku .lib, który zawiera informacje o połączeniu biblioteki DLL wraz z kodami i funkcjami, aby zaspokoić linker podczas budowania aplikacji, która używa biblioteka DLL i (3) plik włączający z funkcjami prototypów funkcji biblioteki DLL biblioteki.
Tworzysz swoją aplikację, wywołując funkcje znajdujące się w bibliotece DLL, ale używając informacji o typie z pliku dołączanego i łącząc się z identyfikatorami powiązanego pliku .lib. Ta procedura umożliwia programowi Visual Studio automatyzację dużej części pracy wymaganej do korzystania z biblioteki DLL.
Albo możesz ręcznie kodować LoadLibrary()
i budować tabelę funkcji w bibliotece DLL, używając GetProcAddress()
. Robiąc kodowanie ręczne, wszystko, czego naprawdę potrzebujesz, to prototypy funkcji funkcji biblioteki DLL, które możesz następnie wpisać samodzielnie i bibliotekę DLL. W efekcie wykonujesz ręcznie pracę, którą kompilator Visual Studio zrobi dla ciebie, jeśli używasz wtyczki biblioteki .lib i dołączasz plik.
Jeśli znasz rzeczywista nazwa funkcji i prototyp funkcji funkcji w DLL biblioteki, to co można zrobić, to mieć swoje narzędzia wiersza polecenia wymagają następujące informacje:
- nazwę funkcji być nazywany jako ciąg tekstowy w komendzie linii
- lista argumentów do wykorzystania jako seria ciągów tekstowych w linii poleceń
- dodatkowy parametr, który opisuje prototyp funkcji
Jest to podobne do działania w środowisku wykonawczym C i C++, które akceptuje listy zmiennych zmiennych o nieznanych typach parametrów. Na przykład funkcja printf()
, która drukuje listę wartości argumentów, ma ciąg formatujący, po którym następują argumenty do wydrukowania. Funkcja printf()
używa ciągu formatów do określenia typów różnych argumentów, liczby argumentów do spodziewania się i rodzajów transformacji wartości do wykonania.
Więc jeśli narzędzie miało wiersza poleceń coś jak następuje:
dofunc "%s,%d,%s" func1 "name of " 3 " things"
A DLL biblioteka miała funkcję, której prototyp wyglądał:
void func1 (char *s1, int i, int j);
następnie narzędzie będzie dynamicznie wygenerować wywołanie funkcji przez przekształcenie ciągów znaków linii poleceń w rzeczywiste typy potrzebne do wywołania funkcji.
Będzie to działać w przypadku prostych funkcji, które pobierają zwykłe stare typy danych, jednak bardziej skomplikowane typy, takie jak argument typu struct
wymagałyby więcej pracy, ponieważ potrzebowałbyś pewnego rodzaju opisu struct
wraz z opisem argumentów być może podobnym do JSON.
Dodatek I: Prosty przykład
Poniżej znajduje się kod źródłowy aplikacji programu Visual Studio konsoli Windows, który wpadłem w debuggera. Argumenty polecenia we właściwościach to pif.dll PifLogAbort
, które spowodowały załadowanie biblioteki DLL z innego projektu, pif.dll, a następnie wywołanie funkcji PifLogAbort()
w tej bibliotece.
Zobacz, jak lista argumentów do funkcji PifLogAbort()
została zbudowana jako struktura zawierająca tablicę. Wartości argumentów są umieszczane w tablicy zmiennej struct
, a następnie funkcja nazywa się przekazywanie całej wartości struct
. To, co robi, to przesłać kopię tablicy parametrów na stos, a następnie wywołuje funkcję. Funkcja PifLogAbort()
widzi stos na podstawie jego listy argumentów i przetwarza elementy tablicy jako indywidualne argumenty lub parametry.
// dllfunctest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
typedef struct {
UCHAR *myList[4];
} sarglist;
typedef void ((*libfunc) (sarglist q));
/*
* do a load library to a DLL and then execute a function in it.
*
* dll name.dll "funcname"
*/
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE dll = LoadLibrary(argv[1]);
if (dll == NULL) return 1;
// convert the command line argument for the function name, argv[2] from
// a TCHAR to a standard CHAR string which is what GetProcAddress() requires.
char funcname[256] = {0};
for (int i = 0; i < 255 && argv[2][i]; i++) {
funcname[i] = argv[2][i];
}
libfunc generic_function = (libfunc) GetProcAddress(dll, funcname);
if (generic_function == NULL) return 2;
// build the argument list for the function and then call the function.
// function prototype for PifLogAbort() function exported from the library DLL
// is as follows:
// VOID PIFENTRY PifLogAbort(UCHAR *lpCondition, UCHAR *lpFilename, UCHAR *lpFunctionname, ULONG ulLineNo);
sarglist xx = {{(UCHAR *)"xx1", (UCHAR *)"xx2", (UCHAR *)"xx3", (UCHAR *)1245}};
generic_function(xx);
return 0;
}
Ten prosty przykład ilustruje niektóre techniczne przeszkody, które należy przezwyciężyć. Będziesz musiał wiedzieć, jak przetłumaczyć różne typy parametrów na właściwe wyrównanie w obszarze pamięci, które następnie jest wypychane na stos.
Interfejs do tej przykładowej funkcji jest wyjątkowo jednolity, ponieważ większość argumentów to wskaźniki, z wyjątkiem ostatniego, który jest int
. W 32-bitowym pliku wykonywalnym wszystkie cztery typy zmiennych mają tę samą długość w bajtach. Z bardziej zróżnicowaną listą typów na liście argumentów będziesz musiał zrozumieć, w jaki sposób twój kompilator wyrównuje parametry, gdy przesyła argumenty na stos przed wykonaniem połączenia.
Dodatek II: Rozszerzenie prosty przykład
Inną możliwością jest posiadanie zestawu funkcji pomocniczych wraz z inną wersją struct
. struct
udostępnia obszar pamięci do utworzenia kopii stosu, a funkcje pomocy są używane do budowania kopii.
Tak więc struct
i jego funkcje pomocnicze mogą wyglądać następująco.
typedef struct {
UCHAR myList[128];
} sarglist2;
typedef struct {
int i;
sarglist2 arglist;
} sarglistlist;
typedef void ((*libfunc2) (sarglist2 q));
void pushInt (sarglistlist *p, int iVal)
{
*(int *)(p->arglist.myList + p->i) = iVal;
p->i += sizeof(int);
}
void pushChar (sarglistlist *p, unsigned char cVal)
{
*(unsigned char *)(p->arglist.myList + p->i) = cVal;
p->i += sizeof(unsigned char);
}
void pushVoidPtr (sarglistlist *p, void * pVal)
{
*(void * *)(p->arglist.myList + p->i) = pVal;
p->i += sizeof(void *);
}
A potem funkcje struct
i pomocnicze będą wykorzystane do budowy listę argumentów jak następuje po której funkcja z biblioteki DLL jest wywoływana z kopią stosie przewidzianym:
sarglistlist xx2 = {0};
pushVoidPtr (&xx2, "xx1");
pushVoidPtr (&xx2, "xx2");
pushVoidPtr (&xx2, "xx3");
pushInt (&xx2, 12345);
libfunc2 generic_function2 = (libfunc2) GetProcAddress(dll, funcname);
generic_function2(xx2.arglist);
Calling konwencja może w końcu dać ci żal. Faktyczne oczekiwania co do funkcji, jak je nazywasz, są oczywistym założeniem. I "... z przekazanymi argumentami.", Zauważając * mnogość * tego stwierdzenia, jest interesująca. Mijasz * jeden *. Jeśli potrzebujesz czegoś więcej (tj. Spodziewasz się, że to w magiczny sposób wyślesz "argv [3] ... argv [argc-1]" jako argumenty funkcji), to nie zrobi tego, a robienie tego dobrze staje się skomplikowane szybko . – WhozCraig
Nie jest jasne, co masz na myśli, gdybym mógł przekazać listę-pustych-wskaźników do wskaźnika-funkcji. Jeśli funkcja, do której dzwonisz, jest zdefiniowana jako __ ...MyFunction (void *) __, a następnie tak, możesz to tak nazwać, inaczej nie będziesz w stanie. Upewnij się także, że jest oznaczony jako 'declspec (stdcall)'. –
Po prostu szukałem sposobu na wywołanie narzędzia wiersza polecenia, np. kernel32.dll i dowolna z jego funkcji i przekazywać do niego argumenty. Miałem nadzieję - jak powiedział WhozCraig - na magiczny sposób, aby znaleźć listę argumentów z zaimportowanej funkcji – h4x0r