4

In my project i have functions with different number of input parameters with different types. Since these functions are parts of libraries, I cannot change their definitions or bodies.

void methodA(boolean p1, int p2, long p3){
    ... some unrelevant code here ...
}

void methodB(int p1, int p2, int p3, long p4){
    ... some unrelevant code here too ...
}

int methodC(long p4){
    ...
}

In my project i need to have a method, which would receive the address of one of these functions. Additionally it receives well-formed list of parameters (which fits the function in the first parameter). Then this method has to call the passed function with the passed parameters.

Here is what I have now: (i have simplified the code a bit to make my idea clear)

void intercaller(void* some_func_address, ...){

    // VARARGS parameters extractor
    va_list listPointer;
    va_start( listPointer, some_func_address );

    int p1 = va_arg( listPointer, int );
    int p2 = va_arg( listPointer, int );
    int p3 = va_arg( listPointer, int );
    long p4 = va_arg( listPointer, long );

    // TODO: THIS IS NOT GENERIC CALL , CANN ONLY CALL METHOD B
    ((void (*)( int , int , int , long )) some_func_address)( p1 , p2 , p3 , p4 );

    va_end( listPointer );
}

My problem is the actual function call. The parameter list in the function call should be generic and should be able to include different number of parameters, sadly i dont know how to do that... I have tried passing varargs list like here:

((void (*)( va_list )) some_func_address)( listPointer);

but this messes up the parameters in the called function...

So my question is: is there a way to call a given function with given parameters in a generic manner? Maybe I need some sort of a typedeff or a wrapper function?

Aksim Elnik
  • 425
  • 6
  • 27
  • 4
    You probably want [`std::invoke`](http://en.cppreference.com/w/cpp/utility/functional/invoke). If your compiler doesn't support it yet you can find implementations of it elsewhere. – nwp May 04 '17 at 15:27
  • 1
    Since you expressly want a C++ solution, I have removed [c] from among your tags. C is a separate language. – John Bollinger May 04 '17 at 15:33
  • 1
    Do you have a hard requirement on a single function with signature `void intercaller(void* some_func_address, ...)`? – Caleth May 04 '17 at 15:51
  • 1
    You don't want variadic functions, you want variadic templates and/or lambdas. – n. m. could be an AI May 04 '17 at 15:54

4 Answers4

3

If you don't have std::invoke yet, use variadic templates. To treat void functions nicely, use SFINAE.

template<typename R, typename... Args>
auto call(R(*function)(Args...), Args... args) -> typename std::enable_if<!std::is_same<R, void>::value, R>::type {
    return function(args...);
}

template<typename... Args>
void call(void (*function)(Args...), Args... args) {
    function(args...);
}

Example:

void a() {
    std::cout << 'a';
}

void b(int a) {
    std::cout << "b:" << a;
}

int c(int a) {
    return a;
}

int main() {
    call(a);
    call(b, 1);
    std::cout << "c:" << call(c, 2);
}

Don't forget to #include <type_traits> for std::enable_if and std::is_same.

Try it online!

Not a real meerkat
  • 5,604
  • 1
  • 24
  • 55
  • getting "expected initializer before '<' token" when adding the templates to my code, but the idea looks nice – Aksim Elnik May 04 '17 at 16:28
  • 1
    @AksimElnik I added a link to try this solution online. If you're still having problems, can you tell me your compiler, version, and the arguments you're passing to it? – Not a real meerkat May 04 '17 at 17:08
  • 2
    Interesting solution. Is there a way to somehow define a function pointer to the call template though? Something like this won't work `void* func_ptr = &call;` and if the interceptor function needs to be registered somewhere as a callback function it won't work I suppose. – Haasip Satang May 04 '17 at 21:33
  • 1
    @HaasipSatang you're correct, it won't work. Because this is a function template (And not really a function, prior to template instantiation), you can't take its address. If you can afford to know the function type when registering the callback, you can simply instantiate the template and then take the address of the instatiated function. But, in the case of a callback, simply using a (maybe generic)lambda would generally be much better. – Not a real meerkat May 04 '17 at 21:41
  • 1
    @CássioRenan Not sure if I understand. How could the template above be instantiated in a generic way without specifying the concrete types? Let's say all the functions from your sample (a, b and c) would be registered somewhere (like when registering the natives in JNI). Then you want to intercept the calls by a call to the function template (assuming this would have a way to figure out the target method without receiving it as input parameter). Could you sketch in your example how you would instantiate the template for such a generic case? – Haasip Satang May 04 '17 at 22:10
  • 1
    I just found that [c-faq](http://c-faq.com/varargs/handoff.html). So if you really need a function and not a template as proposed by @CássioRenan the unsatisfying answer might be "In general, you cannot". – Haasip Satang May 05 '17 at 16:26
  • 1
    @CássioRenan Do you see any chance to get something like this to work? `void intercaller(void* some_func_address, void* args)` . I'm not sure how the parameters are layed out in stack / memory. If it is a continuous blockit could be possible to get them one by one by parsing the second parameter `void* args` (assuming you know the parameter types from somewhere you could parse it byte-wise). Sounds hacky, I know...just brainstorming. – Haasip Satang May 05 '17 at 16:40
  • 2
    @HaasipSatang if you're taking a `void*`(This means you're doing type erasure), and you're dispatching the call at runtime, so no, it's not possible. What I meant by *"If you can afford to know the function type when registering the callback"* is that this may only work if you register all callbacks on compile-time, when you can then instantiate your `intercaller`, assuming it was as template like I proposed. – Not a real meerkat May 05 '17 at 16:50
  • 1
    @HaasipSatang To your other comment. C and C++ are different languages. The [c-faq](http://c-faq.com/) is not really applicable when talking about C++. If you're looking for a C solution, you may want to ask a question on the [tag:c] tag. – Not a real meerkat May 05 '17 at 16:52
  • @CássioRenan Yes, of course, sorry. I just thought regarding the vargs the same concept might apply. I guess I can't contribute here anymore. But ok, so I understand there is no dynamic runtime solution here, just a compile time based on the template like you proposed. Thanks for clarifying. – Haasip Satang May 05 '17 at 17:11
  • 1
    @CássioRenan Just found this: [how to pass variable-argument list to next va-function](https://www.codeproject.com/Articles/9968/va-list-va-start-va-pass-or-how-to-pass-variable-a) and gave it a try... it *almost* works. I could forward to the "target" function (a function without vaargs like `methodB` above), but some of the arguments seem to be shifted and hence have the wrong values. seems very hacky though, – Haasip Satang May 05 '17 at 21:00
  • @HaasipSatang that is what happens to me too , when i pass the copy of the var_arg list in the arguments: `((void (*)( va_list )) some_func_address)( listPointer );` – Aksim Elnik May 08 '17 at 15:32
  • @CássioRenan I tried your solution, it's pretty cool. But unfortunately I'm really in a situation like HaasipSatang mentioned where I need a function pointer to my generic interceptor as I need to register somewhere. Right now I still don't see a way how I could realize that with the template and then dynamically at runtime forward to the original function. Maybe I should update that in the question. – Aksim Elnik May 08 '17 at 20:55
  • @AksimElnik If I understand you correctly, your intercaller must be able to call a function through a `void*` pointer. To be able to do that, it must know somehow about the types of the parameters, so it will be able to pass them. Neither the `void*` pointer nor the `va_list` have this information. This means you have to provide it, or this simply can't work in a portable way, as different target architectures may(and will) have different calling conventions. (...) – Not a real meerkat May 08 '17 at 21:03
  • (...) That said, [you may have a design problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What exactly are you trying to achieve? You may want to add more information on your question. – Not a real meerkat May 08 '17 at 21:05
  • @CássioRenan Well, in my application i know the signatures(return type, parameter types) of the methods that i call, meaning i can read from va_list correct values in a propper way. My problem is really calling the void* function pointer without having to put whole va_list into variables and then explicitly passing them... I assume that a called function knows it's parameter list and , as the passed va_list contains all the values, i thought a called function would just read parameters from the memory as it would normally do... – Aksim Elnik May 09 '17 at 17:58
1

va_args are still somewhat black magic to me, but I believe the second arg to va_start should be the first arg to the called function. I don't understand what your "clazz" is. I believe you you should call va_start as:

va_start( listpointer, some_func_address ); 

instead of:

va_start( listPointer, clazz );
DontPanic
  • 2,164
  • 5
  • 29
  • 56
1

Would this help you out?

#include <stdarg.h>

template <typename T>
T extract(va_list& list)
{
    return va_arg(list, T);
}

template<typename Result, typename ... Parameters>
Result call(Result(*function)(Parameters...), va_list& list)
{
    return function(extract<Parameters>(list)...);
}

void f1(int x, int y)
{
    std::cout << x << ' ' << y << std::endl;
}

void f2(double x, double y)
{
    std::cout << x << ' ' << y << std::endl;
}

void interceptor(void* f, ...)
{
    va_list list;
    va_start(list, f);
    if(f == &f1)
    {
        call(f1, list);
    }
    else if(f == f2)
    {
        call(f2, list);
    }
    va_end(list);
}

int main(int argc, char* argv[])
{
    interceptor((void*)&f1, 7, 7);
    interceptor((void*)&f2, 10.12, 12.10);
    return 0;
}

I personally would yet prefer pasing an enum representing the functions to the interceptor function instead of the void* pointer and using switch/case inside.

If you can make the interceptor a template function, it gets even much easier (drop the call template function entirely):

template<typename Result, typename ... Parameters>
void interceptor(Result(*function)(Parameters...), ...)
{
    va_list list;
    va_start(list, function);
    function(extract<Parameters>(list)...);
    va_end(list);
}

int main(int argc, char* argv[])
{
    interceptor(&f1, 7, 7);
    interceptor(&f2, 10.12, 12.10);
    return 0;
}
Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • First of all thank you for your help :) However i have a question. Why in the example above, do you call f1 or f2 instead of just f? Wouldnt it be easier to just call the void* f since you get it directly as parameter? What I am trying to do now is `call(f, list)` but i get a wrong parameter types error... Aand i cannot do interceptor as template function , since i need to have its address before really executing it... And as far as i know address of a function pointer is quite problematic to get... – Aksim Elnik Jun 01 '17 at 09:31
  • Well, that's the problem: f is just a void* pointer - there is no template parameter deduction possible from! So there is no other way round. Actually, it is not even [legal](https://stackoverflow.com/a/5579907/1312382) C/C++ casting a function pointer to void*... – Aconcagua Jun 01 '17 at 09:52
1

Now coming from your other question, what about this:

(Side note: referenced question tells (in the comments) the void* pointers are coming from some custom map, so there shouldn't be – as far as I can see – any issue with replacing them by other appropriate pointers/classes – which I am going to do...)

#include <stdarg.h>

class FunctionWrapper
{
public:
    virtual ~FunctionWrapper() { }
    virtual void operator()(va_list&) = 0;
};

template<typename Result, typename ... Parameters>
class FWrapper : public FunctionWrapper
{
    Result (*mFunction)(Parameters...);
    template <typename T>
    T extract(va_list& list)
    {
        return va_arg(list, T);
    }
public:
    FWrapper(Result (*function)(Parameters...))
            : mFunction(function)
    { }
    virtual void operator()(va_list& list)
    {
        static_cast<void>(mFunction(extract<Parameters>(list)...));
    }
};

// facilitates creating the wrappers:
template<typename Result, typename ... Parameters>
FunctionWrapper* createWrapper(Result (*function)(Parameters...))
{
    return new FWrapper<Result, Parameters ...>(function);
}

void f1(int x, int y)
{
    std::cout << x << ' ' << y << std::endl;
}

void f2(double x, double y)
{
    std::cout << x << ' ' << y << std::endl;
}

// e. g.:
FunctionWrapper* gWrappers[] = { createWrapper(&f1), createWrapper(&f2) };
// from your other question: you'd fill the wrappers into the map you mentioned there:
// map[whatever] = createWrapper(&function);

void interceptor(FunctionWrapper* wrapper, ...)
{
    va_list list;
    va_start(list, wrapper);
    (*wrapper)(list);
    va_end(list);
}

int main(int argc, char* argv[])
{
    interceptor(gWrappers[0], 7, 7);
    interceptor(gWrappers[1], 10.12, 12.10);

    return 0;
}

This solves the issue via polymorphism: A function wrapper class template class (we need a non-template base class to be able to place all the template instances into an array or a map; this is what your original – but actually illegal – void* pointer served for), resolving the va_list into arguments and calling the original function with...

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • Thank you once again for your effort. Unfortunately, during the compile time the types my f1 and f2 functions are unknown , meaning i only get void* handles , pointing to them, so the call to create a wrapper does not compile :/ – Aksim Elnik Jun 01 '17 at 11:15
  • 1
    Uh - that was a *very* important detail missing... What about signatures - are these kown at compile time, at least, or do you get this information dynamically, too? – Aconcagua Jun 01 '17 at 13:19
  • Yea, i am getting them at the runtime too. – Aksim Elnik Jun 01 '17 at 13:24
  • Now I am wondering - where do you get these functions from? How are they intended to be called from ordinary C or C++ (let's skip the JNI part for a moment, just assume you wanted to use these functions normally from C/C++), if there is no signature available at compile time? How are they intended to be provided with parameters dynamically? – Aconcagua Jun 01 '17 at 14:27
  • Well, I know they are defined in a specific dll. I just want to intercept all native function and handle them with this interceptor. At runtime, the JVM gives me the address of a native function as a void* and I want to intercept that. At compile time, I know only the void* (address of the function). – Aksim Elnik Jun 01 '17 at 14:36
  • 1
    DLL, so ok, Windows, I assume Then if I understand right, the LoadLibrary and GetProcAddress stuff (as explained e. g. [here](https://stackoverflow.com/a/8696688/1312382)) is out of your control, and you probably don't even know which DLL actually is loaded. Is that correct so far? That will get difficult with ordinary C or C++. Experimented with a `void(*)(...)` function pointer, but did not get it working... – Aconcagua Jun 01 '17 at 14:49
  • Hm, must I additionally assume that you don't even know - at compile time - *which* dll is going to be used??? – Aconcagua Jun 02 '17 at 11:39