Looking at world through __stdcall-colored glasses

Raymond

Windows core components are compiled with the /Gz flag, which sets __stdcall as the default calling convention. On x86-32, the __stdcall calling convention is slightly more efficient than __cdecl because the stack cleanup is done at function return, rather than at the call site.¹

This configuration for Windows core components does mean that component authors wear __stdcall-colored glasses when they write their header files.

void DoSomething(int a, int b, int c);

This declares the function Do­Something with no explicit calling convention, which means that the compiler will use whatever calling convention was set as the default. For Windows core component authors, this is __stdcall, but for everybody else, the default is __cdecl.

And since the function is implemented in a Windows core component, the actual calling convention in the implementation is __stdcall.

The consequence of this situation is that the header file works great for the team that wrote the code, and their unit tests work great, and their test apps work great, everything works great. But anybody outside Windows who tries to call the function will probably be calling it with __cdecl, and then exciting things will happen.

“Works on my machine!”

To be fair, this is something that is easily overlooked, and the whole concept of calling conventions may not be something many developers are familiar with in the first place. It’s not like there’s a computer science course on ABIs and calling conventions.²

What this means for you is that if you see a Windows header file that declares a function or function pointer without an explicit calling convention, you first guess should be that the calling convention is __stdcall.

// bad header file

typedef void (*WIDGETFILTERPROC)(int a, int b);
void FilterWidgets(int c, WIDGETFILTERPROC filter);

Your first guess should be that the header file was intended to be written like this:

typedef void (CALLBACK *WIDGETFILTERPROC)(int a, int b);
void WINAPI FilterWidgets(int c, WIDGETFILTERPROC filter);

Next time, we’ll look into the wages of this sin.

¹ On other architectures, __stdcall is identical to __cdecl.

² Even if such a course existed, it probably was just a semester, and quickly forgotten. For example, I often see multithreading errors in code from younger developers: They probably did study multithreading at some point in their degree program, but it was likely just one week out of twelve-week course, and it wasn’t reinforced by subsequent work, so it ended up forgotten.

5 comments

Comments are closed. Login to edit/delete your existing comments

  • skSdnW

    The RichEdit team took it to the next level when they implemented ITextServices and related “COM” interfaces. They just declared a C++ class inheriting from IUnknown in their header. This means the first 3 methods are normal COM stdcall and the rest are MSVC thiscall. Passing a parameter in ECX when coding in C is rather difficult.

    At least one other component did the same mistake but I can’t remember off the top of my head, something speech related?

  • MGetz

    Looks at the board and selects “Things Raymond has mentioned in the past with calling conventions for 200”

    The answer is: What commonly used OS facility has built in calling convention fixes because people didn’t use STDCALL when they were supposed to.

    Answer: What is rundll

    • skSdnW

      WndProc/DlgProc callers also use a special stack cookie to detect cdecl window procedures.

  • Ivan K

    I think there were also some cases of variadic functions being marked as stdcall in the headers (possibly by just sticking on WINAPI or whatever macro in the funtion signature like everything else had). A search seems to confirm this memory, and the compiler just ignored the convention specifier.