web-dev-qa-db-fra.com

Exporter tous les symboles lors de la création d'un DLL

Avec VS2005, je souhaite créer une DLL et exporter automatiquement tous les symboles sans ajouter __declspec (dllexport) partout et sans créer manuellement de fichiers .def. Est-ce qu'il y a un moyen de faire ça?

62
Jazz

Ça peut être fait...

Pour ce faire, utilisez l’option/DEF de l’éditeur de liens pour transmettre un "fichier de définition de module" contenant une liste de nos exportations. Je vois dans votre question que vous connaissez ces fichiers. Cependant, nous ne le faisons pas à la main. La liste des exportations elle-même est créée par la commande dumpbin / LINKERMEMBER et par la manipulation de la sortie via un script simple au format d’un fichier de définition de module.

C’est beaucoup de travail à installer, mais cela nous permet de compiler du code créé sans déclaration dllexport pour Unix sous Windows.

36
Andrew Stein

Réponse courte

Vous pouvez le faire avec l'aide de la nouvelle version de CMake (toute version de cmake-3.3.20150721-g9cd2f-win32-x86.exe ou supérieure).

Actuellement, il se trouve dans la branche dev . Plus tard, la fonctionnalité sera ajoutée à la version finale de cmake-3.4.

Lien vers le cmake dev: 

cmake_dev

Lien vers un article décrivant la technique:

Créer des dll sur Windows sans declspec () à l'aide de la nouvelle fonctionnalité d'export CMake

Lien vers un exemple de projet:

cmake_windows_export_all_symbols


Longue réponse

Attention: Toutes les informations ci-dessous sont liées au compilateur MSVC ou à Visual Studio.

Si vous utilisez d'autres compilateurs tels que gcc sous Linux ou MinGW, le compilateur gcc sous Windows ne génère pas d'erreurs de liaison en raison de symboles non exportés, car le compilateur gcc exporte tous les symboles d'une bibliothèque dynamique (dll) par défaut à la place des compilateurs MSVC ou Intel Windows. .

Dans Windows, vous devez explicitement exporter le symbole d'une dll.

Plus d'informations à ce sujet sont fournies par des liens: 

Exporter à partir d'une DLL

HowTo: Exporter des classes C++ à partir d'une DLL

Donc, si vous voulez exporter tous les symboles de dll avec MSVC (compilateur Visual Studio), vous avez deux options: 

  • Utilisez le mot-clé __declspec (dllexport) dans la définition de la classe/fonction.
  • Créez un fichier de définition de module (.def) et utilisez le fichier .def lors de la génération de la DLL.

1. Utilisez le mot clé __declspec (dllexport) dans la définition de la classe/fonction


1.1. Ajouter des macros "__declspec (dllexport)/__declspec (dllimport)" à une classe ou une méthode que vous souhaitez utiliser. Donc, si vous voulez exporter toutes les classes, vous devez ajouter ces macros à toutes les classes

Plus d'informations à ce sujet sont fournies par le lien:

Exportation à partir d'une DLL à l'aide de __declspec (dllexport)

Exemple d'utilisation (remplacez "Projet" par le nom du projet réel):

// ProjectExport.h

#ifndef __PROJECT_EXPORT_H
#define __PROJECT_EXPORT_H

#ifdef USEPROJECTLIBRARY
#ifdef  PROJECTLIBRARY_EXPORTS 
#define PROJECTAPI __declspec(dllexport)
#else
#define PROJECTAPI __declspec(dllimport)
#endif
#else
#define PROJECTAPI
#endif

#endif

Ajoutez ensuite "PROJECTAPI" à toutes les classes . Définissez "USEPROJECTLIBRARY" uniquement si vous souhaitez exporter/importer des symboles de dll . Définissez "PROJECTLIBRARY_EXPORTS" pour la dll.

Exemple d'exportation de classe:

#include "ProjectExport.h"

namespace hello {
    class PROJECTAPI Hello {}   
}

Exemple d'exportation de fonction:

#include "ProjectExport.h"

PROJECTAPI void HelloWorld();

Attention: n'oubliez pas d'inclure le fichier "ProjectExport.h".


1.2. Exporter en tant que fonctions C . Si vous utilisez un compilateur C++ pour que le code de compilation soit écrit en C, vous pouvez ajouter extern "C" devant une fonction pour éliminer les mutilations de noms

Pour plus d'informations sur le nom C++, cliquez sur le lien:

Nom Décoration

Exemple d'utilisation:

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

Plus d'informations à ce sujet sont fournies par le lien:

Exportation de fonctions C++ à utiliser dans des exécutables en langage C


2. Créez un fichier de définition de module (.def) et utilisez le fichier .def lors de la création de la DLL.

Plus d'informations à ce sujet sont fournies par le lien:

Exportation à partir d'une DLL à l'aide de fichiers DEF

En outre, je décris trois approches pour créer un fichier .def.


2.1. Exporter les fonctions C

Dans ce cas, vous pouvez simplement ajouter des déclarations de fonction dans le fichier .def à la main.

Exemple d'utilisation:

extern "C" void HelloWorld();

Exemple de fichier .def (convention de dénomination __cdecl):

EXPORTS 
_HelloWorld

2.2. Exporter les symboles de la bibliothèque statique

J'ai essayé l'approche proposée par "user72260".

Il a dit:

  • Tout d'abord, vous pouvez créer une bibliothèque statique.
  • Ensuite, utilisez "dumpbin/LINKERMEMBER" pour exporter tous les symboles de la bibliothèque statique.
  • Analyser la sortie.
  • Mettez tous les résultats dans un fichier .def.
  • Créez une DLL avec le fichier .def.

J’ai utilisé cette approche, mais il n’est pas très pratique de toujours créer deux versions (l’une en tant que bibliothèque statique et l’autre en tant que bibliothèque dynamique). Cependant, je dois admettre que cette approche fonctionne vraiment.


2.3. Exporter des symboles à partir de fichiers .obj ou avec l'aide de CMake


2.3.1. Avec l'utilisation de CMake

Remarque importante: Vous n'avez pas besoin de macros d'exportation vers des classes ou des fonctions!

Remarque importante: Vous ne pouvez pas utiliser/GL ( Optimisation du programme entier ) avec cette approche!

  • Créez un projet CMake basé sur le fichier "CMakeLists.txt".
  • Ajoutez la ligne suivante au fichier "CMakeLists.txt": Set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
  • Créez ensuite un projet Visual Studio à l'aide de "CMake (cmake-gui)".
  • Compiler le projet.

Exemple d'utilisation:

Dossier racine

CMakeLists.txt (dossier racine)

cmake_minimum_required(VERSION 2.6)
project(cmake_export_all)

set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)

set(dir ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${dir}/bin")

set(SOURCE_EXE main.cpp)

include_directories(foo)

add_executable(main ${SOURCE_EXE})

add_subdirectory(foo)

target_link_libraries(main foo)

main.cpp (dossier racine)

#include "foo.h"

int main() {
    HelloWorld();

    return 0;
}

Dossier Foo (dossier racine/dossier Foo)

CMakeLists.txt (dossier Foo)

project(foo)

set(SOURCE_LIB foo.cpp)

add_library(foo SHARED ${SOURCE_LIB})

foo.h (dossier Foo)

void HelloWorld();

foo.cpp (dossier Foo)

#include <iostream>

void HelloWorld() {
    std::cout << "Hello World!" << std::endl;
}

Lien vers le projet exemple à nouveau:

cmake_windows_export_all_symbols

CMake utilise l'approche différente de "2.2. Exporter les symboles de la bibliothèque statique". 

Il fait ce qui suit:

1) Créez le fichier "objects.txt" dans le répertoire de construction avec les informations. Les fichiers .obj sont utilisés dans une dll.

2) Compilez la dll, c’est-à-dire créez des fichiers .obj.

3) Selon les informations du fichier "objects.txt", extraire tous les symboles du fichier .obj.

Exemple d'utilisation:

DUMPBIN /SYMBOLS example.obj > log.txt

Plus d'informations à ce sujet sont fournies par le lien:

/SYMBOLES

4) Analyse extraite des informations du fichier .obj.

À mon avis, j'utiliserais la convection d'appel, par exemple "__cdecl/__ fastcall", le champ du symbole "SECTx/UNDEF" (la troisième colonne), le champ du symbole "External/Static" (la cinquième colonne), "??", "? " informations pour analyser un fichier .obj.Je ne sais pas comment CMake exactement analyser un fichier .obj . Cependant, CMake est une source ouverte, vous pouvez donc savoir s'il vous intéresse.

Lien vers le projet CMake:.

CMake_github

5) Placez tous les symboles exportés dans un fichier .def.

6) Liez une dll à l’utilisation d’un fichier créé par .def.

Les étapes 4) à 5), qui consistent à analyser les fichiers .obj et à créer un fichier .def avant de lier et d'utiliser le fichier .def que CMake crée à l'aide de "l'événement de pré-liaison" . vous pouvez appeler n’importe quel programme de votre choix .. Donc, en cas d ’« utilisation de CMake », appelez le CMake avec les informations suivantes pour savoir où placer le fichier .def et où se trouver le fichier« objects.txt » avec l'argument "-E __create_def" . Vous pouvez vérifier ces informations en créant un projet CMake Visusal Studio avec "set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)" ", puis vérifiez le fichier de projet" .vcxproj "pour dll.

Si vous essayez de compiler un projet sans "set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)" ou avec "set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF)", vous obtiendrez des erreurs de liaison en raison du fait que les symboles ne sont pas exportés à partir d'une dll.

Plus d'informations à ce sujet sont fournies par le lien:.

Comprendre les étapes de construction personnalisées et les événements de construction


2.3.2. Sans utilisation de CMake

Vous pouvez facilement créer un petit programme pour analyser le fichier .obj par vous-même sans utiliser CMake. Hovewer, je dois admettre que CMake est un programme très utile, en particulier pour le développement multiplate-forme.

You simple could create a small program for parsing .obj file by youself without CMake usege. Hovewer, I have to admit that CMake is very usefull program especially for cross-platform development.

29
Maks

J'ai écrit un petit programme pour analyser la sortie de "dumpbin/linkermember" sur le fichier .lib. J'ai plus de 8 000 références de fonctions à exporter à partir d'une seule DLL. 

Le problème avec le faire sur un DLL est que vous devez lier le DLL sans les définitions exportées une fois pour créer le fichier .lib, puis générer le .def, ce qui signifie que vous avez maintenant. pour relier de nouveau la DLL avec le fichier .def, afin d’exporter les références. 

Travailler avec des bibliothèques statiques est plus facile. Compilez toutes vos sources dans des bibliothèques statiques, exécutez dumbin, générez un fichier .def avec votre petit programme, puis liez les bibliothèques ensemble dans un DLL, à présent que les noms d'exportation sont disponibles.

Malheureusement, mon entreprise ne me permet pas de vous montrer la source. Le travail à effectuer consiste à identifier les "symboles publics" de la sortie de vidage qui ne sont pas nécessaires dans votre fichier def. Vous devez jeter beaucoup de ces références, NULL_IMPORT_DESCRIPTOR, NULL_THUNK_DATA, __imp *, etc.

8
user72260

Merci @Maks pour la réponse détaillée .

Vous trouverez ci-dessous un exemple de ce que j’ai utilisé dans l’événement Pre-Link pour générer le fichier def à partir de obj. J'espère que ce sera utile pour quelqu'un.

dumpbin /SYMBOLS $(Platform)\$(Configuration)\mdb.obj | findstr /R "().*External.*mdb_.*" > $(Platform)\$(Configuration)\mdb_symbols
(echo EXPORTS & for /F "usebackq tokens=2 delims==|" %%E in (`type $(Platform)\$(Configuration)\mdb_symbols`) do @echo  %%E) > $(Platform)\$(Configuration)\lmdb.def

En gros, je viens de prendre l’un des objets (mdb.obj) et les fonctions grepped mdb_ *. Ensuite, la sortie analysée conserve uniquement les noms en tenant compte du nombre d'espaces pour l'indentation (l'un après la division en jetons et l'autre en écho. Je ne sais pas si c'est important cependant).

Le script du monde réel sera probablement un peu plus complexe cependant.

2
Sergey

Je souhaite créer une DLL et exporter automatiquement tous les symboles sans ajouter __declspec (dllexport) partout et sans créer manuellement de fichiers .def. Est-ce qu'il y a un moyen de faire ça?

C'est une réponse tardive, mais elle fournit les détails de la réponse de Maks dans la section (2). Cela évite également les scripts et utilise un programme C++ appelé dump2def. Le code source pour dump2def est ci-dessous.

Enfin, les étapes ci-dessous supposent que vous travaillez à partir d'une invite de Visual Studio Developer , qui est un terminal Windows sur lequel vcvarsall.bat a été exécuté. Vous devez vous assurer que les outils de construction tels que cl.exe, lib.exe, link.exe et nmake.exe sont sur le chemin.

Plus d'informations à ce sujet sont fournies par le lien:

Exportation depuis un DLL avec DEF Des dossiers
...

Les instructions ci-dessous utilisent:

  • static.lib - archive de bibliothèque statique (fichier * .a sous Linux)
  • dynamic.dll - bibliothèque dynamique (fichier * .so sous Linux)
  • import.lib - bibliothèque dynamique (bibliothèque d'importation sous Windows)

Notez également que, même si vous exportez tout à partir de la DLL, les clients doivent toujours utiliser declspec(dllimport) sur tous les symboles (classes, fonctions et données) qu'ils utilisent. Voir aussi sur MSDN.

Tout d’abord, prenez vos objets et créez une archive statique:

AR = lib.exe
ARFLAGS = /nologo

CXX_SRCS = a.cpp b.cpp c.cpp ...
LIB_OBJS = a.obj b.obj c.obj ...

static.lib: $(LIB_OBJS)
    $(AR) $(ARFLAGS) $(LIB_OBJS) /out:$@

Deuxièmement, exécutez dumpbin.exe /LINKERMEMEBER sur l’archive pour créer un fichier *.dump:

dynamic.dump:
    dumpbin /LINKERMEMBER static.lib > dynamic.dump

Troisièmement, exécutez dump2def.exe sur le fichier *.dump pour générer le fichier *.def. Le code source pour dump2def.exe est ci-dessous.

dynamic.def: static.lib dynamic.dump
    dump2def.exe dynamic.dump dynamic.def

Quatrièmement, construisez la DLL:

LD = link.exe
LDFLAGS = /OPT:REF /MACHINE:X64
LDLIBS = kernel32.lib

dynamic.dll: $(LIB_OBJS) dynamic.def
    $(LD) $(LDFLAGS) /DLL /DEF:dynamic.def /IGNORE:4102 $(LIB_OBJS) $(LDLIBS) /out:$@

/IGNORE:4102 est utilisé pour éviter cet avertissement. Il est attendu dans ce cas:

dynamic.def : warning LNK4102: export of deleting destructor 'public: virtual v
oid * __ptr64 __cdecl std::exception::`scalar deleting destructor'(unsigned int)
 __ptr64'; image may not run correctly

Lorsque la recette dynamic.dll est invoquée, elle crée également un fichier d'importation dynamic.lib et un fichier dynamic.exp:

> cls && nmake /f test.nmake dynamic.dll
...
Creating library dynamic.lib and object dynamic.exp

Et:

 C:\Users\Test\testdll>dir *.lib *.dll *.def *.exp
 Volume in drive C is Windows
 Volume Serial Number is CC36-23BE

 Directory of C:\Users\Test\testdll

01/06/2019  08:33 PM        71,501,578 static.lib
01/06/2019  08:33 PM        11,532,052 dynamic.lib

 Directory of C:\Users\Test\testdll

01/06/2019  08:35 PM         5,143,552 dynamic.dll

 Directory of C:\Users\Test\testdll

01/06/2019  08:33 PM         1,923,070 dynamic.def

 Directory of C:\Users\Test\testdll

01/06/2019  08:35 PM         6,937,789 dynamic.exp
               5 File(s)     97,038,041 bytes
               0 Dir(s)  139,871,186,944 bytes free

Coller le tout ici est ce à quoi ressemble le makefile de Nmake. Il fait partie d'un fichier réel Nmake :

all: test.exe

test.exe: pch.pch static.lib $(TEST_OBJS)
    $(LD) $(LDFLAGS) $(TEST_OBJS) static.lib $(LDLIBS) /out:$@

static.lib: $(LIB_OBJS)
    $(AR) $(ARFLAGS) $(LIB_OBJS) /out:$@

dynamic.map:
    $(LD) $(LDFLAGS) /DLL /MAP /MAPINFO:EXPORTS $(LIB_OBJS) $(LDLIBS) /out:dynamic.dll

dynamic.dump:
    dumpbin.exe /LINKERMEMBER static.lib /OUT:dynamic.dump

dynamic.def: static.lib dynamic.dump
    dump2def.exe dynamic.dump

dynamic.dll: $(LIB_OBJS) dynamic.def
    $(LD) $(LDFLAGS) /DLL /DEF:dynamic.def /IGNORE:4102 $(LIB_OBJS) $(LDLIBS) /out:$@

clean:
    $(RM) /F /Q pch.pch $(LIB_OBJS) pch.obj static.lib $(TEST_OBJS) test.exe *.pdb

Et voici le code source pour dump2def.exe:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <set>

typedef std::set<std::string> SymbolMap;

void PrintHelpAndExit(int code)
{
    std::cout << "dump2def - create a module definitions file from a dumpbin file" << std::endl;
    std::cout << "           Written and placed in public domain by Jeffrey Walton" << std::endl;
    std::cout << std::endl;

    std::cout << "Usage: " << std::endl;

    std::cout << "  dump2def <infile>" << std::endl;
    std::cout << "    - Create a def file from <infile> and write it to a file with" << std::endl;
    std::cout << "      the same name as <infile> but using the .def extension" << std::endl;

    std::cout << "  dump2def <infile> <outfile>" << std::endl;
    std::cout << "    - Create a def file from <infile> and write it to <outfile>" << std::endl;

    std::exit(code);
}

int main(int argc, char* argv[])
{
    // ******************** Handle Options ******************** //

    // Convenience item
    std::vector<std::string> opts;
    for (size_t i=0; i<argc; ++i)
        opts.Push_back(argv[i]);

    // Look for help
    std::string opt = opts.size() < 3 ? "" : opts[1].substr(0,2);
    if (opt == "/h" || opt == "-h" || opt == "/?" || opt == "-?")
        PrintHelpAndExit(0);

    // Add <outfile> as needed
    if (opts.size() == 2)
    {
        std::string outfile = opts[1];
        std::string::size_type pos = outfile.length() < 5 ? std::string::npos : outfile.length() - 5;
        if (pos == std::string::npos || outfile.substr(pos) != ".dump")
            PrintHelpAndExit(1);

        outfile.replace(pos, 5, ".def");
        opts.Push_back(outfile);
    }

    // Check or exit
    if (opts.size() != 3)
        PrintHelpAndExit(1);

    // ******************** Read MAP file ******************** //

    SymbolMap symbols;

    try
    {
        std::ifstream infile(opts[1].c_str());
        std::string::size_type pos;
        std::string line;

        // Find start of the symbol table
        while (std::getline(infile, line))
        {
            pos = line.find("public symbols");
            if (pos == std::string::npos) { continue; }        

            // Eat the whitespace after the table heading
            infile >> std::ws;
            break;
        }

        while (std::getline(infile, line))
        {
            // End of table
            if (line.empty()) { break; }

            std::istringstream iss(line);
            std::string address, symbol;
            iss >> address >> symbol;

            symbols.insert(symbol);
        }
    }
    catch (const std::exception& ex)
    {
        std::cerr << "Unexpected exception:" << std::endl;
        std::cerr << ex.what() << std::endl;
        std::cerr << std::endl;

        PrintHelpAndExit(1);
    }

    // ******************** Write DEF file ******************** //

    try
    {
        std::ofstream outfile(opts[2].c_str());

        // Library name, cryptopp.dll
        std::string name = opts[2];
        std::string::size_type pos = name.find_last_of(".");

        if (pos != std::string::npos)
            name.erase(pos);

        outfile << "LIBRARY " << name << std::endl;
        outfile << "DESCRIPTION \"Crypto++ Library\"" << std::endl;        
        outfile << "EXPORTS" << std::endl;
        outfile << std::endl;

        outfile << "\t;; " << symbols.size() << " symbols" << std::endl;

        // Symbols from our object files
        SymbolMap::const_iterator it = symbols.begin();
        for ( ; it != symbols.end(); ++it)
            outfile << "\t" << *it << std::endl;
    }
    catch (const std::exception& ex)
    {
        std::cerr << "Unexpected exception:" << std::endl;
        std::cerr << ex.what() << std::endl;
        std::cerr << std::endl;

        PrintHelpAndExit(1);
    }   

    return 0;
}
0
jww