3

I'm making a hooking library, which basically intercepts a function and makes it jump to the intercept function.

class CHook
{
public:

    template<typename S, typename D>
    void SetupHook(S Source, D Destionation)
    {
        pSource = (PBYTE)Source;
        pDestination = (PBYTE)Destionation;
    }

private:
    PBYTE pSource;
    PBYTE pDestination;
};

I want CHook::SetupHook to take (for Destination) either DWORD (address of function), function pointer which both can be type casted to PBYTE.

I want CHook::SetupHook to also be able to take a function pointer from lambda but it cannot be type casted to PBYTE so I overload it and since I know lambda function are classes I use std::is_class to identify them.

template<typename S, typename D>
void SetupHook(S Source, typename std::enable_if<!std::is_class<D>::value, D>::type Destionation)
{
    pSource = (PBYTE)Source;
    pDestination = (PBYTE)Destionation;
}

template<typename S, typename D>
void SetupHook(S Source, typename std::enable_if<std::is_class<D>::value, D>::type Destionation)
{
    pSource = (PBYTE)Source;
    pDestination = (PBYTE)to_function_pointer(Destionation);
}

But it results in these erorrs:

error C2783: 'void CHook::SetupHook(S,std::enable_if<std::is_class<D>::value,D>::type)': could not deduce template argument for 'D'
note: see declaration of 'CHook::SetupHook'
Budskii
  • 109
  • 7
  • Try `template typename std::enable_if<!std::is_class::value>::type SetupHook(S Source, D Destionation)` instead (ditto the second overload) – Piotr Skotnicki Mar 17 '16 at 19:26
  • that works, post it as the answer and ill select it as the answer. Also, any explanation for this? – Budskii Mar 17 '16 at 19:33
  • If you're storing a pointer in it (assuming you're doing this on Windows) PVOID might be a better choice than DWORD. A DWORD is defined to be a 32-bit unsigned integer, which isn't going to do well when storing a pointer on a 64-bit system. Or avoid the platform-specific types altogether if you're tying to make it portable. – George Mar 17 '16 at 22:57

2 Answers2

1

When you write code like:

template <typename S, typename D>
void SetupHook(S Source, typename std::enable_if<!std::is_class<D>::value, D>::type Destionation)
{
    // (...)
}

you make D a non-deducible type template parameter. That is, a type template parameter list, <typename S, typename D>, does not correspond to the parameter list of a function, and so, the compiler can't tell which type template parameter it is supposed to deduce in place of

typename std::enable_if<!std::is_class<D>::value, D>::type

Formally, a nested name specifier introduces a non-deduced context.

The solution is let the compiler deduce D, as a plain parameter type, and to put std::enable_if elsewhere, e.g.:

template <typename S, typename D>
typename std::enable_if<!std::is_class<D>::value>::type SetupHook(S Source, D Destionation)
{
    // (...)
}

Now, the compiler sees that D is the type of the second argument expression.

Community
  • 1
  • 1
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
1

Kind of a hack-y trick, but you can convert a lambda to a function pointer using unary+. Note that you can't convert a lambda to a function pointer if it captures any variables.

template<typename S, typename D>
void SetupHook(S Source, D Destionation)
{
    pSource = (PBYTE)Source;
    // static_cast is required for avoiding Visual Studio bug
    pDestination = (PBYTE)+static_cast<void(*)()>(Destionation);
}

...

void some_func() {}

int main() {
  auto str = "hello";
  auto some_lambda = [](){};
  auto some_capturing_lambda = [=]() {
    std::cout << str;
  };
  SetupHook(str, some_func);               // Works
  SetupHook(str, some_lambda);             // Works
  SetupHook(str, some_capturing_lambda);   // Error in Unary Expression
}
Sam Cristall
  • 4,328
  • 17
  • 29
  • I wouldn’t call this a hack. The only real use of unary `+` is to invoke conversions. – Jon Purdy Mar 17 '16 at 23:33
  • I had no idea about this, so simple! I was using a long method to get the function pointer, thanks! – Budskii Mar 18 '16 at 00:05
  • I also need to be able to pass raw function address e.g. 0x401000 into the parameter source/destination, will unary + not work with that? I think I will still need a seperate overload for raw function address and have like a std::is_integral check, something like that – Budskii Mar 18 '16 at 00:33
  • Doesn't work for me, I get these errors: `>Main.cpp(38): error C2593: 'operator +' is ambiguous 1> Main.cpp(38): note: could be 'built-in C++ operator+(void (__cdecl *)(void))' 1> Main.cpp(38): note: or 'built-in C++ operator+(void (__stdcall *)(void))' 1> Main.cpp(38): note: or 'built-in C++ operator+(void (__fastcall *)(void))' 1> Main.cpp(38): note: or 'built-in C++ operator+(void (__vectorcall *)(void))'` – Budskii Mar 18 '16 at 02:31
  • @Budskii what are you passing for that case? Looks like you're using visual studio, what version? I tested this on clang and gcc. – Sam Cristall Mar 18 '16 at 14:45
  • I basically copy and pasted your code, errors for both lambda arguments passed to SetupHook() and yes, I am indeed using visual studio – Budskii Mar 18 '16 at 15:30
  • @Budskii Apparently this is a bug in Visual Studio. Workaround is here: http://stackoverflow.com/a/26220743/1843316 I will update my answer – Sam Cristall Mar 18 '16 at 15:37