Mit VS2005 möchte ich eine DLL erstellen und automatisch alle Symbole exportieren, ohne überall __declspec (dllexport) hinzuzufügen und ohne .def-Dateien von Hand zu erstellen. Gibt es eine Möglichkeit, dies zu tun?
Antworten:
Es kann getan werden ...
Wir verwenden hier die Option / DEF des Linkers, um eine "Moduldefinitionsdatei" zu übergeben, die eine Liste unserer Exporte enthält. Ich sehe aus Ihrer Frage, dass Sie über diese Dateien Bescheid wissen. Wir machen es jedoch nicht von Hand. Die Liste der Exporte selbst wird mit dem Befehl dumpbin / LINKERMEMBER erstellt und die Ausgabe über ein einfaches Skript im Format einer Moduldefinitionsdatei bearbeitet.
Das Einrichten ist viel Arbeit, aber es ermöglicht uns, Code zu kompilieren, der ohne dllexport-Deklarationen für Unix unter Windows erstellt wurde.
__declspec(dllexport)
überall schreiben . Ebenso schwierig ist es, überall ein anderes Exportmakro hinzuzufügen.
Sie können dies mit Hilfe der neuen Version von CMake tun (jede Version cmake-3.3.20150721-g9cd2f-win32-x86.exe oder höher).
Derzeit ist es in der Dev-Branche. Später wird die Funktion in der Release-Version von cmake-3.4 hinzugefügt.
Link zum cmake dev:
Link zu einem Artikel, der die Technik beschreibt:
Erstellen Sie DLLs unter Windows ohne declspec () mit der neuen Funktion CMake export all
Link zu einem Beispielprojekt:
cmake_windows_export_all_symbols
Achtung: Alle folgenden Informationen beziehen sich auf den MSVC-Compiler oder Visual Studio.
Wenn Sie andere Compiler wie gcc unter Linux oder MinGW gcc compiler unter Windows verwenden, treten keine Verknüpfungsfehler aufgrund nicht exportierter Symbole auf, da der gcc-Compiler standardmäßig alle Symbole in einer dynamischen Bibliothek (DLL) anstelle von MSVC- oder Intel Windows-Compilern exportiert .
In Windows müssen Sie das Symbol explizit aus einer DLL exportieren.
Weitere Informationen hierzu finden Sie unter folgenden Links:
HowTo: Exportieren Sie C ++ - Klassen aus einer DLL
Wenn Sie also alle Symbole mit MSVC (Visual Studio Compiler) aus der DLL exportieren möchten, haben Sie zwei Möglichkeiten:
1. Verwenden Sie das Schlüsselwort __declspec (dllexport) in der Definition der Klasse / Funktion
1.1. Fügen Sie einer Klasse oder Methode, die Sie verwenden möchten, die Makros "__declspec (dllexport) / __declspec (dllimport)" hinzu. Wenn Sie also alle Klassen exportieren möchten, sollten Sie diese Makros allen hinzufügen
Weitere Informationen hierzu finden Sie unter folgendem Link:
Exportieren aus einer DLL mit __declspec (dllexport)
Anwendungsbeispiel ("Projekt" durch echten Projektnamen ersetzen):
// 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
Fügen Sie dann allen Klassen "PROJECTAPI" hinzu. Definieren Sie "USEPROJECTLIBRARY" nur, wenn Sie Symbole aus der DLL exportieren / importieren möchten. Definieren Sie "PROJECTLIBRARY_EXPORTS" für die DLL.
Beispiel für den Klassenexport:
#include "ProjectExport.h"
namespace hello {
class PROJECTAPI Hello {}
}
Beispiel für den Funktionsexport:
#include "ProjectExport.h"
PROJECTAPI void HelloWorld();
Achtung: Vergessen Sie nicht, die Datei "ProjectExport.h" einzuschließen.
1.2. Als C-Funktionen exportieren. Wenn Sie den C ++ - Compiler verwenden, um zu kompilieren, dass Code auf C geschrieben ist, können Sie externes "C" vor einer Funktion hinzufügen, um die Namensverfälschung zu beseitigen
Weitere Informationen zum Mangeln von C ++ - Namen finden Sie unter folgendem Link:
Anwendungsbeispiel:
extern "C" __declspec(dllexport) void HelloWorld();
Weitere Informationen hierzu finden Sie unter folgendem Link:
Exportieren von C ++ - Funktionen zur Verwendung in ausführbaren Dateien in C-Sprache
2. Erstellen Sie eine .def-Datei (Module Definition) und verwenden Sie beim Erstellen der DLL die .def-Datei
Weitere Informationen hierzu finden Sie unter folgendem Link:
Exportieren aus einer DLL mit DEF-Dateien
Weiter beschreibe ich drei Ansätze zum Erstellen einer .def-Datei.
2.1. C-Funktionen exportieren
In diesem Fall können Sie Funktionsdeklarationen einfach von Hand in die .def-Datei einfügen.
Anwendungsbeispiel:
extern "C" void HelloWorld();
Beispiel für eine .def-Datei (__cdecl-Namenskonvention):
EXPORTS
_HelloWorld
2.2. Exportieren Sie Symbole aus der statischen Bibliothek
Ich habe den von "user72260" vorgeschlagenen Ansatz ausprobiert.
Er sagte:
Ich habe diesen Ansatz verwendet, aber es ist nicht sehr praktisch, immer zwei Builds zu erstellen (einen als statische und einen als dynamische Bibliothek). Ich muss jedoch zugeben, dass dieser Ansatz wirklich funktioniert.
2.3. Exportieren Sie Symbole aus OBJ-Dateien oder mithilfe von CMake
2.3.1. Mit CMake-Nutzung
Wichtiger Hinweis: Sie benötigen keine Exportmakros für Klassen oder Funktionen!
Wichtiger Hinweis: Sie können / GL ( Whole Program Optimization ) nicht verwenden, wenn Sie diesen Ansatz verwenden!
Anwendungsbeispiel:
Root-Verzeichnis
CMakeLists.txt (Stammordner)
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 (Stammordner)
#include "foo.h"
int main() {
HelloWorld();
return 0;
}
Foo-Ordner (Stammordner / Foo-Ordner)
CMakeLists.txt (Foo-Ordner)
project(foo)
set(SOURCE_LIB foo.cpp)
add_library(foo SHARED ${SOURCE_LIB})
foo.h (Foo-Ordner)
void HelloWorld();
foo.cpp (Foo-Ordner)
#include <iostream>
void HelloWorld() {
std::cout << "Hello World!" << std::endl;
}
Nochmals Link zum Beispielprojekt:
cmake_windows_export_all_symbols
CMake verwendet den anderen Ansatz als "2.2. Symbole aus statischer Bibliothek exportieren".
Es macht folgendes:
1) Erstellen Sie die Datei "objects.txt" im Build-Verzeichnis mit Informationen zu OBJ-Dateien, die in einer DLL verwendet werden.
2) Kompilieren Sie die DLL, dh erstellen Sie OBJ-Dateien.
3) Basierend auf den Dateiinformationen "objects.txt" extrahieren Sie alle Symbole aus der OBJ-Datei.
Anwendungsbeispiel:
DUMPBIN /SYMBOLS example.obj > log.txt
Weitere Informationen hierzu finden Sie unter folgendem Link:
4) Analyse aus .obj-Dateiinformationen.
Meiner Meinung nach würde ich die Aufrufkonvektion verwenden, zum Beispiel "__cdecl / __ fastcall", "SECTx / UNDEF" -Symbolfeld (die dritte Spalte), "External / Static" -Symbolfeld (die fünfte Spalte), "??", "? "" Informationen zum Parsen von OBJ-Dateien.
Ich weiß nicht, wie genau CMake eine OBJ-Datei analysiert. CMake ist jedoch Open Source, sodass Sie herausfinden können, ob es für Sie interessiert ist.
Link zum CMake-Projekt:
5) Fügen Sie alle exportierten Symbole in eine .def-Datei ein.
6) Verknüpfen Sie eine DLL mit der Verwendung einer von .def erstellten Datei.
Schritte 4) -5), dh .obj-Dateien analysieren und eine .def-Datei erstellen, bevor die .def-Datei verknüpft und verwendet wird, die CMake mithilfe des "Pre-Link-Ereignisses" ausführt. Während das "Pre-Link-Ereignis" ausgelöst wird, können Sie jedes gewünschte Programm aufrufen. Bei "CMake-Verwendung" "Pre-Link-Ereignis" rufen Sie das CMake mit den folgenden Informationen darüber auf, wo die .def-Datei und wo die "objects.txt" -Datei abgelegt werden soll, und mit dem Argument "-E __create_def". Sie können diese Informationen überprüfen, indem Sie ein CMake Visusal Studio-Projekt mit "set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)" erstellen und dann die Projektdatei ".vcxproj" auf DLL überprüfen.
Wenn Sie versuchen, ein Projekt ohne "set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)" oder mit "set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF)" zu kompilieren, werden Verknüpfungsfehler angezeigt, da Symbole nicht aus einer DLL exportiert werden.
Weitere Informationen hierzu finden Sie unter folgendem Link:
Grundlegendes zu benutzerdefinierten Erstellungsschritten und Erstellungsereignissen
2.3.2. Ohne CMake-Nutzung
Sie können einfach ein kleines Programm zum Parsen der OBJ-Datei selbst ohne CMake-Verwendung erstellen. Hovewer, ich muss zugeben, dass CMake ein sehr nützliches Programm ist, insbesondere für die plattformübergreifende Entwicklung.
type $(Platform)\$(Configuration)\mdb_symbols
) do @echo %% E)> $ (Plattform) \ $ (Konfiguration) \ lmdb.def
Ich habe ein kleines Programm geschrieben, um die Ausgabe von "dumpbin / linkermember" in der .lib-Datei zu analysieren. Ich habe mehr als 8.000 Funktionsreferenzen zum Exportieren aus einer DLL.
Das Problem bei einer DLL besteht darin, dass Sie die DLL ohne die exportierten Definitionen einmal verknüpfen müssen, um die .lib-Datei zu erstellen, und dann die .def generieren müssen. Dies bedeutet, dass Sie die DLL jetzt erneut mit der .def-Datei verknüpfen müssen, um sie tatsächlich zu erstellen Lassen Sie die Referenzen exportieren.
Das Arbeiten mit statischen Bibliotheken ist einfacher. Kompilieren Sie alle Ihre Quellen in statische Bibliotheken, führen Sie dummin aus, generieren Sie mit Ihrem kleinen Programm eine .def und verknüpfen Sie die Bibliotheken zu einer DLL, sobald die Exportnamen verfügbar sind.
Leider erlaubt mir meine Firma nicht, Ihnen die Quelle zu zeigen. Die damit verbundene Arbeit besteht darin, zu erkennen, welche "öffentlichen Symbole" in der Dump-Ausgabe in Ihrer Def-Datei nicht benötigt werden. Sie müssen viele dieser Referenzen, NULL_IMPORT_DESCRIPTOR, NULL_THUNK_DATA, __imp * usw., wegwerfen.
dumpbin.exe
die statische Bibliothek aus. Sie werden nicht haben NULL_IMPORT_DESCRIPTOR
, NULL_THUNK_DATA
, __imp*
etc. Erstellen Sie dann die DLL mit den gleichen Objekten und die neuen DEF - Datei.
Ich möchte eine DLL erstellen und automatisch alle Symbole exportieren, ohne überall __declspec (dllexport) hinzuzufügen und ohne .def-Dateien von Hand zu erstellen. Gibt es eine Möglichkeit, dies zu tun?
Dies ist eine späte Antwort, enthält jedoch die Details für Maks 'Antwort in Abschnitt (2). Es vermeidet auch Skripte und verwendet ein C ++ - Programm namens dump2def
. Der Quellcode für dump2def
ist unten.
Bei den folgenden Schritten wird davon ausgegangen, dass Sie über eine Visual Studio Developer-Eingabeaufforderung arbeiten , bei der es sich um ein Windows-Terminal handelt, auf dem vcvarsall.bat
ausgeführt wurde. Sie müssen die Build - Tools wie um sicherzustellen cl.exe
, lib.exe
, link.exe
und nmake.exe
sind auf dem Weg.
Weitere Informationen hierzu finden Sie unter folgendem Link:
Die folgenden Anweisungen verwenden:
static.lib
- statisches Bibliotheksarchiv (* .a Datei unter Linux)dynamic.dll
- dynamische Bibliothek (* .so-Datei unter Linux)import.lib
- Dynamische Bibliothek (Importbibliothek unter Windows)Beachten Sie auch, dass Clients, obwohl Sie alles aus der DLL exportieren, immer noch declspec(dllimport)
alle Symbole (Klassen, Funktionen und Daten) verwenden müssen, die sie verwenden. Siehe auch auf MSDN.
Nehmen Sie zuerst Ihre Objekte und erstellen Sie ein statisches Archiv:
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:$@
Führen Sie dumpbin.exe /LINKERMEMEBER
zweitens das Archiv aus, um eine *.dump
Datei zu erstellen :
dynamic.dump:
dumpbin /LINKERMEMBER static.lib > dynamic.dump
Drittens führen Sie dump2def.exe
die *.dump
Datei aus, um die *.def
Datei zu erstellen . Der Quellcode für dump2def.exe
ist unten.
dynamic.def: static.lib dynamic.dump
dump2def.exe dynamic.dump dynamic.def
Viertens erstellen Sie die 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
wird verwendet, um diese Warnung zu vermeiden. Es wird in diesem Fall erwartet:
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
Wenn das dynamic.dll
Rezept aufgerufen wird, werden auch eine dynamic.lib
Importdatei und eine dynamic.exp
Datei erstellt:
> cls && nmake /f test.nmake dynamic.dll
...
Creating library dynamic.lib and object dynamic.exp
Und:
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
Wenn Sie es hier zusammenkleben, sieht das Nmake-Makefile so aus. Es ist Teil einer echten Nmake-Datei :
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
Und hier ist der Quellcode für 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;
}
Danke @Maks für die ausführliche Antwort .
Unten ist ein Beispiel dafür, was ich im Pre-Link-Ereignis verwendet habe, um eine def-Datei aus obj zu generieren. Ich hoffe es wird für jemanden hilfreich sein.
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
Grundsätzlich habe ich nur eines der Objekte (mdb.obj) genommen und mdb_ * -Funktionen erfasst. Dann analysierte Ausgabe, um nur Namen unter Berücksichtigung der Anzahl der Leerzeichen für Einrückungen beizubehalten (eines nach dem Aufteilen in Token und eines im Echo. Ich weiß jedoch nicht, ob es wichtig ist).
Das Skript der realen Welt wird jedoch wahrscheinlich etwas komplexer.
Vielleicht findet jemand mein Python-Skript nützlich, um .dump in .def zu konvertieren.
import sys, os
functions = []
startPoint = False
# Exclude standard API like sprintf to avoid multiple definition link error
excluded_functions = [ 'sprintf', 'snprintf', 'sscanf', 'fprintf' ]
if len(sys.argv) < 2:
print('Usage: %s <Input .dump file> <Output .def file>.' % sys.argv[0])
print('Example: %s myStaticLib.dump exports.def' % sys.argv[0])
sys.exit(1)
print('%s: Processing %s to %s' % (sys.argv[0], sys.argv[1], sys.argv[2]))
fin = open(sys.argv[1], 'r')
lines = fin.readlines()
fin.close()
# Reading
for l in lines:
l_str = l.strip()
if (startPoint == True) and (l_str == 'Summary'): # end point
break
if (startPoint == False) and ("public symbols" in l_str):
startPoint = True
continue
if (startPoint == True) and l_str is not '':
funcName = l_str.split(' ')[-1]
if funcName not in excluded_functions:
functions.append(" " + funcName)
# Writing
fout = open(sys.argv[2], 'w')
fout.write('EXPORTS\n')
for f in functions:
fout.write('%s\n' % f)
fout.close()
Mit diesem Skript können Sie die .def-Datei für Ihre .lib in zwei Schritten abrufen:
dumpbin /LINKERMEMBER:1 myStaticLib.lib > myExports.dump
python dump2def.py myExports.dump myExports.def
__declspec(dllexport)
unter Windows,__attribute__ ((dllexport))
gcc und auf anderen Compilern leer sind. Dann-fvisibility=hidden
gcc weitergeben. Sie erhalten eine kleinere, übersichtlichere Symboltabelle und Fehler, die den Windows-Build beim Testen unter Linux beschädigen würden.