c++ windows dll calling-convention binary-compatibility

c++ - __cdecl o__stdcall en Windows?



dll calling-convention (3)

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 .

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 afirmaciones como " Hay algunas variaciones en la interpretación de cdecl , particularmente en cómo devolver valores ". 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, la convención de llamada stdcall parece estar bien definida. 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 publicados en foros declaran funciones como stdcall pero 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 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 solo se aplica a las funciones "clásicas", sino también a las llamadas a funciones de miembros virtuales, ya que la mayoría de los códigos de cliente interactúan con mi DLL a través de "interfaces", clases virtuales puras (siguiendo los patrones descritos, por ejemplo, here y there ).


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 ++ cambia los nombres en la tabla de exportación de DLL, incluso cuando se usa extern "C" . Por ejemplo, foo convierte en _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.

El cambio de nombre de MSVC ++ plantea dos problemas:

  • Usar GetProcAddress en el DLL se vuelve un poco más complicado;
  • MinGW de forma predeterminada no antecede una descalificación a los nombres decorados (por ejemplo, MinGW usará foo@4 lugar de _foo@4 ), 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".

cdecl convención cdecl : la interoperabilidad entre MSVC ++ y MinGW funciona perfectamente, de fábrica y los nombres permanecen sin decoración en la tabla de exportación de DLL. Incluso funciona para métodos virtuales.

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


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 de __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 la persona que llama no verifica. No tengo ningún ejemplo de seguridad de CVS que demuestre que mi intuición es correcta.


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 usan la convención stdcall por defecto, por lo que cualquier persona que use P / Invoke en él tendría que declarar explícitamente la convención de llamadas si usa cdecl.

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