_Cdecl or _Stdcall on Windows

__cdecl or __stdcall on Windows?

I just did some real-world testing (compiling DLLs and applications with MSVC++ and MinGW, then mixing them). As it appears, I had better results with the cdecl calling convention.

More specifically: the problem with stdcall is that MSVC++ mangles names in the DLL export table, even when using extern "C". For example foo becomes _foo@4. This only happens when using __declspec(dllexport), not when using a DEF file; however, DEF files are a maintenance hassle in my opinion, and I don't want to use them.

The MSVC++ name mangling poses two problems:

  • Using GetProcAddress on the DLL becomes slightly more complicated;
  • MinGW by default doesn't prepend an undescore to the decorated names (e.g. MinGW will use foo@4 instead of _foo@4), which complicates linking. Also, it introduces the risk of seeing "non-underscore versions" of DLLs and applications pop up in the wild which are incompatible with the "underscore versions".

I've tried the cdecl convention: interoperability between MSVC++ and MinGW works perfectly, out-of-the-box, and names stay undecorated in the DLL export table. It even works for virtual methods.

For these reasons, cdecl is a clear winner for me.

the use of __stdcall and __cdecl in Windows CE DLLs

In Windows CE, __stdcall is a macro that resolves to __cdecl. That is, they designate the same calling conventions. Your problem lies elsewhere. Use the debugger to investigate.

A calling convention might be specified because it needs to be specified for the desktop version of the DLL, and it's easier to keep the source code the same for both targets than to clutter it with conditional compilation that has no real effect.

Does every function in a Windows GUI application need to use stdcall?

WinMain is declared with __stdcall and calls all the functions I've defined. Does this mean all the functions I define should use the stdcall calling convention?

No. Calling conventions are handled on a per-function-call basis, right at the call site. The convention dictates how the caller and callee manage the call stack - how parameters are passed, in what order, who cleans up the stack, etc. As long as the caller and callee agree to use the same calling convention on each individual function call, it is perfectly safe for a stdcall function to call a function that uses a different convention, like cdecl, and vice versa. A function's calling convention applies only when:

  • the function is being entered by a new caller.
  • the function is returning back to that caller.
  • the function is accessing its own parameters.

Outside of that, what a function does internally has nothing to with its own calling convention.

For example, lets say that WinMain(), a stdcall function, wants to call a cdecl function.

It does not matter at all that WinMain() is itself a stdcall function. While code execution is inside of WinMain(), it can do whatever it wants. WinMain()'s stdcall convention is applied only upon entry and exit of WinMain() itself. That is the contract WinMain() has with ITS caller.

What matters is that WinMain() must follow the rules of cdecl when setting up the call stack for a cdecl function that it is about to call into, and cleaning up the call stack when that function returns back to WinMain().

The same goes for any function call of any calling convention.

I've tried not using __stdcall and nothing bad happened. I have also seen well-known GUI libraries supporting Windows don't use stdcall. Why is the stack not corrupting?

Because the call stack is being managed correctly at every function call and return, so there is no unbalanced cleanup to corrupt the stack.

Calling a function that can be either cdecl or stdcall

It can be done following way:

          mov     esi, esp

push arg3
push arg2
push arg1
call [SomeExternalProc]

mov esp, esi ; now the stack is always properly cleaned

The external procedure will preserve esi. Or you can use any other register preserved by the external procedure or even memory variable - local or global.

Good, the order of the arguments is the same for CDECL and STDCALL - in reverse order. (Left-most arg at the lowest address.) So they're compatible except for where ESP points on return. Both conventions agree on which registers are call-preserved vs. call-clobbered.

Why isn't the entry point in the C runtime library declared __stdcall?

As Raymond said, in this particular case those instances of stdcall and cdecl are physically identical (although a compiler might not let you implicitly convert a int (__stdcall *)(void) function pointer to int (__cdecl *)(void)).

Look at it another way:

Calling conventions are agreements between callers and callees on their mutual environment. The basic thing everyone (esp. in the Windows world) usually talks about with respect to cdecl and stdcall is the order or parameter passing and the responsibility to clean the stack.

But these agreements contains a lot more than that. They define which registers should be preserved by the callee. They define the alignment of the stack (GCC and Microsoft x64, for example). They can include anything else that is shared between the caller and the callee, which is quite a lot. For example, the Microsoft x64 calling convention requires the caller to reserve space for 4 machine words, even though they are passed in registers.

The thing is that these agreement are made separately between each caller and his callee. Really. Modern compilers and linkers, when they know it's safe, make these agreements between callers and callees on a case by case basis. These are perhaps not globally recognized calling conventions, but they are nonetheless agreements between callers and callees. (Some people call them "custom calling conventions", like here: What are custom calling conventions? , but I prefer the term ad-hoc calling conventions.)

To make things easier for people, there are a few standard (more or less) calling conventions that set general rules. For example, instead of saying for void x(int a) to push a on the stack, and for void y(int a, int b) to push b and then a on the stack, and for void z(int a, int b, int c) to push c and then b and then a on the stack we say something like "push the arguments from right to left on the stack". Incidentally that's what cdecl does, for example.

But in degenerate cases it happens that instances of different calling conventions resolve to the same actual agreement between the caller and the callee. Just like quadratic equations have two solutions, except in the degenerate cases where those two solution are both the same number.

The actual calling convention for the PE entry point is "work the expected way1 when called by the following code:"

kernel32!BaseProcessStart:
7c816014 6a0c push 0Ch
7c816016 684060817c push offset kernel32!`string'+0x98 (7c816040)
7c81601b e8b6c4feff call kernel32!_SEH_prolog (7c8024d6)
7c816020 8365fc00 and dword ptr [ebp-4],0
7c816024 6a04 push 4
7c816026 8d4508 lea eax,[ebp+8]
7c816029 50 push eax
7c81602a 6a09 push 9
7c81602c 6afe push 0FFFFFFFEh
7c81602e ff15b013807c call dword ptr [kernel32!_imp__NtSetInformationThread (7c8013b0)]
7c816034 ff5508 call dword ptr [ebp+8]
7c816037 50 push eax
7c816038 e8bb60ffff call kernel32!ExitThread (7c80c0f8)

(The code is from Windows XP SP3, but the principle applies universally.)

You can call the PE entry point DWORD __stdcall RawEntryPoint(void) or int __cdecl _tmainCRTStartup(void) or you can even call it uint32_t __fastcall FastEntryPoint() or unsigned long __vectorcall VectorEntryPoint() if you want.

All those calling conventions are pretty much the same, besides in how they receive parameters. With no parameters it doesn't matter. In this case what you see is a documentation issue and not much more. They could have just as much said "the return address is behind you on the stack so RET works and you should return a single integer value in EAX".

The actual calling convention between kernel32!BaseProcessStart and the PE entry point can be described in using any of those names.


1 I think what work the expected way means is obvious here.

What is better to declare for calling convention of Windows program?

You can only specify the calling convention on functions that you write and/or you have the source code of.
You cannot change calling conventions of function that are in a library (static/dynamic) since those are already compiled/linked.
Important is that the declaration and definition have the same convention.

BTW : you wouldn't gain anything by having (win-)main having the fastcall convention since it's only called once!
You would consider fastcall on functions with many small parameters (that fit in registers) that are called very very often during long periodes of time.

The (buildin) startup routine for windows programs will call either WinMain or main (depending on GUI or Console app) with a specific convention.
If you write a WinMain or main with a different convention then the linker will complain.

Confused about __cdecl and __stdcall calling convention

"Clean up the stack" actually means "adjust the stack pointer so that it matches the changes done to stack pointer when arguments and return address were pushed onto stack". In your example it would be irrelevant who cleans up the stack - you would get the same undefined behavior, because you try to access a stack-allocated variable that is overwritten by a series of later pushes of arguments needed to perform printf().

If you only write code in C or C++ you should not care of the exact details like "who cleans up the stack" - those details are critical only when writing in assembly. In C and C++ just be sure to mark function pointers with right calling conventions before calling a function through those pointers and the compiler will do the rest.



Related Topics



Leave a reply



Submit