Search code examples
c++templatesvariadic-templates

Call a function from its address via a templated function


Assuming I have the runtime memory address of a function in an Application and I know the return type of said function, is it possible to call the function, knowing the functions return type, its arguments and calling convention, using a variadic template?

The templated function has to support both void and non-void return types. Due to the fact that we are dealing with function pointers, the compiler shouldn't complain, despite return ptr.

I thought about doing something like this:

template<typename ReturnType, typename Address, typename... Args>
ReturnType function_caller(Address address, Args... args)
{
    ReturnType(*ptr)(Args...) = address;
    return ptr(args...);
}
int main()
{
    auto address = 0x100;
    auto address2 = 0x200;
    function_caller<void>(&address, 1, 1); // Function with return type void.
    int result = function_caller<int>(&address2, 1, 2, 3.f, "hello"); 
    // result should contain the int value we received by calling the function at 0x200

}

Sadly the compiler throws the error C2440: It can't convert the Address "address" to 'ReturnType (__cdecl *)(int,int)'

I would really appreciate your help with this problem. I know i could just split this wrapper into 2 functions: one for void calls and one for non-void calls, but i hope there is a more elegant, template-supported solution.

Thank you and have a nice day!


Solution

  • the answer is yes, but doing it with variadic template is dangerous .

    to enforce the compiler to cast the address to a function pointer you need to use reinterpret_cast or a c cast .

    note : you are improperly casting an integral address to a pointer because you are really trying to cast the address of the variable containing the address to the pointer not the address itself !

    so this line :

    function_caller<void>(&address, 1, 1); // Function with return type void.
    

    should be :

    function_caller<void>(address, 1, 1); // Function with return type void.
    

    and always use the address type as uintptr_t which will fit for any address available for the architecture (64 bit or 32)

    but doing so with variadic template isn't safe at all . the reason is that the function have particular arguments type like this :

    int fn(std::string& str, const char* ptr, uint64_t& i);
    

    but when you cast with variadic template the compiler will deduce the types from the arguments passed however some conversions may be required !

    so in your current version :

    int i;
    function_caller<int>(0x15216516, "str", "ptr", i);
    

    the compile will assume that the function signature is something like :

    int fn(const char*, const char*, int); // wrong types means stack corruptions and undefined behaviors
    

    also see this :

    std::string to_string(std::string_view v);
    
    function_caller<std::string>(0x15216516, "str"); // wrong the compiler won't convert the string literal for you and the function will end up with a dangling view
    
    function_caller<std::string>(0x15216516, std::string("str")); // wrong again there is no conversion from std::string to std::string_view here
    
    

    so it is really reliable only to specify the whole function type and use that to cast the address like what boost.dll does