Exporter des fonctions depuis une DLL avec dllexport

105

Je voudrais un exemple simple d'exportation d'une fonction à partir d'une DLL Windows C ++.

Je voudrais voir l'en-tête, le .cppfichier et le .deffichier (si absolument nécessaire).

Je voudrais que le nom exporté ne soit pas décoré . Je voudrais utiliser la convention d'appel la plus standard ( __stdcall?). Je voudrais l'utilisation __declspec(dllexport)et ne pas avoir à utiliser de .deffichier.

Par exemple:

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

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

J'essaie d'éviter que l'éditeur de liens ajoute des traits de soulignement et / ou des nombres (nombre d'octets?) Au nom.

Je suis d'accord pour ne pas prendre en charge dllimportet dllexportutiliser le même en-tête. Je ne veux aucune information sur l'exportation des méthodes de classe C ++, juste des fonctions globales de style C.

METTRE À JOUR

Ne pas inclure la convention d'appel (et l'utilisation extern "C") me donne les noms d'export comme j'aime, mais qu'est-ce que cela signifie? Quelle que soit la convention d'appel par défaut, j'obtiens ce que pinvoke (.NET), declare (vb6) et GetProcAddressj'attends? (Je suppose que GetProcAddresscela dépendrait du pointeur de fonction créé par l'appelant).

Je veux que cette DLL soit utilisée sans fichier d'en-tête, donc je n'ai pas vraiment besoin de beaucoup de fantaisie #definespour rendre l'en-tête utilisable par un appelant.

Je suis d'accord avec une réponse étant que je dois utiliser un *.deffichier.

Aardvark
la source
Je me souviens peut-être mal mais je pense que: a) extern Csupprimera la décoration qui décrit les types de paramètres de la fonction, mais pas la décoration qui décrit la convention d'appel de la fonction; b) pour supprimer toute décoration, vous devez spécifier le nom (non décoré) dans un fichier DEF.
ChrisW
C'est ce que je voyais aussi. Peut-être devriez-vous ajouter cela comme une réponse à part entière?
Aardvark

Réponses:

134

Si vous voulez des exportations C simples, utilisez un projet C et non C ++. Les DLL C ++ reposent sur la gestion des noms pour tous les ismes C ++ (espaces de noms, etc.). Vous pouvez compiler votre code en C en allant dans les paramètres de votre projet sous C / C ++ -> Avancé, il existe une option "Compiler en tant que" qui correspond aux commutateurs du compilateur / TP et / TC.

Si vous souhaitez toujours utiliser C ++ pour écrire les éléments internes de votre bibliothèque, mais exporter certaines fonctions démêlées pour une utilisation en dehors de C ++, consultez la deuxième section ci-dessous.

Exportation / importation de bibliothèques DLL dans VC ++

Ce que vous voulez vraiment faire est de définir une macro conditionnelle dans un en-tête qui sera inclus dans tous les fichiers source de votre projet DLL:

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

Ensuite, sur une fonction que vous souhaitez exporter vous utilisez LIBRARY_API:

LIBRARY_API int GetCoolInteger();

Dans votre projet de construction de bibliothèque, créez une définition, LIBRARY_EXPORTScela entraînera l'exportation de vos fonctions pour votre construction DLL.

Puisque LIBRARY_EXPORTSne sera pas défini dans un projet utilisant la DLL, lorsque ce projet inclut le fichier d'en-tête de votre bibliothèque, toutes les fonctions seront importées à la place.

Si votre bibliothèque doit être multiplateforme, vous pouvez définir LIBRARY_API comme rien si vous n'êtes pas sous Windows:

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

Lorsque vous utilisez dllexport / dllimport, vous n'avez pas besoin d'utiliser des fichiers DEF, si vous utilisez des fichiers DEF, vous n'avez pas besoin d'utiliser dllexport / dllimport. Les deux méthodes accomplissent la même tâche de différentes manières, je crois que dllexport / dllimport est la méthode recommandée parmi les deux.

Exportation de fonctions démêlées à partir d'une DLL C ++ pour LoadLibrary / PInvoke

Si vous en avez besoin pour utiliser LoadLibrary et GetProcAddress, ou peut-être pour importer à partir d'un autre langage (par exemple PInvoke de .NET, ou FFI en Python / R, etc.), vous pouvez utiliser en extern "C"ligne avec votre dllexport pour dire au compilateur C ++ de ne pas modifier les noms. Et puisque nous utilisons GetProcAddress au lieu de dllimport, nous n'avons pas besoin de faire la danse ifdef d'en haut, juste un simple dllexport:

Le 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())
  );
}

Et voici à quoi ressemblent les exportations avec Dumpbin / exports:

  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)

Donc, ce code fonctionne bien:

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
la source
1
extern "C" a semblé supprimer la transformation des noms de style c ++. Toute la chose import / export (que j'ai essayé de suggérer de ne pas inclure dans la question) n'est pas vraiment ce que je demande (mais c'est une bonne information). J'ai pensé que cela nuirait au problème.
Aardvark
La seule raison pour laquelle je peux penser que vous en auriez besoin est pour LoadLibrary et GetProcAddress ... Ceci est déjà pris en charge, je vais l'expliquer dans mon corps de réponse ...
joshperry
Est-ce que EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport)? Est-ce dans le SDK?
Aardvark le
3
N'oubliez pas d'ajouter le fichier de définition du module dans les paramètres de l'éditeur de liens du projet - simplement "ajouter un élément existant au projet" ne suffit pas!
Jimmy
1
Je l'ai utilisé pour compiler une DLL avec VS, puis l'appeler à partir de R en utilisant .C. Génial!
Juancentro
33

Pour C ++:

Je viens de faire face au même problème et je pense qu'il vaut la peine de mentionner qu'un problème survient lorsque l'on utilise à la fois __stdcall(ou WINAPI) et extern "C" :

Comme vous le savez, extern "C"supprime la décoration afin qu'au lieu de:

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

vous obtenez un nom de symbole non décoré:

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

Cependant, la _stdcall(= macro WINAPI, qui change la convention d'appel) décore également les noms de sorte que si nous utilisons les deux, nous obtenons:

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

et le bénéfice de extern "C"est perdu car le symbole est décoré (avec _ @bytes)

Notez que cela ne se produit que pour l'architecture x86 car la __stdcallconvention est ignorée sur x64 ( msdn : sur les architectures x64, par convention, les arguments sont passés dans les registres lorsque cela est possible, et les arguments suivants sont passés sur la pile .).

Ceci est particulièrement délicat si vous ciblez à la fois les plates-formes x86 et x64.


Deux solutions

  1. Utilisez un fichier de définition. Mais cela vous oblige à conserver l'état du fichier def.

  2. le moyen le plus simple: définissez la macro (voir msdn ):

#define EXPORT comment (éditeur de liens, "/ EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)

puis incluez le pragma suivant dans le corps de la fonction:

#pragma EXPORT

Exemple complet:

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

Cela exportera la fonction non décorée pour les cibles x86 et x64 tout en préservant la __stdcallconvention pour x86. Le __declspec(dllexport) n'est pas obligatoire dans ce cas.

Malick
la source
5
Merci pour cet indice important. Je me suis déjà demandé pourquoi ma DLL 64 bits est différente de celle de 32 bits. Je trouve votre réponse beaucoup plus utile que celle acceptée comme réponse.
Elmue
1
J'aime vraiment cette approche. Ma seule recommandation serait de renommer la macro en EXPORT_FUNCTION car la __FUNCTION__macro ne fonctionne que dans les fonctions.
Luis
3

J'ai eu exactement le même problème, ma solution était d'utiliser le fichier de définition de module (.def) au lieu de __declspec(dllexport)définir les exportations ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ). Je n'ai aucune idée de pourquoi cela fonctionne, mais c'est le cas

Rytis I
la source
Note à tous ceux en cours d' exécution d' autre dans celle - ci: en utilisant un .deffichier module exporte exerce ses travaux, mais au détriment d'être en mesure de fournir des externdéfinitions dans le fichier d' en- tête par exemple des données dans globales auquel cas, vous devez fournir la externdéfinition manuellement interne des utilisations de ces données. (Oui, il y a des moments où vous en avez besoin.) Il est préférable à la fois en général et en particulier pour le code multiplate-forme d'utiliser simplement __declspec()avec une macro afin que vous puissiez transmettre les données normalement.
Chris Krycho
2
La raison en est probablement parce que si vous utilisez __stdcall, puis __declspec(dllexport)ne pas enlever les décorations. Ajout de la fonction à un .deftestament cependant.
Björn Lindqvist
1
@ BjörnLindqvist +1, notez que ce n'est le cas que pour x86. Voyez ma réponse.
Malick
-1

Je pense que _naked peut obtenir ce que vous voulez, mais il empêche également le compilateur de générer le code de gestion de pile pour la fonction. extern "C" provoque la décoration de nom de style C. Supprimez cela et cela devrait vous débarrasser de vos _. L'éditeur de liens n'ajoute pas les traits de soulignement, le compilateur le fait. stdcall entraîne l'ajout de la taille de la pile d'arguments.

Pour plus d'informations, voir: http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

La plus grande question est de savoir pourquoi voulez-vous faire cela? Quel est le problème avec les noms mutilés?

Rob K
la source
Les noms mutilés sont laids lorsqu'ils sont appelés, utilisez LoadLibrary / GetProcAddress ou d'autres méthodes qui ne reposent pas sur l'en-tête ac / c ++.
Aardvark
4
Ce ne serait pas utile - vous ne voulez supprimer le code de gestion de pile généré par le compilateur que dans des circonstances très spécialisées. (Le simple fait d'utiliser __cdecl serait un moyen moins dangereux de perdre les décorations - par défaut, __declspec (dllexport) ne semble pas inclure le préfixe _ habituel avec les méthodes __cdecl.)
Ian Griffiths
Je ne disais pas vraiment que ce serait utile, d'où mes mises en garde sur les autres effets et la question de savoir pourquoi il voulait même le faire.
Rob K