¿__cdecl o __stdcall en Windows?

84

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 poder usarse a partir de 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 mejor cdeclo stdcall.

A veces escucho declaraciones como "la convención de llamada de C es la única garantizada para ser la misma en todos los compiladores", lo que contrasta con declaraciones como " Hay algunas variaciones en la interpretación de cdecl, particularmente en cómo devolver valores ". Esto no parece impedir que ciertos desarrolladores de bibliotecas (como libsndfile ) utilicen la convención de llamadas de C en las DLL que distribuyen, sin ningún problema visible.

Por otro lado, la stdcallconvención de llamada parece estar bien definida. Por lo que me han dicho, básicamente se requiere que todos los compiladores de Windows lo sigan 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 con Win32 / COM no sería muy útil. Muchos fragmentos de código publicados en foros declaran funciones como, stdcallpero parece que no puedo encontrar una sola publicación que explique claramente por qué .

Hay demasiada información contradictoria y cada búsqueda que hago me da diferentes respuestas, lo que realmente no me ayuda a decidir entre los dos. Estoy buscando una explicación clara, detallada y argumentada de por qué debería elegir uno sobre el otro (o por qué los dos son equivalentes).

Tenga en cuenta que esta pregunta no solo se aplica a las funciones "clásicas", sino también a las llamadas de funciones de miembros virtuales, ya que la mayoría del código de cliente interactuará con mi DLL a través de "interfaces", clases virtuales puras (siguiendo los patrones descritos, por ejemplo, aquí y allá ).

Etienne Dechamps
fuente
4
"Hay algunas variaciones en la interpretación de cdecl, particularmente en cómo devolver valores" - esto significa aproximadamente que diferentes sistemas operativos que usan cdecl en x86 pueden usar diferentes variantes de cdecl, es decir, ABI diferentes que ambos llaman "cdecl". Windows fija las variaciones, cualquier implementación que se ejecute en Windows y no respete las opciones de Windows no podrá llamar a las funciones cdecl de Windows, así que como dices con stdcall, es inútil para la programación de Windows, y hay algunos C estándar funciones que sería difícil de implementar.
Steve Jessop
1
La convención de llamada __stdcall se utiliza para llamar a funciones de API de Win32. docs.microsoft.com/en-us/cpp/cpp/stdcall?view=vs-2017
Zanna

Respuestas:

79

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

Más específicamente: el problema stdcalles que MSVC ++ modifica los nombres en la tabla de exportación de DLL, incluso cuando se usa extern "C". Por ejemplo se fooconvierte _foo@4. 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.

La alteración de nombres de MSVC ++ plantea dos problemas:

  • Usar GetProcAddressen la DLL se vuelve un poco más complicado;
  • MinGW por defecto no antepone un no-puntaje a los nombres decorados (por ejemplo, MinGW usará en foo@4lugar de _foo@4), lo que complica el enlace. Además, introduce el riesgo de que aparezcan "versiones sin subrayado" de DLL y aplicaciones que son incompatibles con las "versiones de subrayado".

Probé la cdeclconvención: la interoperabilidad entre MSVC ++ y MinGW funciona perfectamente, lista para usar y los nombres permanecen sin decorar en la tabla de exportación de DLL. Incluso funciona con métodos virtuales.

Por estos motivos, cdecles un claro ganador para mí.

Etienne Dechamps
fuente
En particular, esto no se aplica a las DLL de 64 bits, porque __stdcall y __cdecl generalmente se ignoran, por lo que no se produce alteración de nombres en las funciones C exportadas.
jtbr
@jtbr ¿Qué tal las funciones C ++ exportadas (es decir, no extern "C")?
Ela782
@ Ela782 C ++ siempre tendrá algún cambio de nombre, para admitir la sobrecarga de funciones. Pero esto no está estandarizado y, por lo tanto, puede variar entre compiladores.
jtbr
@jtbr Lo siento, no quise decir el nombre destrozado. Me refiero a si __stdcall y __cdecl también se ignoran en las DLL de 64 bits con funciones C ++ exportadas.
Ela782
1
@ Ela782 Sí; Creo que __stdcall y __cdecl se ignoran en todas las compilaciones x86-64. Ver wikipedia .
jtbr
20

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

Además, creo que los lenguajes administrados usan la convención stdcall de forma predeterminada, por lo que cualquiera que use P / Invoke tendría que indicar explícitamente la convención de llamada si va con cdecl.

Por lo tanto, si todas las firmas de sus funciones se van a definir estáticamente, probablemente me inclinaría por stdcall, si no cdecl.

Brandon Moretz
fuente
11

En términos de seguridad, la __cdeclconvención es "más segura" porque es la persona que llama la que necesita desasignar la pila. Lo que puede suceder en una __stdcallbiblioteca es que el desarrollador podría haber olvidado desasignar la pila correctamente, o un atacante podría inyectar algún código corrompiendo la pila de la DLL (por ejemplo, mediante el enlace de API) que luego no es verificado por la persona que llama. No tengo ningún ejemplo de seguridad CVS que muestre que mi intuición es correcta.

usuario2471214
fuente