2016-12-02 6 views
0

Ich möchte ein sehr, sehr kleines Programm schreiben, das die Startargumente analysiert und eine von mehreren DLLs zum "Booten" auswählt.Wie kann ich eine DLL wie eine ausführbare Datei zur Laufzeit "starten"?

Ich habe bereits eine Anwendung geschrieben, die ich als DLL ausführen möchte, indem ich sie als Anwendung schreibe und dann die Visual Studio-Projekteigenschaften ändere, um sie stattdessen als DLL zu erstellen. Ich weiß, dass ich LoadLibrary und GetProcAddress zusammen benutzen muss, um die Funktionalität zu erhalten, die ich will, aber ich habe Probleme, eine klare und umfassende Dokumentation zu diesem Thema zu finden, da viele der Anwendungsfälle nicht wirklich dieser Art entsprechen. Außerdem muss ich diese Route basierend auf den Projekt- und Plattformbeschränkungen gehen.

Ich fand this page, die einige Informationen hat, aber es ist nicht klar genug für mich, um für meine Zwecke anzupassen.

Edit: Hier ist, wo ich gerade bin.

Ich habe eine DLL-Projekt, dessen Hauptfunktion Signatur etwas wie folgt aussieht:

__declspec(dllexport) int cdecl main(int argc, char *argv[]) 

ich auch ein Anwendungsprojekt haben, dessen Versuch, die DLL zu laden und die obige Funktion sieht wie folgt ausgeführt werden:

typedef int (CALLBACK* LPFNDLLFUNC1)(int, char *);

...

 HMODULE dllHandle = NULL; 
     BOOL freeResult, runTimeLinkSuccess = FALSE; 
     LPFNDLLFUNC1 lpfnDllFunc1; // Function pointer 
     if (args->IsEmpty()) 
     { 
      dllHandle = LoadLibrary(L"TrueApplication.dll"); 
      if (NULL != dllHandle) 
      { 
       lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(dllHandle, "main"); 
       if (lpfnDllFunc1) 
       { 
        int retVal = lpfnDllFunc1(0, "1"); 
       } 

Derzeit funktioniert der LoadLibrary-Aufruf, aber GetProcAddress nicht.

+2

Was nimmst du als den Vorteil wahr, es zu einer DLL anstelle einer EXE zu machen? Wir müssen Ihre Argumentation kennen, so dass wir keine Lösung empfehlen, die einige der von Ihnen gewünschten DLL-Vorteile "rückgängig macht". –

+0

Die Verwendung dieser Methode ist weder meine Wahl noch meine Idee, aber es ist das, was ich angesichts der Einschränkungen der Plattform und des Projekts tun muss. Wenn ich mehrere ausführbare Dateien anstelle von DLLs verwende, muss ich im Grunde das gleiche tun, als hätte die Plattform für jede ausführbare Datei einen anderen Anwendungseintrag. Beispielsweise müssten Benutzer die App "Launcher" sowie "App 1", "App 2" usw. installieren. – Roderick

Antwort

1

Zunächst ist das Ändern des Projekttyps von der ausführbaren in die DLL nicht genug, um eine DLL zu erstellen. Sie müssen auch einige Symbole exportieren, um Ihre API zu erstellen. Zumindest müssen Sie die Funktionen, die Sie exportieren, mit __declspec(dllexport) dekorieren. Ich empfehle jedoch, dass Sie C-API exportieren, dh extern "C" Funktionen mit C-kompatiblen Argumente. Daher sollten die exportierten Funktionen mit extern "C" __declspec(dllexport) vorangestellt werden., Aber es hat einen Vorteil

const char* dllname = "myfile.dll"; 
    h = LoadLibrary(dllname); 
    if(h == nullptr) 
    { 
     /*handle error*/ 
    } 

    using myfunc_type = bool (*)(int x, double y); //example 
    auto myfunc = reinterpret_cast<myfunc_type>(GetProcAddress(h, "myfunc"));  
    //...... 
    myfunc(x,y); //call the imported function 

Diese Lösung mehr Arbeit als statische Belastung mit /delayload gezeigt von Jerry Coffin nimmt: wenn eine DLL

Sobald Sie das getan haben, Sie dynamisch Ihre DLL wie folgt laden Wird benötigt, aber nicht gefunden, können Sie Benutzern eine eigene Fehlermeldung geben, anstatt sich auf die von Windows kommende Nachricht zu verlassen (was für Nichttechniker oft inakzeptabel ist). Sie können auch die Überprüfung der API-Version mit einer eigenen benutzerdefinierten Fehlermeldung in der API einschließen.

Edit: der Beispielcode funktioniert, wenn Sie es wie diese

ändern
extern "C" __declspec(dllexport) int main(int argc, char *argv[]){...} 
typedef int (* LPFNDLLFUNC1)(int, char **); 
+0

Verzögerung lädt auch den Fehler abfangen und eine benutzerdefinierte Nachricht anzeigen. –

+0

@BenVoigt Könntest du mich bitte auf einen Leitfaden hinweisen? Ist es auch möglich, benutzerdefinierte Fehlermeldungen für Versionskonflikte zu erstellen (wenn sich die API der DLL im Laufe der Zeit ändert)? – Eugene

+0

Was ich also tun möchte, würde für die Hauptfunktion der DLL so aussehen, oder? 'mit myfunc_type = int (*) (int x, doppelte y); // Beispiel auto myfunc = reinterpret_cast (GetProcAddress (h, "main")); ' – Roderick

2

Sie nicht haben LoadLibrary und GetProcAddress verwenden, um die Funktionalität in der DLL aufzurufen.

Häufig würden Sie Ihre DLLs erstellen, jede mit einem eigenen Einstiegspunkt. Nehmen wir an, Sie möchten die Befehlszeile analysieren, eine DLL auswählen und ihren Einstiegspunkt ohne Argumente aufrufen. Am Ende haben Sie so etwas wie dies oben:

void DLL_a(); 
void DLL_b(); 
void DLL_c(); 

int main(int argc, char **argv) { 
    // we'll assume DLL_a is the default: 
    if (argc < 2) 
     DLL_a(); 

    // For now, we'll do a *really* trivial version of parsing the command line 
    // to choose the right DLL: 
    if (argv[1][0] == 'A') 
     DLL_a(); 
    else if (argv[1]][0] == 'B') 
     DLL_b(); 
    else if (argv[1][0] == 'C') 
     DLL_c(); 
    else { 
     std::cerr << "Unrecognized argument\n"; 
     return 1; 
    } 
} 

Wenn Sie Ihr Haupt verknüpfen, können Sie die .lib zu jeder DLL entsprechenden angeben werden, und Sie werden wahrscheinlich wollen die /delayload Flagge an den Linker spezifizieren. Dies bedeutet, dass eine DLL erst geladen wird, wenn eine Funktion in der DLL tatsächlich aufgerufen wird. Wenn Sie beispielsweise eine Version mit reduzierter Funktionalität Ihres Programms verteilen möchten, die nur DLL A enthält, kann sie weiterhin ausgeführt werden (ohne DLL B oder C auf dem System des Benutzers), solange keine Funktion von DLL B vorhanden ist oder C wird immer genannt. Wenn Sie /delayload nicht angeben, versucht der Loader beim Starten des Programms alle DLLs dem RAM zuzuordnen. Führen Sie ihre DllMain aus, um sie für die Verwendung zu initialisieren, rekursiv für alle DLLs, von denen sie abhängen usw.

/delayload hat einen weiteren Vorteil: Es vermeidet die Zuordnung der anderen DLLs zu Adressen überhaupt, wenn sie nie verwendet werden. Es klingt so, als würde jeder Aufruf nur eine DLL verwenden, also ist das in Ihrem Fall wahrscheinlich ein Gewinn.

+0

Nicht nur die Speicherzuordnung, sondern auch DllMain (und Konstruktoren für globale Objekte) werden nicht für die nicht ausgewählte DLL ausgeführt. –

1

Sie GetProcAddress (...) nicht Notwendigkeit tun, dies zu tun, auch wenn dieser Ansatz (Option 2) ist einfacher, wenn Sie verstehen, wie die Compiler generiert Symbolnamen.


Option # 1

DllMain startet einen Hauptthread

niemals etwas innerhalb von DllMain kompliziert tun, können Sie Ihre Software Deadlock.

DLLs ihren eigenen Einstiegspunkt (und Austrittspunkt und Gewinde befestigen-Punkt ... es ist eine wirklich beschäftigt Funktion). Wenn Sie einfach LoadLibrary (...) auf Ihrer DLL aufrufen, wird mindestens ein Anruf an DllMain (...) für den Prozess gesendet.

BOOL 
APIENTRY 
DllMain (HMODULE hModule, 
      DWORD ul_reason_for_call, 
      LPVOID lpReserved) 

Sie können tatsächlich ul_reason_for_call == DLL_PROCESS_ATTACH als Auftrag behandeln DllMain auszuführen, als ob es Ihr Programm Hauptfunktion waren.

Nun, auf keinen Fall sollten Sie tatsächlich eine Programmschleife hier starten ... jederzeit DllMain läuft es hält eine sehr wichtige Betriebssystemsperre (DLL Loader) und Sie müssen diese durch die Rückkehr für den normalen Programmbetrieb freigeben.

Das bedeutet, wenn Sie DllMain als Ihr Programm Einstiegspunkt verwenden möchten, muss es einen Thread und Ihre ursprüngliche main Methode zum Laichen darf nicht zurückkehren, bis dieser Thread beendet ...


Option # 2

DLL Exportiert eine main Funktion.

Be sehr achtsam Konvention zu nennen, wird der Compiler die Symbole für Sie umbenennen und Lokalisierungsfunktionen in einer DLL machen mit GetProcAddress weniger als intuitiv.

In der DLL, Exportmain:

__declspec (dllexport) 
int 
__cdecl main (int argc, char *argv []) 
{ 
    printf ("foobar"); 
    return 0; 
} 

In Ihrem Programm Importmain aus der DLL:

// Need a typedef for the function you are going to get from the DLL 
typedef int (__cdecl *main_pfn)(int argc, char *argv[]); 

int main (int argc, char *argv[]) 
{ 
    HMODULE hModMyDLL = LoadLibraryA ("MyDll.dll"); 

    if (hModMyDLL != 0) { 
    // 
    // The preceding underscore deals with automatic decorations 
    // the compiler added to the __cdecl function name. 
    // 
    // It is possible to do away with this completely if you use a .def 
    // file to assign export names and ordinals manually, but then you 
    //  lose the ability to tell a function's calling convention by its 
    //  name alone. 
    // 
    main_pfn MyMain = (main_pfn) 
     GetProcAddress (hModMyDLL, "_main"); 

    // Call the main function in your DLL and return when it does 
    if (MyMain != nullptr) 
     return MyMain (argc, argv); 
    } 

    return -1; 
} 

Beide Ansätze haben ihre Vorzüge.

von DllMain einen Thread Laichen vermeidet alles zu wissen, überhaupt darüber, wie die DLL zu ladende implementiert werden, aber es erfordert, dass Sie auch nie wieder Ihre main Funktion zu entwerfen - die DLL ExitProcess (...) nennen.

Exportieren von Funktionen und später importieren sie nach Namen können Sie Pussyfooting um den Windows DLL Loaderlock vermeiden. Wenn Sie jedoch keine .def Datei verwenden, um explizit die exportierten Symbole zu nennen, der Compiler wird Dekorationen hinzuzufügen wie _... (__cdecl) oder [email protected] (__stdcall) den Namen und Sie müssen diese Konventionen lernen mach irgendetwas Nützliches mit GetProcAddress.

+0

Ich sollte darauf hinweisen, dass der Grund, warum Sie 'GetProcAddress (...)' auf Win32-API-Funktionen mit ihrem undekorierten Namen verwenden können (trotz der Definition von 'WINAPI' auf ** __ Stdcall **), weil Microsoft ein '.def' verwendet um DLLs wie 'user32' ihre Exportnamen zu geben. Sie entfernen absichtlich die Deklaration der rufenden Konvention, so dass 'GetProcAddress' weniger belastend ist. Du kannst das auch tun. –

Verwandte Themen