2011-06-28 10 views
62

Actualmente estoy desarrollando una biblioteca C++ para Windows que se distribuirá como una DLL. Mi objetivo es maximizar la interoperabilidad binaria; más precisamente, las funciones en mi DLL deben ser utilizables desde código compilado con múltiples versiones de MSVC++ y MinGW sin tener que recompilar la DLL. Sin embargo, estoy confundido acerca de qué convención de llamadas es la mejor, cdecl o stdcall.__cdecl o __stdcall en Windows?

A veces escucho declaraciones como "la convención de llamadas C es la única garantizada para ser la misma en todos los compiladores", que contrasta con declaraciones como "There are some variations in the interpretation of cdecl, particularly in how to return values". Esto no parece detener a ciertos desarrolladores de bibliotecas (como libsndfile) para usar la convención de llamadas C en las DLL que distribuyen, sin ningún problema visible.

Por otro lado, el convenio de llamadas stdcall parece estar bien definido. Según lo que me han contado, todos los compiladores de Windows básicamente deben seguirlo porque es la convención utilizada para Win32 y COM. Esto se basa en la suposición de que un compilador de Windows sin compatibilidad Win32/COM no sería muy útil. Una gran cantidad de fragmentos de código publicado en los foros de declarar funciona como stdcall pero me parece que no puede encontrar uno solo puesto que explica claramente por qué .

Hay demasiada información contradictoria, y cada búsqueda que ejecuto me da respuestas diferentes que realmente no me ayudan a decidir entre los dos. Estoy buscando una explicación clara, detallada y argumentada sobre por qué debería elegir una sobre la otra (o por qué las dos son equivalentes).

Tenga en cuenta que esta pregunta no sólo se aplica a las funciones "clásicas", sino también para las llamadas a funciones miembro virtuales, ya que la mayoría código de cliente se conectará con mi DLL a través de "interfaces", clases virtuales puras (siguiendo las pautas descritas por ejemplo here y there)

+3

"Hay algunas variaciones en la interpretación de cdecl, particularmente en cómo devolver valores" - esto significa aproximadamente que diferentes SO que usan cdecl en x86 pueden usar diferentes variantes de cdecl, que son diferentes ABI que ambos llaman "cdecl" . Windows elimina las variaciones, cualquier implementación que se ejecute en Windows y no respete las opciones de Windows no podrá llamar a las funciones de cdecl de Windows, así que como dices con stdcall, es inútil para la programación de Windows, y hay algunas C estándar funciones sería difícil de implementar. –

Respuesta

63

Acabo de hacer algunas pruebas en el mundo real (compilando DLL y aplicaciones con MSVC++ y MinGW, y luego mezclándolas). Como parece, obtuve mejores resultados con la convención de llamadas cdecl.

Más específicamente: el problema con stdcall es que MSVC++ desordena los nombres en la tabla de exportación de DLL, incluso cuando usa extern "C". Por ejemplo foo se convierte en [email protected]. Esto solo ocurre cuando se usa __declspec(dllexport), no cuando se usa un archivo DEF; sin embargo, los archivos DEF son una molestia de mantenimiento en mi opinión, y no quiero usarlos.

El MSVC++ nombre mangling plantea dos problemas:

  • Usando GetProcAddress en la DLL se vuelve un poco más complicado;
  • MinGW de forma predeterminada no antecede una descalificación a los nombres decorados (por ejemplo, MinGW utilizará [email protected] en lugar de [email protected]), lo que complica el enlace. Además, introduce el riesgo de que aparezcan "versiones sin guiones bajos" de DLL y aplicaciones emergentes que no son compatibles con las "versiones de subrayado".

que he probado la convención cdecl: la interoperabilidad entre MSVC++ y MinGW funciona perfectamente, fuera de la caja, y los nombres permanecen sin decorar en la tabla de exportación DLL. Incluso funciona para métodos virtuales.

Por estas razones, cdecl es un claro ganador para mí.

14

La mayor diferencia en las dos convenciones de llamadas es que "_ _cdecl" coloca la carga de equilibrar la pila después de una llamada de función en la persona que llama, lo que permite funciones con cantidades variables de argumentos. La convención "__stdcall" es de naturaleza "más simple", pero menos flexible a este respecto.

Además, creo que los lenguajes administrados utilizan la convención stdcall por defecto, por lo que cualquier persona que use P/Invoke debería indicar explícitamente la convención de llamadas si usa cdecl.

Por lo tanto, si todas sus firmas de funciones van a estar definidas estáticamente, probablemente me inclinaría hacia stdcall, si no cdecl.

6

En términos de seguridad, la convención __cdecl es "más segura" porque es la persona que llama la que debe desasignar la pila. Lo que puede suceder en una biblioteca __stdcall es que el desarrollador puede haber olvidado desasignar la pila correctamente, o un atacante podría inyectar algún código al corromper la pila de la DLL (por ejemplo, mediante el enganche API) que no es verificado por la persona que llama. No tengo ningún ejemplo de seguridad de CVS que demuestre que mi intuición es correcta.

Cuestiones relacionadas