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?
71
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?
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)
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.__declspec(dllexport)
überall schreiben . Ebenso schwierig ist es, überall ein anderes Exportmakro hinzuzufügen.Kurze Antwort
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:
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
Lange Antwort
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:
Exportieren aus einer DLL
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:
Namensdekoration
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):
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:
/ SYMBOLE
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:
CMake_github
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.
quelle
type $(Platform)\$(Configuration)\mdb_symbols
) do @echo %% E)> $ (Plattform) \ $ (Konfiguration) \ lmdb.defIch 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.
quelle
dumpbin.exe
die statische Bibliothek aus. Sie werden nicht habenNULL_IMPORT_DESCRIPTOR
,NULL_THUNK_DATA
,__imp*
etc. Erstellen Sie dann die DLL mit den gleichen Objekten und die neuen DEF - Datei.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ürdump2def
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 sicherzustellencl.exe
,lib.exe
,link.exe
undnmake.exe
sind auf dem Weg.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ürdump2def.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:Wenn das
dynamic.dll
Rezept aufgerufen wird, werden auch einedynamic.lib
Importdatei und einedynamic.exp
Datei erstellt:Und:
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; }
quelle
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.
quelle
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
quelle
Nein, Sie benötigen ein Makro, das aufgelöst wird,
__declspec(dllexport)
wenn es in der CPP-Datei enthalten ist, die die exportierten Funktionen implementiert, und__declspec(dllimport)
andernfalls aufgelöst wird.quelle