Esporta tutti i simboli durante la creazione di una DLL

Con VS2005, voglio creare una DLL ed esportare automaticamente tutti i simboli senza aggiungere __declspec (dllexport) ovunque e senza file .def creati a mano. C’è un modo per farlo?

Si può fare…

Il modo in cui lo facciamo qui è quello di utilizzare l’opzione / DEF del linker per passare un “file di definizione del modulo” contenente un elenco delle nostre esportazioni. Vedo dalla tua domanda che conosci questi file. Tuttavia, non lo facciamo a mano. L’elenco delle esportazioni è creato dal comando dumpbin / LINKERMEMBER e modifica l’output tramite un semplice script nel formato di un file di definizione del modulo.

È molto lavoro da configurare, ma ci consente di compilare codice creato senza dichiarazioni dllexport per Unix su Windows.

Risposta breve

Puoi farlo con l’aiuto della nuova versione di CMake (qualsiasi versione cmake-3.3.20150721-g9cd2f-win32-x86.exe o successiva).

Attualmente è nel ramo dev. Successivamente, la funzionalità verrà aggiunta nella versione di rilascio di cmake-3.4.

Collegamento allo strumento cmake:

cmake_dev

Link a un articolo che descrive la tecnica:

Crea DLL su Windows senza declspec () usando la nuova funzionalità di esportazione di CMake tutte

Link a un progetto di esempio:

cmake_windows_export_all_symbols


Risposta lunga

Attenzione: tutte le informazioni di seguito sono correlate al compilatore MSVC o Visual Studio.

Se usi altri compilatori come gcc su Linux o il compilatore gcc MinGW su Windows non hai errori di collegamento dovuti a simboli non esportati, perché il compilatore gcc esporta tutti i simboli in una libreria dynamic (dll) per impostazione predefinita invece dei compilatori MSVC o Intel Windows .

In Windows devi esportare esplicitamente il simbolo da una dll.

Maggiori informazioni a riguardo sono fornite dai link:

Esportazione da una DLL

Istruzioni: esportare le classi C ++ da una DLL

Quindi se vuoi esportare tutti i simboli da dll con MSVC (compilatore di Visual Studio) hai due opzioni:

  • Utilizzare la parola chiave __declspec (dllexport) nella definizione della class / funzione.
  • Creare un file di definizione del modulo (.def) e utilizzare il file .def durante la creazione della DLL.

1. Utilizzare la parola chiave __declspec (dllexport) nella definizione della class / funzione


1.1. Aggiungi macro “__declspec (dllexport) / __declspec (dllimport)” a una class o metodo che desideri utilizzare. Quindi se vuoi esportare tutte le classi devi aggiungere questi macro a tutti loro

Maggiori informazioni a riguardo sono fornite dal link:

Esportare da una DLL usando __declspec (dllexport)

Esempio di utilizzo (sostituire “Progetto” con il nome del progetto reale):

 // 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 

Quindi aggiungi “PROJECTAPI” a tutte le classi. Definisci “USEPROJECTLIBRARY” solo se vuoi esportare / importare simboli da dll. Definisci “PROJECTLIBRARY_EXPORTS” per la dll.

Esempio di esportazione di class:

 #include "ProjectExport.h" namespace hello { class PROJECTAPI Hello {} } 

Esempio di esportazione di funzione:

 #include "ProjectExport.h" PROJECTAPI void HelloWorld(); 

Attenzione: non dimenticare di includere il file “ProjectExport.h”.


1.2. Esporta come funzioni C. Se si utilizza il compilatore C ++ per il codice di compilazione è scritto su C, è ansible aggiungere extern “C” di fronte a una funzione per eliminare il nome mangling

Maggiori informazioni sulla manipolazione dei nomi in C ++ sono fornite dal link:

Nome Decorazione

Esempio di utilizzo:

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

Maggiori informazioni a riguardo sono fornite dal link:

Esportazione di funzioni C ++ per l’uso in file eseguibili in linguaggio C


2. Creare un file di definizione del modulo (.def) e utilizzare il file .def durante la creazione della DLL

Maggiori informazioni a riguardo sono fornite dal link:

Esportazione da una DLL utilizzando i file DEF

Inoltre descrivo tre approcci su come creare il file .def.


2.1. Esportare funzioni C.

In questo caso puoi semplicemente aggiungere dichiarazioni di funzioni nel file .def a mano.

Esempio di utilizzo:

 extern "C" void HelloWorld(); 

Esempio di file .def (convenzione di denominazione __cdecl):

 EXPORTS _HelloWorld 

2.2. Esporta simboli dalla libreria statica

Ho provato l’approccio suggerito da “user72260”.

Egli ha detto:

  • In primo luogo, è ansible creare una libreria statica.
  • Quindi utilizzare “dumpbin / LINKERMEMBER” per esportare tutti i simboli dalla libreria statica.
  • Analizza l’output.
  • Metti tutti i risultati in un file .def.
  • Crea DLL con il file .def.

Ho usato questo approccio, ma non è molto comodo creare sempre due build (uno come statico e l’altro come libreria dynamic). Tuttavia, devo ammettere, questo approccio funziona davvero.


2.3. Esporta simboli da file .obj o con l’aiuto di CMake


2.3.1. Con l’utilizzo di CMake

Avviso importante: non hai bisogno di esportare macro in classi o funzioni!

Avviso importante: non è ansible utilizzare / GL ( Whole Program Optimization ) quando si utilizza questo approccio!

  • Creare un progetto CMake basato sul file “CMakeLists.txt”.
  • Aggiungi la seguente riga al file “CMakeLists.txt”: set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
  • Quindi creare il progetto Visual Studio con l’aiuto di “CMake (cmake-gui)”.
  • Compila il progetto.

Esempio di utilizzo:

Cartella root

CMakeLists.txt (cartella principale)

 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 (cartella principale)

 #include "foo.h" int main() { HelloWorld(); return 0; } 

Foo folder (cartella root / cartella Foo)

CMakeLists.txt (cartella Foo)

 project(foo) set(SOURCE_LIB foo.cpp) add_library(foo SHARED ${SOURCE_LIB}) 

foo.h (cartella Foo)

 void HelloWorld(); 

foo.cpp (cartella Foo)

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

Collega nuovamente al progetto di esempio:

cmake_windows_export_all_symbols

CMake utilizza il diverso dall'approccio "2.2 Esporta simboli dalla libreria statica".

Fa quanto segue:

1) Creare il file "objects.txt" nella directory di build con le informazioni dei file .obj utilizzati in una DLL.

2) Compilare la DLL, ovvero creare i file .obj.

3) In base alle informazioni sul file "objects.txt" estrai tutti i simboli dal file .obj.

Esempio di utilizzo:

 DUMPBIN /SYMBOLS example.obj > log.txt 

Maggiori informazioni a riguardo sono fornite dal link:

/ SIMBOLI

4) Parse estratta dalle informazioni del file .obj.

A mio parere, utilizzerei la convezione di chiamata, ad esempio "__cdecl / __ fastcall", il campo "SECTx / UNDEF" (la terza colonna), il campo "External / Static" (la quinta colonna), "??", "? " informazioni per l'analisi di un file .obj.

Non so come esattamente CMake analizza un file .obj. Tuttavia, CMake è open source, quindi potresti scoprire se è interessato a te.

Collegamento al progetto CMake:

CMake_github

5) Metti tutti i simboli esportati in un file .def.

6) Collega una DLL con l'uso di un file .def creato.

Passaggi 4) -5), ovvero file parse .obj e creare un file .def prima di colbind e utilizzare il file .def che CMake fa con l'aiuto di "Evento pre-collegamento". Mentre si triggers l'evento "Pre-Link", puoi chiamare qualsiasi programma tu voglia. Quindi, in caso di "utilizzo di CMake", "evento pre-link" chiama CMake con le seguenti informazioni su dove inserire il file .def e dove il file "objects.txt" e con l'argomento "-E __create_def". È ansible verificare queste informazioni creando il progetto CMake Visusal Studio con "set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)" e quindi controllare il file di progetto ".vcxproj" per dll.

Se si tenta di compilare un progetto senza "set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)" o con "set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF)" si otterranno errori di collegamento, a causa del fatto che i simboli non vengono esportati da una dll.

Maggiori informazioni a riguardo sono fornite dal link:

Capire passi di Build personalizzati e creare eventi


2.3.2. Senza l'utilizzo di CMake

Semplicemente potresti creare un piccolo programma per analizzare il file .obj da solo senza l'uso di CMake. Hovewer, devo ammettere che CMake è un programma molto utile specialmente per lo sviluppo multipiattaforma.

Ho scritto un piccolo programma per analizzare l’output di “dumpbin / linkermember” nel file .lib. Ho più di 8.000 riferimenti alle funzioni da esportare da una DLL.

Il problema con il farlo su una DLL è che devi colbind la DLL senza le definizioni esportate una volta per creare il file .lib, quindi generare il .def che significa che ora devi ricolbind la DLL di nuovo con il file .def in realtà avere i riferimenti esportati.

Lavorare con le librerie statiche è più facile. Compila tutte le tue fonti in librerie statiche, esegui dumbin, genera un .def con il tuo piccolo programma, quindi collega le librerie insieme in una DLL ora che i nomi di esportazione sono disponibili.

Sfortunatamente la mia compagnia non mi permetterà di mostrarti la fonte. Il lavoro in questione è riconoscere quali “simboli pubblici” nell’output di dump non sono necessari nel file def. Devi buttare via molti riferimenti, NULL_IMPORT_DESCRIPTOR, NULL_THUNK_DATA, __imp *, ecc.

Grazie a @Maks per la risposta dettagliata .

Di seguito è riportato un esempio di ciò che ho usato nell’evento Pre-Link per generare file def da obj. Spero che sarà utile per qualcuno.

 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 

Fondamentalmente ho appena preso uno degli oggetti (mdb.obj) e le funzioni mdb_ * inutilizzate. Quindi ho analizzato l’output per mantenere solo i nomi tenendo conto della quantità di spazi per l’indentazione (uno dopo la suddivisione in token e l’altro in eco. Non so se è importante).

Lo script del mondo reale probabilmente sarà più complesso.

No, avrai bisogno di una macro che si risolva in __declspec(dllexport) quando è inclusa nel file .cpp che implementa le funzioni esportate e si risolve in __declspec(dllimport) contrario.