web-dev-qa-db-fra.com

C++ DLL Exportation: noms décorés/traités

Créé le C++ de base DLL et les noms exportés à l'aide du fichier de définition de module (MyDLL.def) . Après la compilation, je vérifie les noms de fonction exportés à l'aide de dumpbin.exe Je m'attends à ce que:

SomeFunction

mais je vois ceci à la place:

SomeFunction = SomeFunction@@@23mangledstuff#@@@@

Pourquoi?

La fonction exportée ne semble pas avoir été décorée (surtout si vous n’utilisez pas le fichier Module Def), mais qu’en est-il des autres choses? 

Si j'utilise dumpbin.exe contre un DLL à partir d'une application commerciale, vous obtenez le nettoyage:

SomeFunction

et rien d'autre...

J'ai également essayé de supprimer la définition de module et d'exporter les noms en utilisant le style d'exportation "C", à savoir:

extern "C" void __declspec(dllexport) SomeFunction();

(Le simple fait d'utiliser "extern" C n'a pas créé de fonction exportée)

Cependant, cela crée toujours le même résultat, à savoir:

SomeFunction = SomeFunction@@@23mangledstuff#@@@@

J'ai aussi essayé l'option #define dllexport __declspec(dllexport) et créé une lib sans problème. Cependant, je ne souhaite pas avoir à fournir un fichier LIB aux personnes utilisant le DLL dans leur application C #. 

C'est un simple C++ Vanilla DLL (code non managé), compilé avec C++ rien d'autre qu'un simple en-tête et un code. Sans Module Def, les fonctions exportées sont mutilées (je peux créer une bibliothèque statique et utiliser le LIB sans problème. J'essaie d'éviter cela). Si j'utilise extern "C" __declspec(dllexport)OUa Définition du module, j'obtiens ce qui semble être un nom de fonction non décoré ... le seul problème est qu'il est suivi d'un "=" et ce qui ressemble à une version décorée de la fonction . Je veux me débarrasser de ces trucs après le "=" - ou du moins comprendre pourquoi ils sont là. 

Dans l'état actuel des choses, je suis à peu près certain de pouvoir appeler la fonction depuis C # à l'aide d'un P/Invoke ... Je veux juste éviter cette ordure à la fin du "=". 

Je suis ouvert aux suggestions sur la façon de modifier les paramètres du projet/compilateur, mais je viens d'utiliser le modèle standard Visual Studio DLL - rien de spécial. 

26
Bob

Vous pouvez obtenir ce que vous voulez en désactivant la génération d'informations de débogage. Projet + Propriétés, lieur, débogage, génération d'informations de débogage = n °.

Naturellement, vous ne voulez faire cela que pour la version Release. Où l'option est déjà définie de cette façon.

16
Hans Passant

Au lieu d'utiliser un fichier .def, insérez simplement pragma comment comme ceci

#pragma comment(linker, "/EXPORT:SomeFunction=_SomeFunction@@@23mangledstuff#@@@@")

Edit: Ou encore plus facile: Dans le corps de la fonction, utilisez

#pragma comment(linker, "/EXPORT:"__FUNCTION__"="__FUNCDNAME__)

. . . si vous avez des difficultés à trouver le nom de la fonction décorée. Ce dernier pragma peut être encore réduit avec une simple définition de macro.

40
wqw

Vous devez déclarer les fonctions en tant que extern "C" si vous ne voulez pas que leurs noms soient mutilés.

12
jwismar

Par expérience, faites attention si vous utilisez __stdcall dans la signature de votre fonction. Avec __stdcall, le nom restera dans une certaine mesure mutilé (vous le saurez assez rapidement). Apparemment, il existe deux niveaux de mutilation, l'un que extern "C" traite au niveau C++, mais il ne traite pas d'un autre niveau de gestion de noms causé par __stdcall. Les mutilations supplémentaires sont apparemment liées à la surcharge - mais je n'en suis pas certain.

11
jjones

Désolé de répondre à un ancien fil de discussion, mais ce qui a été marqué comme réponse ne fonctionne pas pour moi.

Comme plusieurs personnes l'ont souligné, la décoration externe "C" est importante. La modification du paramètre "Projet/Propriétés/Éditeur de liens/Débogage/Générer des informations de débogage" n'a aucune incidence sur les noms endommagés générés pour moi en mode de construction Debug ou Release.

Installation: VS2005 compilant un projet de bibliothèque de classes Visual C++. Je vérifiais la sortie .dll compilée avec l'outil Dependency Walker de Microsoft.

Voici un exemple de recette qui a fonctionné pour moi ...

Dans project.h:

#define DllExport extern "C" __declspec( dllexport )

DllExport bool API_Init();
DllExport bool API_Shutdown();

Dans project.cpp:

#include "project.h"

bool API_Init()
{
  return true;
}

bool API_Shutdown()
{
  return true;
}

Puis appelé à partir du code géré C #, class.cs:

using System.Runtime.Interopservices;
namespace Foo
{
    public class Project
    {
        [DllImport("project.dll")]
        public static extern bool API_Init();

        [DllImport("project.dll")]
        public static extern bool API_Shutdown();
    }
}

En procédant de la manière décrite ci-dessus, les noms altérés en mode débogage et en mode publication ont été évités, quel que soit le paramètre Générer les informations de débogage. Bonne chance.

6
AlwaysLearning

Même sans changement, les noms de génération 32 bits et 64 bits sont exportés différemment, même avec extern "C". Découvrez-le avec DEPENDS.EXE.

Cela peut signifier de gros problèmes pour tout client qui utilise LoadLibrary + GetProcAdress pour accéder à votre fonction.

Donc, en plus de tous les autres, utilisez un fichier de définition de module comme suit:

LIBRARY MYDLL
EXPORTS
myFunction=myFunction

Oui, c'est un peu pénible à maintenir, mais combien de fonctions exportées écrivez-vous par jour?

De plus, je change généralement les macros comme indiqué ci-dessous, car mes DLL exportent des fonctions et non des classes C++ et je veux qu'elles soient appelables par la plupart des environnements de programmation:

#ifdef WTS_EXPORTS
#define WTS_API(ReturnType) extern "C" __declspec(dllexport) ReturnType WINAPI
#else
#define WTS_API(ReturnType) extern "C" __declspec(dllimport) ReturnType WINAPI
#endif

WTS_API(int) fnWTS(void);

La dernière ligne utilisée pour confondre VisualAssistX il y a quelques années, je ne sais pas s'il la digère correctement maintenant :-)

5
Dimitris Staikos

Je sais combien de fois j'ai essayé de forcer les noms de fonction en utilisant le code et # pragma's . Et je finis toujours avec exactement la même chose, en utilisant le fichier de définition de module (* .def) à la fin . Et voici la raison:

//---------------------------------------------------------------------------------------------------
// Test cases built using VC2010 - Win32 - Debug / Release << doesn't matter
//---------------------------------------------------------------------------------------------------
// SET: Project > Properties > Linker > Debugging > Generate Debug Info = Yes (/DEBUG)
//  || (or, also doesn't matter)
// SET: Project > Properties > Linker > Debugging > Generate Debug Info = No + delete PDB file!

extern "C" __declspec(dllexport) void SetCallback(LPCALLBACK function);
> SetCallback

extern "C" __declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);
> _SetCallback@4

__declspec(dllexport) void SetCallback(LPCALLBACK function);
> ?SetCallback@@YAXP6AXHPADPAX@Z@Z

__declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);
> ?SetCallback@@YGXP6GXHPADPAX@Z@Z    

//---------------------------------------------------------------------------------------------------
// this also big is nonsense cause as soon you change your calling convention or add / remove
// extern "C" code won't link anymore.

// doesn't work on other cases
#pragma comment(linker, "/EXPORT:SetCallback")
extern "C" __declspec(dllexport) void SetCallback(LPCALLBACK function);

// doesn't work on other cases
#pragma comment(linker, "/EXPORT:SetCallback=SetCallback")
extern "C" __declspec(dllexport) void SetCallback(LPCALLBACK function);

// doesn't work on other cases / creates alias
#pragma comment(linker, "/EXPORT:SetCallback=_SetCallback@4")
extern "C" __declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);

// doesn't work on other cases / creates alias
#pragma comment(linker, "/EXPORT:SetCallback=?SetCallback@@YAXP6AXHPADPAX@Z@Z")
__declspec(dllexport) void SetCallback(LPCALLBACK function);

// doesn't work on other cases / creates alias
#pragma comment(linker, "/EXPORT:SetCallback=?SetCallback@@YGXP6GXHPADPAX@Z@Z")
__declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);

//---------------------------------------------------------------------------------------------------
// So far only repetable case is using Module-Definition File (*.def) in all possible cases:
EXPORTS
  SetCallback

extern "C" __declspec(dllexport) void SetCallback(LPCALLBACK function);
> SetCallback

extern "C" __declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);
> SetCallback

__declspec(dllexport) void SetCallback(LPCALLBACK function);
> SetCallback

__declspec(dllexport) void __stdcall SetCallback(LPCALLBACK function);
> SetCallback

// And by far this is most acceptable as it will reproduce exactly same exported function name 
// using most common compilers. Header is dictating calling convention so not much trouble for
// other sw/ppl trying to build Interop or similar.

Je me demande pourquoi personne ne l'a fait, il ne m'a fallu que 10 minutes pour tester tous les cas.

4
SoLaR

En gros, lorsque vous utilisez des fonctions en C++, une partie de leurs noms inclut désormais leur signature, afin de faciliter les fonctionnalités du langage telles que la surcharge.

Si vous écrivez une DLL à l'aide de __declspec (dllexport), elle devrait également générer une bibliothèque. Lien vers cette bibliothèque, et vous serez automatiquement lié et les fonctions enregistrées par le CRT au moment du démarrage (si vous vous souvenez de modifier toutes vos importations en exportations). Si vous utilisez ce système, vous n'avez pas besoin de connaître les mutilations de noms.

0
Puppy

le SomeFunction @@@ 23mangledstuff # @@@@@ est mutilé pour donner les types et la classe de la fonction C++. Les exportations simples sont des fonctions appelables à partir de C, c’est-à-dire écrites en C ou bien déclarées extern "C" dans du code C++. Si vous voulez une interface simple, vous devez faire en sorte que les fonctions que vous exportez utilisent uniquement des types C fonctions non membres dans l'espace de noms global.

0
Mark

Au cas où cela ne serait pas clair parmi les centaines de lignes de gaufres concernant les exportations mutilées. Voici mon 2c vaut :)

Après avoir créé un projet appelé Win32Project2 à l’aide de VS 2012 et choisi d’exporter tous les symboles de l’assistant. Vous devriez avoir 2 fichiers appelés Win32Project2.cpp et Win32project2.h

Les deux feront référence à un exemple de variable exportable et à un exemple de fonction exportée. 

Dans Win32Project2.h, vous aurez les éléments suivants:

#ifdef WIN32PROJECT2_EXPORTS
#define WIN32PROJECT2_API __declspec(dllexport)
#else
#define WIN32PROJECT2_API __declspec(dllimport)
#endif

extern WIN32PROJECT2_API int nWin32Project2;
WIN32PROJECT2_API int fnWin32Project2(void);

Pour démêler, remplacez les deux dernières lignes par des déclarations "C" externes en:

extern "C" WIN32PROJECT2_API int nWin32Project2;
extern "C" WIN32PROJECT2_API int fnWin32Project2(void);

Dans Win32Project2.cpp, vous aurez également les définitions par défaut suivantes:

// This is an example of an exported variable
WIN32PROJECT2_API int nWin32Project2=0;

// This is an example of an exported function.
WIN32PROJECT2_API int fnWin32Project2(void)
{
    return 42;
}

Pour démêler, changez-les en:

// This is an example of an exported variable
extern "C" WIN32PROJECT2_API int nWin32Project2=0;

// This is an example of an exported function.
extern "C" WIN32PROJECT2_API int fnWin32Project2(void)
{
    return 42;
}

Essentiellement, vous devez utiliser le préfixe extern "C" devant les déclarations afin de forcer l'éditeur de liens à produire des noms similaires à un C non démêlé.

Si vous préférez utiliser des noms mutilés pour ce brouillage supplémentaire (au cas où les informations de manipulation seraient utiles à quelqu'un), utilisez "dumpbin/exports Win32Project2.dll" à partir d'une ligne de commande VC pour rechercher les noms de référence actuels. Il aura la forme "? FnWind32Project2 @ [param bytes] @ [autres informations]. Il existe également d'autres outils de visualisation DLL si la commande VC est exécutée. Shell ne fait pas flotter votre bateau.

La raison pour laquelle MS ne respecte pas cette convention est un mystère. Les informations de manipulation réelles signifient quelque chose (comme la taille du paramètre en octets et plus) qui pourrait être utile pour la validation et le débogage, mais qui est autrement gouj.

Pour importer la fonction DLL ci-dessus dans un projet C # (dans ce cas, une application Windows C # de base contenant un formulaire contenant le bouton "button1"), voici un exemple de code:

using System.Runtime.InteropServices;



    namespace AudioRecApp
    {

      public partial class Form1 : Form
      {
        [ DllImport("c:\\Projects\test\Debug\Win32Projects2.dll")] 
        public static extern int fnWin32Project2();

        public Form1()
        {
          InitializeComponent();
        }


        private void button1_Click(object sender, EventArgs e)
        {
          int value;

          value = fnWin32Project2();
        }
      }
    }
0
PGP