Esportazione di funzioni da una DLL con dllexport


105

Vorrei un semplice esempio di esportazione di una funzione da una DLL di Windows C ++.

Vorrei vedere l'intestazione, il .cppfile e il .deffile (se assolutamente necessario).

Vorrei che il nome esportato non fosse decorato . Vorrei utilizzare la convenzione di chiamata più standard ( __stdcall?). Vorrei l'uso __declspec(dllexport)e non devo usare un .deffile.

Per esempio:

  //header
  extern "C"
  {
   __declspec(dllexport) int __stdcall foo(long bar);
  }

  //cpp
  int __stdcall foo(long bar)
  {
    return 0;
  }

Sto cercando di evitare che il linker aggiunga caratteri di sottolineatura e / o numeri (conteggi di byte?) Al nome.

Mi va bene non supportare dllimporte dllexportusare la stessa intestazione. Non voglio alcuna informazione sull'esportazione di metodi di classe C ++, solo funzioni globali in stile C.

AGGIORNARE

Non includendo la convenzione di chiamata (e usando extern "C") mi dà i nomi di esportazione come mi piace, ma cosa significa? Qualunque sia la convenzione di chiamata predefinita che sto ottenendo quale pinvoke (.NET), declare (vb6) e mi GetProcAddressaspetterei? (Immagino GetProcAddressche dipenda dal puntatore a funzione creato dal chiamante).

Voglio che questa DLL venga utilizzata senza un file di intestazione, quindi non ho davvero bisogno di molta fantasia #definesper rendere l'intestazione utilizzabile da un chiamante.

Sono d'accordo con la risposta che devo usare un *.deffile.


Potrei ricordare male, ma penso che: a) extern Crimuoverà la decorazione che descrive i tipi di parametro della funzione, ma non la decorazione che descrive la convenzione di chiamata della funzione; b) per rimuovere tutte le decorazioni è necessario specificare il nome (non decorato) in un file DEF.
ChrisW,

Questo è anche quello che stavo vedendo. Forse dovresti aggiungere questo come una risposta completa?
Aardvark

Risposte:


134

Se vuoi semplici esportazioni in C, usa un progetto C non C ++. Le DLL C ++ si basano sulla modifica dei nomi per tutti gli ismi C ++ (spazi dei nomi, ecc ...). Puoi compilare il tuo codice come C andando nelle impostazioni del tuo progetto in C / C ++ -> Avanzate, c'è un'opzione "Compila come" che corrisponde alle opzioni del compilatore / TP e / TC.

Se vuoi ancora usare C ++ per scrivere gli interni della tua libreria ma esportare alcune funzioni non modificate per l'uso al di fuori di C ++, vedi la seconda sezione di seguito.

Esportazione / importazione di librerie DLL in VC ++

Quello che vuoi veramente fare è definire una macro condizionale in un'intestazione che sarà inclusa in tutti i file sorgente nel tuo progetto DLL:

#ifdef LIBRARY_EXPORTS
#    define LIBRARY_API __declspec(dllexport)
#else
#    define LIBRARY_API __declspec(dllimport)
#endif

Quindi su una funzione che vuoi esportare usi LIBRARY_API:

LIBRARY_API int GetCoolInteger();

Nel progetto di compilazione della libreria, crea una definizione, LIBRARY_EXPORTSquesto farà sì che le tue funzioni vengano esportate per la build della tua DLL.

Poiché LIBRARY_EXPORTSnon verrà definito in un progetto che utilizza la DLL, quando quel progetto include il file di intestazione della libreria verranno invece importate tutte le funzioni.

Se la tua libreria deve essere multipiattaforma puoi definire LIBRARY_API come nulla quando non su Windows:

#ifdef _WIN32
#    ifdef LIBRARY_EXPORTS
#        define LIBRARY_API __declspec(dllexport)
#    else
#        define LIBRARY_API __declspec(dllimport)
#    endif
#elif
#    define LIBRARY_API
#endif

Quando si utilizza dllexport / dllimport non è necessario utilizzare file DEF, se si utilizzano file DEF non è necessario utilizzare dllexport / dllimport. I due metodi eseguono la stessa attività in modi diversi, credo che dllexport / dllimport sia il metodo consigliato tra i due.

Esportazione di funzioni non modificate da una DLL C ++ per LoadLibrary / PInvoke

Se hai bisogno di questo per usare LoadLibrary e GetProcAddress, o magari importare da un altro linguaggio (cioè PInvoke da .NET, o FFI in Python / R ecc.) Puoi usare extern "C"inline con il tuo dllexport per dire al compilatore C ++ di non alterare i nomi. E poiché stiamo usando GetProcAddress invece di dllimport non abbiamo bisogno di eseguire la danza ifdef dall'alto, solo un semplice dllexport:

Il codice:

#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)

EXTERN_DLL_EXPORT int getEngineVersion() {
  return 1;
}

EXTERN_DLL_EXPORT void registerPlugin(Kernel &K) {
  K.getGraphicsServer().addGraphicsDriver(
    auto_ptr<GraphicsServer::GraphicsDriver>(new OpenGLGraphicsDriver())
  );
}

Ed ecco come appaiono le esportazioni con Dumpbin / export:

  Dump of file opengl_plugin.dll

  File Type: DLL

  Section contains the following exports for opengl_plugin.dll

    00000000 characteristics
    49866068 time date stamp Sun Feb 01 19:54:32 2009
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 0001110E getEngineVersion = @ILT+265(_getEngineVersion)
          2    1 00011028 registerPlugin = @ILT+35(_registerPlugin)

Quindi questo codice funziona bene:

m_hDLL = ::LoadLibrary(T"opengl_plugin.dll");

m_pfnGetEngineVersion = reinterpret_cast<fnGetEngineVersion *>(
  ::GetProcAddress(m_hDLL, "getEngineVersion")
);
m_pfnRegisterPlugin = reinterpret_cast<fnRegisterPlugin *>(
  ::GetProcAddress(m_hDLL, "registerPlugin")
);

1
extern "C" sembrava rimuovere il nome dello stile c ++ alterando. L'intera importazione ed esportazione (che ho cercato di suggerire di non includere nella domanda) non è proprio ciò di cui sto chiedendo (ma sono buone informazioni). Ho pensato che avrebbe offuscato il problema.
Aardvark

L'unico motivo per cui posso pensare che ne avresti bisogno è per LoadLibrary e GetProcAddress ... Questo è già stato risolto, spiegherò nel mio corpo di risposta ...
joshperry

EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport)? È nell'SDK?
Aardvark

3
Non dimenticare di aggiungere il file di definizione del modulo nelle impostazioni del linker del progetto - semplicemente "aggiungere un elemento esistente al progetto" non è sufficiente!
Jimmy

1
L'ho usato per compilare una DLL con VS e poi chiamarla da R usando .C. Grande!
Juancentro

33

Per C ++:

Ho appena affrontato lo stesso problema e penso che valga la pena menzionare un problema che si presenta quando si usano entrambi __stdcall(o WINAPI) e extern "C" :

Come sai extern "C"rimuove la decorazione in modo che invece di:

__declspec(dllexport) int Test(void)                        --> dumpbin : ?Test@@YaHXZ

si ottiene un nome simbolo non decorato:

extern "C" __declspec(dllexport) int Test(void)             --> dumpbin : Test

Tuttavia la _stdcall(= macro WINAPI, che cambia la convenzione di chiamata) decora anche i nomi in modo che se li usiamo entrambi otteniamo:

   extern "C" __declspec(dllexport) int WINAPI Test(void)   --> dumpbin : _Test@0

e il vantaggio di extern "C"è perso perché il simbolo è decorato (con _ @bytes)

Si noti che ciò si verifica solo per l'architettura x86 perché la __stdcallconvenzione viene ignorata su x64 ( msdn : su architetture x64, per convenzione, gli argomenti vengono passati nei registri quando possibile e gli argomenti successivi vengono passati nello stack ).

Questo è particolarmente complicato se stai prendendo di mira sia le piattaforme x86 che x64.


Due soluzioni

  1. Usa un file di definizione. Ma questo ti costringe a mantenere lo stato del file def.

  2. il modo più semplice: definire la macro (vedere msdn ):

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

e quindi includere il seguente pragma nel corpo della funzione:

#pragma EXPORT

Esempio completo:

 int WINAPI Test(void)
{
    #pragma EXPORT
    return 1;
}

Questo esporterà la funzione non decorata per entrambi i target x86 e x64, preservando la __stdcallconvenzione per x86. In questo caso __declspec(dllexport) non è richiesto.


5
Grazie per questo importante suggerimento. Mi chiedevo già perché la mia DLL a 64 bit è diversa da quella a 32 bit. Trovo la tua risposta molto più utile di quella accettata come risposta.
Elmue

1
Mi piace molto questo approccio. La mia unica raccomandazione sarebbe quella di rinominare la macro in EXPORT_FUNCTION perché la __FUNCTION__macro funziona solo nelle funzioni.
Luis

3

Ho avuto esattamente lo stesso problema, la mia soluzione era utilizzare il file di definizione del modulo (.def) invece di __declspec(dllexport)definire le esportazioni ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ). Non ho idea del perché funzioni, ma funziona


Nota a chiunque altro in esecuzione in questo: utilizzando un .defesportazioni modulo di file fa il lavoro, ma a scapito di essere in grado di fornire externle definizioni nel file di intestazione per esempio i dati-in globali questo caso, è necessario fornire il externmanualmente definizione interni usi di quei dati. (Sì, ci sono momenti in cui ne hai bisogno.) È meglio sia in generale che soprattutto per il codice multipiattaforma da usare semplicemente __declspec()con una macro in modo da poter distribuire i dati normalmente.
Chris Krycho

2
La ragione è probabilmente perché se si sta utilizzando __stdcall, quindi __declspec(dllexport)si non rimuovere le decorazioni. Aggiungendo però la funzione a un .deftestamento.
Björn Lindqvist

1
@ BjörnLindqvist +1, nota che è solo il caso di x86. Vedi la mia risposta.
Malick

-1

Penso che _naked possa ottenere ciò che desideri, ma impedisce anche al compilatore di generare il codice di gestione dello stack per la funzione. extern "C" causa la decorazione del nome in stile C. Rimuovilo e quello dovrebbe sbarazzarti delle tue _. Il linker non aggiunge i trattini bassi, il compilatore lo fa. stdcall fa sì che la dimensione dello stack di argomenti venga aggiunta.

Per ulteriori informazioni, vedere: http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

La domanda più grande è perché vuoi farlo? Cosa c'è che non va con i nomi alterati?


I nomi alterati sono brutti quando chiamati usano LoadLibrary / GetProcAddress o altri metodi che non si basano sull'intestazione ac / c ++.
Aardvark

4
Ciò sarebbe inutile: si desidera rimuovere il codice di gestione dello stack generato dal compilatore solo in circostanze molto specifiche. (Usare solo __cdecl sarebbe un modo meno dannoso per perdere le decorazioni - per impostazione predefinita __declspec (dllexport) non sembra includere il solito prefisso _ con i metodi __cdecl.)
Ian Griffiths

Non stavo davvero dicendo che sarebbe stato utile, da qui i miei avvertimenti sugli altri effetti e mi chiedevo perché volesse farlo.
Rob K
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.