cdecl vs. stdcall (APIENTRY) (d3d8.1 wrapper)
Moderator: InsideQC Admins
8 posts
• Page 1 of 1
cdecl vs. stdcall (APIENTRY) (d3d8.1 wrapper)
I'm attempting to wrap up switching between OpenGL and MH's Direct3D 8.1 wrapper in a single executable.
OpenGL uses APIENTRY (ends up being stdcall) and the MH D3D wrapper, literally containing functions, would by default be using cdecl. And they aren't compatible so I need to reconcile that and it looks like the FTEQW way is the right way to be able to map a function like qglWhatever to glWhatever or d3dwrapperWhatever in the most optimal way.
I noticed in FTEQW, the D3D wrapper functions (for presumably, the original d3dquake wrapper )are declared using the stdcall calling convention (APIENTRY).
I get the idea of calling conventions, pushing various variables onto the stack in a certain way and am largely comfortable with this. Yet, I'm not quite in my zone of comfort.
Are there any downsides to using APIENTRY (which ends up being stdcall) versus cdecl or hidden twists?
I read something about how stdcall "doesn't clean up the stack", but I'm assuming this gets taken care of by the compiler and is transparent. I don't see anything unusual in the FTEQW source code for those functions.
OpenGL uses APIENTRY (ends up being stdcall) and the MH D3D wrapper, literally containing functions, would by default be using cdecl. And they aren't compatible so I need to reconcile that and it looks like the FTEQW way is the right way to be able to map a function like qglWhatever to glWhatever or d3dwrapperWhatever in the most optimal way.
I noticed in FTEQW, the D3D wrapper functions (for presumably, the original d3dquake wrapper )are declared using the stdcall calling convention (APIENTRY).
- Code: Select all
void APIENTRY D3DVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer)
{
vertarray = (float *)pointer;
if (size != 3 || type != GL_FLOAT || (stride%4))
Sys_Error("D3DVertexPointer is limited");
if (!stride)
stride = sizeof(float)*size;
vertarraystride = stride/4;
}
I get the idea of calling conventions, pushing various variables onto the stack in a certain way and am largely comfortable with this. Yet, I'm not quite in my zone of comfort.
Are there any downsides to using APIENTRY (which ends up being stdcall) versus cdecl or hidden twists?
I read something about how stdcall "doesn't clean up the stack", but I'm assuming this gets taken care of by the compiler and is transparent. I don't see anything unusual in the FTEQW source code for those functions.
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
The differences are in who has responsibility for maintaining the stack and the name decoration; these aren't things that you need to worry about as the compiler will look after them all for you.
Where you do need to worry about them is if you are GetProcAddressing a function from a DLL or supplying a function pointer as a callback. It's important to get the calling convention right there. All you need to do is match the calling convention used in the relevant header file and the compiler will look after the rest for you; no work or worry required on your part. (The exception is if you're using naked functions - where you need to explicitly declare them as naked - in which case you get to have all the fun of having to handle this stuff yourself. Check out BoxOnPlaneSide in the Q2 source for an example.)
Note that this doesn't universally apply to all callback functions, just to functions where the function pointer type was declared with a different calling convention to your default (Visual Studio will let you change the default convention if you want to). The Windows API uses __stdcall for it's own callbacks because these things mattered back in the days of 640k and micro-optimizing every byte of storage (I guess there may have been some clever tricks involved, like not bothering to clean the stack if you know that it wasn't used). You can use whatever you like so long as the function used as a callback has the same calling convention as the typedef for the function pointer type (Quake itself uses __cdecl for it's xcommand_t callbacks).
For my wrapper, because I'm supplying my own header file rather than using <gl/gl.h>, I can use whatever I like too. I don't need to match the calling convention in <gl/gl.h> and the compiler will know what to do. If I was using <gl/gl.h> I'd need to match the calling conventions used in that (and you'll notice that in some cases I do, these being anything that needs a wglGetProcAddress, like my GL_ActiveTexture function).
Where you do need to worry about them is if you are GetProcAddressing a function from a DLL or supplying a function pointer as a callback. It's important to get the calling convention right there. All you need to do is match the calling convention used in the relevant header file and the compiler will look after the rest for you; no work or worry required on your part. (The exception is if you're using naked functions - where you need to explicitly declare them as naked - in which case you get to have all the fun of having to handle this stuff yourself. Check out BoxOnPlaneSide in the Q2 source for an example.)
Note that this doesn't universally apply to all callback functions, just to functions where the function pointer type was declared with a different calling convention to your default (Visual Studio will let you change the default convention if you want to). The Windows API uses __stdcall for it's own callbacks because these things mattered back in the days of 640k and micro-optimizing every byte of storage (I guess there may have been some clever tricks involved, like not bothering to clean the stack if you know that it wasn't used). You can use whatever you like so long as the function used as a callback has the same calling convention as the typedef for the function pointer type (Quake itself uses __cdecl for it's xcommand_t callbacks).
For my wrapper, because I'm supplying my own header file rather than using <gl/gl.h>, I can use whatever I like too. I don't need to match the calling convention in <gl/gl.h> and the compiler will know what to do. If I was using <gl/gl.h> I'd need to match the calling conventions used in that (and you'll notice that in some cases I do, these being anything that needs a wglGetProcAddress, like my GL_ActiveTexture function).
Last edited by mh on Tue May 10, 2011 10:43 pm, edited 1 time in total.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
We knew the words, we knew the score, we knew what we were fighting for
-

mh - Posts: 2292
- Joined: Sat Jan 12, 2008 1:38 am
Ah, 'tis as I guessed: http://blogs.msdn.com/b/oldnewthing/arc ... 47184.aspx
If I was a betting man I'd say that APIENTRY was defined as __pascal in 16-bit Windows but changed to __stdcall for 32-bit Windows, and that the use of this convention on other libraries was a hangover from the old days.
Nearly all Win16 functions are exported as Pascal calling convention. The callee-clean convention saves three bytes at each call point, with a fixed overhead of two bytes per function. So if a function is called ten times, you save 3*10 = 30 bytes for the call points, and pay 2 bytes in the function itself, for a net savings of 28 bytes. It was also fractionally faster. On Win16, saving a few hundred bytes and a few cycles was a big deal.
If I was a betting man I'd say that APIENTRY was defined as __pascal in 16-bit Windows but changed to __stdcall for 32-bit Windows, and that the use of this convention on other libraries was a hangover from the old days.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
We knew the words, we knew the score, we knew what we were fighting for
-

mh - Posts: 2292
- Joined: Sat Jan 12, 2008 1:38 am
example:
somefunc(5);
stdcall:
push $5
call somefunc
cdecl:
push $5
call somefunc
add $4, %esp
example:
void somefunc(int arg) {**code**}
stdcall:
enter
**code**
leave
add $4, %esp
ret
cdecl:
enter
**code**
leave
ret
or something.
stdcall unpushes the arguments within the function itself
cdecl unpushes the arguments within the caller.
cdecl is required for variable arguments, while stdcall has slightly lower codesize footprint (all those pops are in one place instead of every call).
In practise, mov somevar, someoffset(%esp) is the same speed as a push, despite taking more instruction bytes to do it. So if a function is called multiple times with similar arguments, the cdecl form will win, as it doesn't have to re-push every single argument. it can order them however it wants, even pushing arguments for a later function call before calling an earlier one.
you get stack corruption if you mix up your calling conventions, of course.
Thus when using function pointers and external definitions and other stuff like that, you must make sure your calling conventions match.
Hence why the d3d wrapper, which replaces GL functions matches the calling convention of the regular pointers to GL functions.
fastcall is the third calling convention, which is generally unspecified, varies between compilers, and should never be used for API interfaces. It tends to pass various arguments via registers rather than via the stack.
The default is cdecl unless otherwise specified (maybe in project settings).
Windows uses stdcall for the windows API, because that convention is compatible with pascal which was considered highly important when windows was first created. Mixing code was easier by adding calling conventions to C.
stdcall and fastcall calling conventions require the function to accept a fixed number of arguments. They do not accept variable arguments, at all. Such variable argument functions must be declared as cdecl.
stdcall and fastcall functions have an additional postfix on their name, specifying the number of arguments they take. Which means that if such a function is not prototyped, it will not match, and will trigger a linker error (if the extra arguments are not used, cdecl doesn't care, if they are, then behaviour relates purely to the use of 'uninitialised' values). You should be prototyping all your functions anyway, gcc defaults to an error if you do not, if I remember correctly.
On amd64 instruction sets, there is only one official calling convention for any given platform. Any cdecl/fastcall/stdcall nonsense is ignored. The actual calling convention is similar to fastcall.
Note that I said on any given platform. There are still differences. MSVC's convention attempts to keep variable arguments as vaugely compatible with x86 code while the linux variant allows a few more register arguments or something. GCC allows you to specify exactly which, but that's only really useful for wine type usages.
If you're replacing gl functions with d3dwrapper functions, then APIENTRY is the easiest/best way to do it, and its unlikely to affect performance in any perceptible way.
So long story short cdecl requires the calleR to pop the arguments, stdcall requires the calleE to pop the arguments - the compiler hides the details. stdcall doesn't support variable arguments - this is the only bit that the compiler doesn't hide. Otherwise it really doesn't make any difference so long as the convention is used consistantly on original function, prototypes, and pointers.
somefunc(5);
stdcall:
push $5
call somefunc
cdecl:
push $5
call somefunc
add $4, %esp
example:
void somefunc(int arg) {**code**}
stdcall:
enter
**code**
leave
add $4, %esp
ret
cdecl:
enter
**code**
leave
ret
or something.
stdcall unpushes the arguments within the function itself
cdecl unpushes the arguments within the caller.
cdecl is required for variable arguments, while stdcall has slightly lower codesize footprint (all those pops are in one place instead of every call).
In practise, mov somevar, someoffset(%esp) is the same speed as a push, despite taking more instruction bytes to do it. So if a function is called multiple times with similar arguments, the cdecl form will win, as it doesn't have to re-push every single argument. it can order them however it wants, even pushing arguments for a later function call before calling an earlier one.
you get stack corruption if you mix up your calling conventions, of course.
Thus when using function pointers and external definitions and other stuff like that, you must make sure your calling conventions match.
Hence why the d3d wrapper, which replaces GL functions matches the calling convention of the regular pointers to GL functions.
fastcall is the third calling convention, which is generally unspecified, varies between compilers, and should never be used for API interfaces. It tends to pass various arguments via registers rather than via the stack.
The default is cdecl unless otherwise specified (maybe in project settings).
Windows uses stdcall for the windows API, because that convention is compatible with pascal which was considered highly important when windows was first created. Mixing code was easier by adding calling conventions to C.
stdcall and fastcall calling conventions require the function to accept a fixed number of arguments. They do not accept variable arguments, at all. Such variable argument functions must be declared as cdecl.
stdcall and fastcall functions have an additional postfix on their name, specifying the number of arguments they take. Which means that if such a function is not prototyped, it will not match, and will trigger a linker error (if the extra arguments are not used, cdecl doesn't care, if they are, then behaviour relates purely to the use of 'uninitialised' values). You should be prototyping all your functions anyway, gcc defaults to an error if you do not, if I remember correctly.
On amd64 instruction sets, there is only one official calling convention for any given platform. Any cdecl/fastcall/stdcall nonsense is ignored. The actual calling convention is similar to fastcall.
Note that I said on any given platform. There are still differences. MSVC's convention attempts to keep variable arguments as vaugely compatible with x86 code while the linux variant allows a few more register arguments or something. GCC allows you to specify exactly which, but that's only really useful for wine type usages.
If you're replacing gl functions with d3dwrapper functions, then APIENTRY is the easiest/best way to do it, and its unlikely to affect performance in any perceptible way.
So long story short cdecl requires the calleR to pop the arguments, stdcall requires the calleE to pop the arguments - the compiler hides the details. stdcall doesn't support variable arguments - this is the only bit that the compiler doesn't hide. Otherwise it really doesn't make any difference so long as the convention is used consistantly on original function, prototypes, and pointers.
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
Spike wrote:If you're replacing gl functions with d3dwrapper functions, then APIENTRY is the easiest/best way to do it, and its unlikely to affect performance in any perceptible way.
So long story short cdecl requires the calleR to pop the arguments, stdcall requires the calleE to pop the arguments - the compiler hides the details. stdcall doesn't support variable arguments - this is the only bit that the compiler doesn't hide. Otherwise it really doesn't make any difference so long as the convention is used consistantly on original function, prototypes, and pointers.
Alrighty. Thanks for the info. This morning when I woke up, I have to admit I didn't understand what PASCAR FAR was in the network code, for instance.
- Code: Select all
int (PASCAL FAR *pioctlsocket)(SOCKET s, long cmd, u_long FAR *argp);
Go figure, things keep getting less scary and mysterious.
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
mh wrote:The differences are in who has responsibility for maintaining the stack and the name decoration; these aren't things that you need to worry about as the compiler will look after them all for you.
....All you need to do is match the calling convention used in the relevant header file and the compiler will look after the rest for you; no work or worry required on your part.
Cool.
The Windows API uses __stdcall for it's own callbacks because these things mattered back in the days of 640k and micro-optimizing every byte of storage
Hmmmm. Strange how from 1988 to 1997 computers rapidly evolved (DOS/8-bit/VGA/keyboard/low mem --> Windows/32-bit/high color/mouse/big memory) before reaching an "island of stability" relatively speaking.
I can imagine a few extra bytes here and there would have been very detrimental.
For my wrapper, because I'm supplying my own header file rather than using <gl/gl.h>, I can use whatever I like too. I don't need to match the calling convention in <gl/gl.h> and the compiler will know what to do. If I was using <gl/gl.h> I'd need to match the calling conventions used in that (and you'll notice that in some cases I do, these being anything that needs a wglGetProcAddress, like my GL_ActiveTexture function).
Yeah I spotted those 5-6 guys.
Spike wrote:If you're replacing gl functions with d3dwrapper functions, then APIENTRY is the easiest/best way to do it, and its unlikely to affect performance in any perceptible way.
So long story short cdecl requires the calleR to pop the arguments, stdcall requires the calleE to pop the arguments - the compiler hides the details. stdcall doesn't support variable arguments - this is the only bit that the compiler doesn't hide. Otherwise it really doesn't make any difference so long as the convention is used consistantly on original function, prototypes, and pointers.
This morning when I woke up, I have to admit I didn't understand what PASCAR FAR was in the network code, for instance.
- Code: Select all
int (PASCAL FAR *pioctlsocket)(SOCKET s, long cmd, u_long FAR *argp);
Go figure, things keep getting less scary and mysterious. Thanks for the info.
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Interesting little fact - several chunks of Quake code are copy 'n' paste from MSDN sample code. The PIXELFORMATDESCRIPTOR stuff is a straight lift, for example. I bet some of that other stuff is too.
On newer versions of Visual Studio PASCAL defines as __stdcall, not sure about older, but no doubt at some point in the past it defined as __pascal. Important to link with the correct SDK (and to not GetProcAddress these functions if you're not doing so!!!) Quake's dynamic linking to the Winsock library should be considered a bug these days, as you're using a function pointer defined with one calling convention to point to a function in a DLL potentially declared with another (I've seen this kind of thing explode in the past).
Say you compile Quake on a machine where your header defines PASCAL as __stdcall but run it on a machine where it's something else in the Winsock library? Boom boom baby. (In practice though this shouldn't happen as it seems extremely reasonable to assume that it's __stdcall on all 32-bit+ Windows).
Being conservative and keeping that code is therefore dangerous. Just statically link to Winsock instead; aside from anything else the only reason for the dynamic linking was to enable Quake to run on Windows 3.1 (check the comment in WINS_Init) which Quake ended up not being able to run on anyway!!!
Don't change BlockingHook though; it's type is defined as PASCAL FAR in the header, so it's best to match the header otherwise if the define changes in the future you might crash.
The FAR stuff is to do with 16-bit memory addressing. Segments and offsets - yuck. There's also a NEAR, but nobody has to worry about this crap any more.
On newer versions of Visual Studio PASCAL defines as __stdcall, not sure about older, but no doubt at some point in the past it defined as __pascal. Important to link with the correct SDK (and to not GetProcAddress these functions if you're not doing so!!!) Quake's dynamic linking to the Winsock library should be considered a bug these days, as you're using a function pointer defined with one calling convention to point to a function in a DLL potentially declared with another (I've seen this kind of thing explode in the past).
Say you compile Quake on a machine where your header defines PASCAL as __stdcall but run it on a machine where it's something else in the Winsock library? Boom boom baby. (In practice though this shouldn't happen as it seems extremely reasonable to assume that it's __stdcall on all 32-bit+ Windows).
Being conservative and keeping that code is therefore dangerous. Just statically link to Winsock instead; aside from anything else the only reason for the dynamic linking was to enable Quake to run on Windows 3.1 (check the comment in WINS_Init) which Quake ended up not being able to run on anyway!!!
Don't change BlockingHook though; it's type is defined as PASCAL FAR in the header, so it's best to match the header otherwise if the define changes in the future you might crash.
The FAR stuff is to do with 16-bit memory addressing. Segments and offsets - yuck. There's also a NEAR, but nobody has to worry about this crap any more.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
We knew the words, we knew the score, we knew what we were fighting for
-

mh - Posts: 2292
- Joined: Sat Jan 12, 2008 1:38 am
PASCAL == pascal == stdcall. always. its termed stdcall because it works in pascal, c, VB, etc, and because its very hard to get a language developer to acknowledge other languages in their compiler. :P
The two are synonyms. Older code calls it pascal, newer code calls it stdcal. Different name, same meaning.
Regarding linking against winsock... Those functions are stdcall, on all platforms, be it 16bit or 32bit. All public winapi functions are stdcall. Otherwise it wouldn't be std, and couldn't be called from VB(6) and stuff. Point is, if the linkage WAS changed, then it wouldn't work even if it was statically linked.
A far call uses a different instruction to invoke and return, lcall vs regular call, lret vs regular ret (not to be confused with iret). These instructions push/pop the CS segment register as part of the call, and is basically a pain to use on any protected-mode system, although win3.1 was generally able to. You won't need to use it on any 32+bit system unless you're writing the operating system itself. On a 64bit system, it might be used to switch between 32bit and 64bit code, for instance.
The two are synonyms. Older code calls it pascal, newer code calls it stdcal. Different name, same meaning.
Regarding linking against winsock... Those functions are stdcall, on all platforms, be it 16bit or 32bit. All public winapi functions are stdcall. Otherwise it wouldn't be std, and couldn't be called from VB(6) and stuff. Point is, if the linkage WAS changed, then it wouldn't work even if it was statically linked.
A far call uses a different instruction to invoke and return, lcall vs regular call, lret vs regular ret (not to be confused with iret). These instructions push/pop the CS segment register as part of the call, and is basically a pain to use on any protected-mode system, although win3.1 was generally able to. You won't need to use it on any 32+bit system unless you're writing the operating system itself. On a 64bit system, it might be used to switch between 32bit and 64bit code, for instance.
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
8 posts
• Page 1 of 1
Who is online
Users browsing this forum: No registered users and 1 guest