Exportieren von Funktionen aus einer DLL mit dllexport

105

Ich möchte ein einfaches Beispiel für den Export einer Funktion aus einer C ++ - Windows-DLL.

Ich möchte den Header, die .cppDatei und die .defDatei sehen (falls unbedingt erforderlich).

Ich möchte, dass der exportierte Name nicht dekoriert wird . Ich möchte die gängigste Anrufkonvention ( __stdcall?) Verwenden . Ich möchte die Verwendung __declspec(dllexport)und muss keine .defDatei verwenden.

Beispielsweise:

  //header
  extern "C"
  {
   __declspec(dllexport) int __stdcall foo(long bar);
  }

  //cpp
  int __stdcall foo(long bar)
  {
    return 0;
  }

Ich versuche zu vermeiden, dass der Linker dem Namen Unterstriche und / oder Zahlen (Anzahl der Bytes?) Hinzufügt.

Ich bin damit einverstanden, dass ich nicht denselben Header unterstütze dllimportund dllexportverwende. Ich möchte keine Informationen zum Exportieren von C ++ - Klassenmethoden, sondern nur globale Funktionen im C-Stil.

AKTUALISIEREN

extern "C"Wenn ich die Aufrufkonvention (und die Verwendung ) nicht einbeziehe, bekomme ich die Exportnamen, wie ich möchte, aber was bedeutet das? Bekomme ich unabhängig von der Standardaufrufkonvention was Pinvoke (.NET), deklariere (vb6) und GetProcAddresswürde es erwarten? (Ich denke, GetProcAddresses würde vom Funktionszeiger abhängen, den der Aufrufer erstellt hat).

Ich möchte, dass diese DLL ohne Header-Datei verwendet wird, daher brauche ich nicht viel Fantasie #defines, um den Header für einen Anrufer nutzbar zu machen.

Ich bin damit einverstanden, dass ich eine *.defDatei verwenden muss.

Erdferkel
quelle
Ich erinnere mich vielleicht falsch, aber ich denke, dass: a) extern Cdie Dekoration entfernt wird, die die Parametertypen der Funktion beschreibt, aber nicht die Dekoration, die die Aufrufkonvention der Funktion beschreibt; b) Um alle Dekorationen zu entfernen , müssen Sie den (nicht dekorierten) Namen in einer DEF-Datei angeben.
ChrisW
Das habe ich auch gesehen. Vielleicht sollten Sie dies als vollwertige Antwort hinzufügen?
Aardvark

Antworten:

134

Wenn Sie einfache C-Exporte wünschen, verwenden Sie ein C-Projekt, nicht C ++. C ++ - DLLs basieren auf der Namensverwaltung für alle C ++ - Ismen (Namespaces usw.). Sie können Ihren Code als C kompilieren, indem Sie in Ihre Projekteinstellungen unter C / C ++ -> Erweitert gehen. Es gibt eine Option "Kompilieren als", die den Compiler-Schaltern / TP und / TC entspricht.

Wenn Sie weiterhin C ++ zum Schreiben der Interna Ihrer Bibliothek verwenden möchten, aber einige Funktionen exportieren möchten, die für die Verwendung außerhalb von C ++ nicht verwickelt sind, lesen Sie den zweiten Abschnitt unten.

Exportieren / Importieren von DLL-Bibliotheken in VC ++

Was Sie wirklich tun möchten, ist ein bedingtes Makro in einem Header zu definieren, das in allen Quelldateien Ihres DLL-Projekts enthalten ist:

#ifdef LIBRARY_EXPORTS
#    define LIBRARY_API __declspec(dllexport)
#else
#    define LIBRARY_API __declspec(dllimport)
#endif

Dann verwenden Sie für eine Funktion, die Sie exportieren möchten, Folgendes LIBRARY_API:

LIBRARY_API int GetCoolInteger();

Erstellen Sie in Ihrem Bibliothekserstellungsprojekt eine Definition. Dadurch LIBRARY_EXPORTSwerden Ihre Funktionen für Ihren DLL-Build exportiert.

Da LIBRARY_EXPORTSin einem Projekt, das die DLL verwendet, nicht definiert wird, werden stattdessen alle Funktionen importiert, wenn dieses Projekt die Header-Datei Ihrer Bibliothek enthält.

Wenn Ihre Bibliothek plattformübergreifend sein soll, können Sie LIBRARY_API als nichts definieren, wenn Sie nicht unter Windows arbeiten:

#ifdef _WIN32
#    ifdef LIBRARY_EXPORTS
#        define LIBRARY_API __declspec(dllexport)
#    else
#        define LIBRARY_API __declspec(dllimport)
#    endif
#elif
#    define LIBRARY_API
#endif

Wenn Sie dllexport / dllimport verwenden, müssen Sie keine DEF-Dateien verwenden. Wenn Sie DEF-Dateien verwenden, müssen Sie dllexport / dllimport nicht verwenden. Die beiden Methoden erfüllen dieselbe Aufgabe auf unterschiedliche Weise. Ich glaube, dass dllexport / dllimport die empfohlene Methode unter den beiden ist.

Exportieren nicht entwirrter Funktionen aus einer C ++ - DLL für LoadLibrary / PInvoke

Wenn Sie dies benötigen, um LoadLibrary und GetProcAddress zu verwenden oder möglicherweise aus einer anderen Sprache zu importieren (z. B. PInvoke aus .NET oder FFI in Python / R usw.), können Sie extern "C"inline mit Ihrem dllexport den C ++ - Compiler anweisen, die Namen nicht zu entstellen. Und da wir GetProcAddress anstelle von dllimport verwenden, müssen wir den ifdef-Tanz nicht von oben ausführen, sondern nur einen einfachen dllexport:

Der Code:

#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)

EXTERN_DLL_EXPORT int getEngineVersion() {
  return 1;
}

EXTERN_DLL_EXPORT void registerPlugin(Kernel &K) {
  K.getGraphicsServer().addGraphicsDriver(
    auto_ptr<GraphicsServer::GraphicsDriver>(new OpenGLGraphicsDriver())
  );
}

Und so sehen die Exporte mit Dumpbin / Exporten aus:

  Dump of file opengl_plugin.dll

  File Type: DLL

  Section contains the following exports for opengl_plugin.dll

    00000000 characteristics
    49866068 time date stamp Sun Feb 01 19:54:32 2009
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 0001110E getEngineVersion = @ILT+265(_getEngineVersion)
          2    1 00011028 registerPlugin = @ILT+35(_registerPlugin)

Dieser Code funktioniert also einwandfrei:

m_hDLL = ::LoadLibrary(T"opengl_plugin.dll");

m_pfnGetEngineVersion = reinterpret_cast<fnGetEngineVersion *>(
  ::GetProcAddress(m_hDLL, "getEngineVersion")
);
m_pfnRegisterPlugin = reinterpret_cast<fnRegisterPlugin *>(
  ::GetProcAddress(m_hDLL, "registerPlugin")
);
Joshperry
quelle
1
extern "C" schien den Namen des Mangels im C ++ - Stil zu entfernen. Die ganze Sache mit Import und Export (die ich vorschlagen wollte, nicht in die Frage aufzunehmen) ist nicht wirklich das, worüber ich frage (aber es sind gute Informationen). Ich dachte, es würde das Problem trüben.
Aardvark
Der einzige Grund, warum ich denken kann, dass Sie das brauchen würden, ist für LoadLibrary und GetProcAddress ... Dies ist bereits erledigt, ich werde in meinem Antworttext
erläutern
Ist EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport)? Ist das im SDK?
Aardvark
3
Vergessen Sie nicht, die Moduldefinitionsdatei in die Linker-Einstellungen des Projekts aufzunehmen - nur "Hinzufügen eines vorhandenen Elements zum Projekt" reicht nicht aus!
Jimmy
1
Ich habe dies verwendet, um eine DLL mit VS zu kompilieren und sie dann mit .C von R aus aufzurufen. Toll!
Juancentro
33

Für C ++:

Ich konfrontiere gerade das gleiche Problem und ich denke , es ist erwähnenswert , ein Problem aufkommt , wenn man beide verwenden __stdcall(oder WINAPI) und extern "C" :

Wie Sie wissen, extern "C"wird die Dekoration entfernt, sodass anstelle von:

__declspec(dllexport) int Test(void)                        --> dumpbin : ?Test@@YaHXZ

Sie erhalten einen nicht dekorierten Symbolnamen:

extern "C" __declspec(dllexport) int Test(void)             --> dumpbin : Test

Das _stdcall(= Makro WINAPI, das die aufrufende Konvention ändert) verziert jedoch auch Namen, sodass wir erhalten, wenn wir beide verwenden:

   extern "C" __declspec(dllexport) int WINAPI Test(void)   --> dumpbin : _Test@0

und der Nutzen von extern "C"geht verloren, weil das Symbol verziert ist (mit _ @bytes)

Beachten Sie, dass dies nur für die x86-Architektur auftritt, da die __stdcallKonvention auf x64 ignoriert wird ( msdn : Auf x64-Architekturen werden nach Konvention Argumente nach Möglichkeit in Registern übergeben und nachfolgende Argumente auf dem Stapel übergeben .).

Dies ist besonders schwierig, wenn Sie sowohl auf x86- als auch auf x64-Plattformen abzielen.


Zwei Lösungen

  1. Verwenden Sie eine Definitionsdatei. Dies zwingt Sie jedoch dazu, den Status der def-Datei beizubehalten.

  2. Der einfachste Weg: Definieren Sie das Makro (siehe msdn ):

#define EXPORT comment (Linker, "/ EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)

und fügen Sie dann das folgende Pragma in den Funktionskörper ein:

#pragma EXPORT

Vollständiges Beispiel:

 int WINAPI Test(void)
{
    #pragma EXPORT
    return 1;
}

Dadurch wird die Funktion für x86- und x64-Ziele nicht dekoriert exportiert, wobei die __stdcallKonvention für x86 beibehalten wird. Das __declspec(dllexport) ist in diesem Fall nicht erforderlich.

Malick
quelle
5
Vielen Dank für diesen wichtigen Hinweis. Ich habe mich schon gefragt, warum sich meine 64-Bit-DLL von der 32-Bit-DLL unterscheidet. Ich finde Ihre Antwort viel nützlicher als die als Antwort akzeptierte.
Elmue
1
Ich mag diesen Ansatz wirklich. Meine einzige Empfehlung wäre, das Makro in EXPORT_FUNCTION umzubenennen, da das __FUNCTION__Makro nur in Funktionen funktioniert.
Luis
3

Ich hatte genau das gleiche Problem. Meine Lösung bestand darin, die Moduldefinitionsdatei (.def) zu verwenden, anstatt __declspec(dllexport)Exporte zu definieren ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ). Ich habe keine Ahnung, warum das funktioniert, aber es funktioniert

Rytis I.
quelle
Hinweis an alle andere in diese ausgeführt wird : unter Verwendung eines .defModuls export tut Arbeit, aber auf Kosten der Lage zu liefern externDefinitionen in der Header - Datei für zB globale Daten in diesem Fall müssen Sie die Versorgung externDefinition manuell in internen Anwendungen von diese Daten. (Ja, es gibt Zeiten, in denen Sie dies benötigen.) Es ist sowohl allgemein als auch speziell für plattformübergreifenden Code besser, einfach __declspec()mit einem Makro zu verwenden, damit Sie die Daten normal weitergeben können.
Chris Krycho
2
Der Grund ist wahrscheinlich, dass wenn Sie verwenden __stdcall, dann __declspec(dllexport)werden die Dekorationen nicht entfernt. Hinzufügen der Funktion zu einem .defTestament jedoch.
Björn Lindqvist
1
@ BjörnLindqvist +1, beachte, dass dies nur für x86 der Fall ist. Siehe meine Antwort.
Malick
-1

Ich denke, _naked könnte bekommen, was Sie wollen, aber es verhindert auch, dass der Compiler den Stapelverwaltungscode für die Funktion generiert. extern "C" bewirkt eine Namensdekoration im C-Stil. Entfernen Sie das und das sollte Ihre _s loswerden. Der Linker fügt keine Unterstriche hinzu, der Compiler jedoch. stdcall bewirkt, dass die Argumentstapelgröße angehängt wird.

Weitere Informationen finden Sie unter: http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

Die größere Frage ist, warum Sie das tun wollen? Was ist los mit den verstümmelten Namen?

Rob K.
quelle
Die verstümmelten Namen sind hässlich, wenn sie mit LoadLibrary / GetProcAddress oder anderen Methoden aufgerufen werden, die nicht auf einem ac / c ++ - Header beruhen.
Aardvark
4
Dies wäre nicht hilfreich - Sie möchten den vom Compiler generierten Stapelverwaltungscode nur unter sehr speziellen Umständen entfernen. (Nur __cdecl zu verwenden wäre ein weniger schädlicher Weg, um die Dekorationen zu verlieren - standardmäßig scheint __declspec (dllexport) nicht das übliche _ Präfix mit __cdecl-Methoden zu enthalten.)
Ian Griffiths
Ich habe nicht wirklich gesagt, dass es hilfreich sein würde, daher meine Vorbehalte gegen die anderen Effekte und die Frage, warum er es überhaupt tun wollte.
Rob K