0

The question pretty much boils down to "Can I safely cast a function pointer to one with parameters convertible to those types?", but that sounds extremely suspicious without a practical example.

I made a small utility function to call something from a DLL, given the DLL name, function name, the appropriate return type, and the parameter types and arguments. However, there might be extra bloat when using this utility than what is needed. I'm wondering if it's safe to not explicitly specify the parameter types. Taking the Windows API MessageBoxW as an example:

callStdcallDllFunction<int, HWND, PCWSTR, PCWSTR, UINT>(
    L"user32", "MessageBoxW", nullptr, nullptr, nullptr, 0
);

Here I explicitly specify the return type and four parameter types of MessageBoxW. Of course, if the arguments, starting from the right, have types equivalent to the ones in the function, I don't have to specify those. But is it ok to deduce the types as long as the arguments are valid to pass to the function in the first place (which these all are)?

callStdcallDllFunction<int>(L"user32", "MessageBoxW", nullptr, nullptr, nullptr, 0);

Here, I believe the result of GetProcAddress will be casted to int(__stdcall *)(std::nullptr_t, std::nullptr_t, std::nullptr_t, int);. The arguments passed in still fit the actual function, though. Is this still well-defined behaviour and will it always do what I want? It's worked so far every time I've tested it.

While I'm at it, is it ok to leave the return type as void if it's unused?

//return type is void instead of the actual int
callStdcallDllFunction(L"user32", "MessageBoxW", nullptr, nullptr, nullptr, 0);

As far as I can tell, this has worked every time I've tried as well. If these are actually guaranteed to work, I'd love to be able to use them like this, but I really don't know if they always will, and I'm not about to take a chance of what I write being undefined behaviour.

The exact code isn't relevant to the problem, but you can find the stdcall versions (like what MessageBox is) that you can use to test here. The only difference between these and the cdecl versions is the addition of __stdcall in the function signature.

chris
  • 60,560
  • 13
  • 143
  • 205

1 Answers1

1

You have to know the exact details of the calling convention and the ABI used to be able to give precise answers.

In some calling conventions parameters are passed on the stack. As long as the size of the parameters are the same, and there are no extra conversions involved, you are not risking a crash. Return value: if it is returned on the stack, be careful. If it comes back in a register, it doesn't matter that much.

Typically types with the same length are not a problem. But be careful with more exotic types like member (function) pointers, they are not necessarily of the same size as a void* for example.

Being convertible is not strict enough. A char can be converted to a long, but on the stack they occupy different number of bytes, so such a function pointer cast can result in differently interpreted stack content.

jmihalicza
  • 2,083
  • 12
  • 20
  • Well, the only options I provided were stdcall and cdecl. Those two do have a difference in how the stack is cleaned. If I have to mind type sizes, I probably won't bother omitting types when it would work for that call, but not another. – chris May 07 '13 at 18:59