Exportar funciones desde una DLL con dllexport

105

Me gustaría un ejemplo simple de cómo exportar una función desde una DLL de Windows C ++.

Me gustaría ver el encabezado, el .cpparchivo y el .defarchivo (si es absolutamente necesario).

Me gustaría que el nombre exportado no estuviera decorado . Me gustaría usar la convención de llamadas más estándar ( __stdcall?). Me gustaría el uso __declspec(dllexport)y no tengo que usar un .defarchivo.

Por ejemplo:

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

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

Estoy tratando de evitar que el enlazador agregue guiones bajos y / o números (¿recuentos de bytes?) Al nombre.

Estoy de acuerdo con no admitir dllimporty dllexportusar el mismo encabezado. No quiero ninguna información sobre la exportación de métodos de clase C ++, solo funciones globales de estilo C.

ACTUALIZAR

Sin incluir la convención de llamada (y el uso extern "C") me da los nombres de exportación que me gustan, pero ¿qué significa eso? ¿Es cualquier convención de llamada predeterminada que obtengo lo que pinvoke (.NET), declare (vb6) y GetProcAddressesperaría? (Supongo GetProcAddressque dependería del puntero de función que creó la persona que llama).

Quiero que esta DLL se use sin un archivo de encabezado, por lo que realmente no necesito tanta fantasía #definespara hacer que el encabezado pueda ser utilizado por una persona que llama.

Estoy de acuerdo con una respuesta que es que tengo que usar un *.defarchivo.

Cerdo hormiguero
fuente
Puede que esté recordando mal, pero creo que: a) extern Celiminará la decoración que describe los tipos de parámetros de la función, pero no la decoración que describe la convención de llamada de la función; b) para eliminar toda la decoración, debe especificar el nombre (sin decoración) en un archivo DEF.
ChrisW
Esto es lo que también estaba viendo. ¿Quizás debería agregar esto como una respuesta completa?
Aardvark

Respuestas:

134

Si desea exportaciones simples de C, use un proyecto de C, no C ++. Las DLL de C ++ se basan en la manipulación de nombres para todos los ismos de C ++ (espacios de nombres, etc.). Puede compilar su código como C yendo a la configuración de su proyecto en C / C ++ -> Avanzado, hay una opción "Compilar como" que corresponde a los modificadores del compilador / TP y / TC.

Si aún desea usar C ++ para escribir los componentes internos de su lib pero exportar algunas funciones sin alterar para usarlas fuera de C ++, consulte la segunda sección a continuación.

Exportación / importación de bibliotecas DLL en VC ++

Lo que realmente quiere hacer es definir una macro condicional en un encabezado que se incluirá en todos los archivos fuente en su proyecto DLL:

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

Luego, en una función que desea exportar, usa LIBRARY_API:

LIBRARY_API int GetCoolInteger();

En el proyecto de construcción de su biblioteca, cree una definición, LIBRARY_EXPORTSesto hará que sus funciones se exporten para su construcción DLL.

Dado LIBRARY_EXPORTSque no se definirá en un proyecto que consume la DLL, cuando ese proyecto incluya el archivo de encabezado de su biblioteca, todas las funciones se importarán en su lugar.

Si su biblioteca va a ser multiplataforma, puede definir LIBRARY_API como nada cuando no está en Windows:

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

Cuando utilice dllexport / dllimport, no es necesario utilizar archivos DEF, si utiliza archivos DEF, no es necesario utilizar dllexport / dllimport. Los dos métodos realizan la misma tarea de diferentes maneras, creo que dllexport / dllimport es el método recomendado de los dos.

Exportación de funciones no manipuladas desde una DLL de C ++ para LoadLibrary / PInvoke

Si necesita esto para usar LoadLibrary y GetProcAddress, o tal vez para importar desde otro idioma (es decir, PInvoke desde .NET, o FFI en Python / R, etc.) puede usar en extern "C"línea con su dllexport para decirle al compilador C ++ que no altere los nombres. Y dado que estamos usando GetProcAddress en lugar de dllimport, no necesitamos hacer el baile ifdef desde arriba, solo un simple dllexport:

El código:

#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())
  );
}

Y así es como se ven las exportaciones 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)

Entonces este código funciona bien:

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")
);
Joshperry
fuente
1
extern "C" parecía eliminar la alteración de nombres de estilo de C ++. Todo el asunto de la importación frente a la exportación (que intenté sugerir que no se incluyera en la pregunta) no es realmente lo que estoy preguntando (pero es una buena información). Pensé que nublaría el problema.
Aardvark
La única razón por la que puedo pensar que lo necesitaría es para LoadLibrary y GetProcAddress ... Esto ya está resuelto, lo expondré en mi cuerpo de respuesta ...
joshperry
¿EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport)? ¿Está eso en el SDK?
Aardvark
3
No olvide agregar el archivo de definición del módulo en la configuración del vinculador del proyecto; ¡simplemente "agregar un elemento existente al proyecto" no es suficiente!
Jimmy
1
Usé esto para compilar una DLL con VS y luego llamarla desde R usando .C. ¡Excelente!
Juancentro
33

Para C ++:

Acabo de enfrentar el mismo problema y creo que vale la pena mencionar que surge un problema cuando uno usa tanto __stdcall(o WINAPI) como extern "C" :

Como sabéis extern "C"quita la decoración para que en lugar de:

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

obtienes un nombre de símbolo sin decorar:

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

Sin embargo la _stdcall(= macro WINAPI, que cambia la convención de llamada) también decora nombres de modo que si usamos ambos obtenemos:

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

y extern "C"se pierde el beneficio de porque el símbolo está decorado (con _ @bytes)

Tenga en cuenta que esto solo ocurre para la arquitectura x86 porque la __stdcallconvención se ignora en x64 ( msdn : en arquitecturas x64, por convención, los argumentos se pasan en los registros cuando es posible, y los argumentos subsiguientes se pasan a la pila ).

Esto es particularmente complicado si su objetivo es plataformas x86 y x64.


Dos soluciones

  1. Utilice un archivo de definición. Pero esto te obliga a mantener el estado del archivo def.

  2. la forma más sencilla: definir la macro (ver msdn ):

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

y luego incluir el siguiente pragma en el cuerpo de la función:

#pragma EXPORT

Ejemplo completo:

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

Esto exportará la función sin decorar para los destinos x86 y x64 mientras se conserva la __stdcallconvención para x86. En este caso __declspec(dllexport) no es necesario.

Malick
fuente
5
Gracias por esta importante sugerencia. Ya me preguntaba por qué mi DLL de 64 bits es diferente de la de 32 bits. Encuentro su respuesta mucho más útil que la aceptada como respuesta.
Elmue
1
Realmente me gusta este enfoque. Mi única recomendación sería cambiar el nombre de la macro a EXPORT_FUNCTION porque la __FUNCTION__macro solo funciona en funciones.
Luis
3

Tuve exactamente el mismo problema, mi solución fue usar el archivo de definición del módulo (.def) en lugar de __declspec(dllexport)definir las exportaciones ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ). No tengo idea de por qué esto funciona, pero lo hace

Rytis I
fuente
Nota a cualquier otra persona corriendo en esto: el uso de un .defmódulo exporta archivo hace el trabajo, pero a expensas de ser capaz de suministrar externlas definiciones en el archivo de cabecera para, por ejemplo los datos en globales cuyo caso, usted tiene que suministrar la externdefinición manualmente en internos usos de esos datos. (Sí, hay ocasiones en las que lo necesita). Es mejor, tanto en general como especialmente para el código multiplataforma, simplemente usarlo __declspec()con una macro para que pueda distribuir los datos normalmente.
Chris Krycho
2
La razón es probablemente porque si está utilizando __stdcall, entonces __declspec(dllexport)será no quitar las decoraciones. .defSin embargo, agregar la función a un testamento.
Björn Lindqvist
1
@ BjörnLindqvist +1, tenga en cuenta que solo es el caso de x86. Mira mi respuesta.
Malick
-1

Creo que _naked podría obtener lo que desea, pero también evita que el compilador genere el código de administración de pila para la función. extern "C" provoca la decoración del nombre del estilo C. Elimine eso y eso debería deshacerse de su _. El enlazador no agrega los guiones bajos, el compilador lo hace. stdcall hace que se agregue el tamaño de la pila de argumentos.

Para obtener más información, consulte: http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

La pregunta más importante es ¿por qué quieres hacer eso? ¿Qué pasa con los nombres destrozados?

Rob K
fuente
Los nombres destrozados son feos cuando se los llama use LoadLibrary / GetProcAddress u otros métodos que no dependen de tener un encabezado ac / c ++.
Aardvark
4
Esto no sería útil: solo desea eliminar el código de administración de pila generado por el compilador en circunstancias muy especializadas. (Solo usar __cdecl sería una forma menos dañina de perder las decoraciones; de forma predeterminada, __declspec (dllexport) no parece incluir el prefijo _ habitual con los métodos __cdecl.)
Ian Griffiths
Realmente no estaba diciendo que sería útil, de ahí mis advertencias sobre los otros efectos y cuestionarme por qué él quería hacerlo.
Rob K