OK, I'm going to clarify things by pretending you asked the underlying question:
How can I call a (void *)(float)
function when I only have a genericised (void *)(int)
pointer to it?
Jumping directly to asking how to change the calling convention sounds like this:
How can I make one implementation's undefined behaviour behave like another implementation's undefined behaviour, because I've written a ton of code that relies on this and don't want to have to fix it?
First, let's see what the C standard says...
If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.
Yup, there's the problem. Sad face :(
But wait, what's that the previous sentence says?
A pointer to a function of one type may be converted to a pointer to a function of another
type and back again; the result shall compare equal to the original pointer
Hooray, we can do it in a well-defined way without resorting to shims and assembly hacks, simply by converting back to the proper function signature when we call!
In short; cast the function pointer, not the arguments, and not only will it work, it will be correct. Since you must know the true signature of the function you're calling (else you couldn't pass the appropriate arguments), you can always make the correct cast, and the intermediate pointer type becomes a non-issue.
Here's a demonstration:
#include <stdio.h>
void fn(float x){
printf("%.1f\n", x);
}
int main(void) {
float f = 27.3;
void (*fptr)(float) = fn;
void (*iptr)(int) = fn; /* LA LA COMPILER WARNING I CAN'T HEAR YOU */
printf("expected:\t");
fptr(f);
printf("casting arg:\t");
iptr(*(int *)&f); /* double-undefined behaviour */
printf("casting iptr:\t");
((void(*)(float))iptr)(f);
}
Note that *(int *)&f
is itself undefined, but in this case (VS2013) the "expected" code (float used as int without conversion) does get generated in both builds. Hey, it's a valid form of undefined behaviour (as is anything else...)
And the output:
expected: 27.3
casting arg: 27.3
casting iptr: 27.3
Press any key to continue . . .
Huh? Why bother? Both methods "work". Let's try the x64 build...
expected: 27.3
casting arg: 0.0
casting iptr: 27.3
Press any key to continue . . .
Ah. There it is. In fact, if you'd written the original code on any of the other 3 currently-supported Windows platforms you wouldn't have this problem, because it wouldn't have worked in the first place - x86-64, IA-64 and ARM all use register-based calling conventions with floats and ints handled separately, whilst x86 is the stack-based anachronism.
Or, if you really really really want a portable attempt at exploiting undefined behaviour, try rewriting all the functions to take a load of dummy ints and floats before the real arguments, and a similar load of ints and floats afterwards, to ensure the ones in the middle get spilled to the stack regardless of evaluation order. Yes, that would be insane...