2015-03-11 11 views
6

Problem polega na tym, że chcę utworzyć ogólną aplikację wiersza poleceń, która może być używana do ładowania biblioteki DLL, a następnie wywoływania funkcji w biblioteka DLL. Nazwa funkcji jest określona w wierszu poleceń za pomocą argumentów podanych również w wierszu poleceń narzędzia.C Przekaż argumenty jako lista pustych-pointerów do zaimportowanej funkcji z LoadLibrary()

Mam dostęp do funkcji zewnętrznej z biblioteki DLL ładowanej dynamicznie przy użyciu funkcji LoadLibrary(). Po załadowaniu biblioteki mogę uzyskać wskaźnik do funkcji za pomocą GetProcAddress() Chcę wywołać funkcję z argumentami podanymi w wierszu poleceń.

Czy mogę przekazać listę pustych wierszy do wskaźnika funkcji, który został zwrócony przez funkcję LoadLibrary() podobną do poniższego przykładu?

Aby zachować prosty przykładowy kod, usunąłem sprawdzanie błędów. Czy istnieje sposób, aby otrzymać coś takiego pracy:

 
    //Somewhere in another dll 
    int DoStuff(int a, int b) 
    { 
     return a + b; 
    } 
 
    int main(int argc, char **argv) 
    { 
     void *retval; 
     void *list = argv[3]; 
     HMODULE dll; 
     void* (*generic_function)(void*); 

     dll = LoadLibraryA(argv[1]); 

     //argv[2] = "DoStuff" 
     generic_function = GetProcAddress(dll, argv[2]); 

     //argv[3] = 4, argv[4] = 7, argv[5] = NULL 
     retval = generic_function(list); 
    } 

Jeśli zapomniałem wspomnieć niezbędnych informacji, proszę dać mi znać. Z góry z góry

+0

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

+0

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)'. –

+0

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

Odpowiedz

4

Musisz rzucić wskaźnik funkcji zwrócony przez LoadLibrary na jeden z właściwymi typami argumentów przed wywołaniem go. Jednym ze sposobów zarządzania nim jest, aby mieć wiele funkcji call-adaptera że zrobić dobry uczynek dla każdego możliwego rodzaju funkcji może chcesz zadzwonić:

void Call_II(void (*fn_)(), char **args) { 
    void (*fn)(int, int) = (void (*)(int, int))fn_; 
    fn(atoi(args[0]), atoi(args[1])); 
} 
void Call_IS(void (*fn_)(), char **args) { 
    void (*fn)(int, char *) = (void (*)(int, char *))fn_; 
    fn(atoi(args[0]), args[1]); 
} 
...various more functions 

Następnie wziąć wskaźnika uzyskanego z GetProcAddress i dodatkowych argumentów i przekazać je do właściwej Call_X funkcji:

void* (*generic_function)(); 

dll = LoadLibraryA(argv[1]); 

//argv[2] = "DoStuff" 
generic_function = GetProcAddress(dll, argv[2]); 

//argv[3] = 4, argv[4] = 7, argv[5] = NULL 

Call_II(generic_function, &argv[3]); 

problem polega na tym, że trzeba wiedzieć, co rodzaj funkcji dostajesz za to wskaźnik i wywołać odpowiednią funkcję adaptera. Co zwykle oznacza utworzenie tabeli nazw/adapterów funkcji i wykonanie w niej odnośnika.

Związany z tym problem polega na tym, że nie ma funkcji analogicznej do GetProcAddress, która powie ci typy argumentów dla funkcji w bibliotece - ta informacja po prostu nie jest przechowywana w dowolnym miejscu dostępnym w bibliotece dll.

1

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); 
+0

Bardzo interesujące podejście! Nie myślałem o łączeniu procedury wywoływania/podawania listy-argumentów. Zdecydowanie zajrzę do tego dalej. Dziękuję za wyrafinowane i szczegółowe wyjaśnienie/odpowiedź. Dobra robota! – h4x0r