2

I'm wrapping the Windows API, and I wish to make error checking easy to use, and helpful. Currently, I have a global error object, with a function set to handle a new error. The set function takes four arguments: bool Error::set (const int code, const char * file, const char * const function, const int line); The function uses the file, function, and line arguments to display them in a nicely formatted message.

To ease the setting of errors, there is a macro #define setError() error.set (GetLastError(), __FILE__, __FUNCTION__, __LINE__); This way I'm able to use setError() at any time to respond to an error that an API function has set by adding it after I call that API function.

Unfortunately, this causes the code to look something like this:

SomeAPIFunction();
setError();
AnotherAPIFunction();
setError();

There is also a problem with constructors:

MyClass:MyClass()
    : a (SomeAPIFunction), b (AnotherAPIFunction)
{
    setError(); //what if both functions set an error?
}

As you can see, by using member initializer syntax, I'm actually limiting myself.

One way to fix this would be to wrap every API function:

int someAPIFunction()
{
    int ret = SomeAPIFunction();
    setError();
    return ret;
}

The function portion of the error message would tell me which function originated the error. Of course, that has to be the worst possible way of dealing with this.

The solution, it seems, is to use variadic templates. The problem is, I have no idea what I'm supposed to be doing to get them working for this. I'd imagine the final code looks something like one of the following:

wrap<int, SomeAPIFunction (5)>();
wrap<int, SomeAPIFunction, 5>();
wrap<int, SomeAPIFunction> (5);

I've read things on beginning variadic templates, but they've all left me clueless of how to set up something like this. Could anyone point me in the right direction?

I found the following on a similar question:

#include <iostream>

template<void f(void)>
struct Wrap {
   void operator()() const {
      std::cout << "Pre call hook" << std::endl;
      f();
   }
};

namespace {
   void test_func() {
      std::cout << "Real function" << std::endl;
   }
}

const Wrap<&test_func> wrapped_test_func = {};

int main() {
   wrapped_test_func();
   return 0;
}

The respondent noted that variadic templates would be a necessity to make this generic enough. It's a start, but I'm lost and grateful of any help on the matter.

Community
  • 1
  • 1
chris
  • 60,560
  • 13
  • 143
  • 205
  • For a start, not all API functions use the GetLastError mechanism. And then, even more troublesome, it is only valid to call GetLastError when those API functions report failure. So the code in your question is doomed to failure. – David Heffernan Feb 28 '12 at 21:04
  • Most Win32 API functions set the `LastError` value *only if there actually was an error*. In the case of success, they leave the `LastError` value alone. There are exceptions to this; the Win32 API documentation describes exactly what each function does. – Greg Hewgill Feb 28 '12 at 21:04
  • I'm aware of that. If `GetLastError` returns 0 (ERROR_SUCCESS), it's ignored. I know some parts use other mechanisms too, but I haven't added any support for them at all yet. As I continue to expand, I'll add in what's necessary. Calling `SetLastError (0);` when I'm finished handling it in preparation for the next API function call is a possibility as well. – chris Feb 28 '12 at 21:06
  • What compiler are you using? (MSVC does not support variadic templates). – Jesse Good Feb 28 '12 at 21:07
  • I'm using CodeBlocks with MinGW and the GNU GCC compiler. I wasn't aware of the MSVC issue though :o This API is personal anyways, so I shouldn't have a need to use MSVC. – chris Feb 28 '12 at 21:09
  • easy to use error checking? Exceptions? Or possibly you want an [API-return wrapper](http://stackoverflow.com/a/8088357/845092), which asserts if you forgot to check an error return value? – Mooing Duck Feb 28 '12 at 21:16
  • I'm not really going for something that terminates the second it sees an error. Rather, a debugging tool on the wrapper/API's end. If something is used incorrectly, and the code has `MSG_ERROR` #defined, a message will pop up the second the error is encountered. This makes it easy to create the program without having to worry about which function you messed up on. After all that's done, you can comment out the #define and just use return values, knowing now that it should run. I'd rather leave exceptions as an optional feature, like the message popping up, as they get annoying to use (imo). – chris Feb 28 '12 at 21:34
  • The reason I find exceptions a bad idea for this particular usage is that calling say `ShowWindow (FindWindow (0, "NONEXISTANT WINDOW"), SW_HIDE);` will not actually crap out. It will have no effect. My method allows the writer to see that the call isn't working, but also that the program still runs. If it doesn't run, they'll know exactly where it stopped. – chris Feb 28 '12 at 21:37
  • 1
    please note that your original "wrapper" has more problems than the need to write it for every function. For starters, the `__LINE__`, `__FILE__`, `__FUNCTION__` parameters are useless. – Ben Voigt Feb 28 '12 at 21:48
  • How so? If I don't pass them in, the message will just constantly display the point in `Error` where the message is formed. As it is, it gives the correct location. – chris Feb 28 '12 at 21:54
  • @chris: Your latest comment to my answer suggests that you've just discovered what I meant -- `__FUNCTION__` always expands to the wrapper, not the call site. – Ben Voigt Feb 28 '12 at 22:51
  • Well, it's bit messier now than it was before, but everything works so long as I call the function I want within another. Less typing and lines than before, but can still be expressed clearly (ie. replacing wrap() with something like checkError()), thanks. – chris Feb 28 '12 at 23:17

1 Answers1

1

I think you'll be able to make it work with this syntax:

wrap(&SomeAPIFunction, arg1, arg2);

The key is to let the compiler use type deduction to determine the template type parameters, since they get pretty messy in a hurry.

The code should look something like:

template<typename TRet, typename... TArgs>
TRet wrap( TRet(WINAPI *api)(TArgs...), TArgs... args )
{
    return api(args...);
}

Naturally, you'll want to use a macro to hide the address-of-function operator, use stringizing to store the function name, and store the filename and line number also, passing all of that to the actual variadic function. You'll need variadic macros for that. In fact, could you do all of this just with variadic macros and no templates?

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • That looks like it should work from a quick glance, and easy to use too :) I'll try it out. – chris Feb 28 '12 at 21:45
  • I tried that, it seems to have a problem with `std::forward()` or `(int&)` or whatever you pump in not being declared, just other forms. I tried playing around with it for a bit, but got nothing. I don't think the compiler requires the & either actually, I never have to put it in for function arguments. About the variadic macros, that sounds like it would work. In all the more confusing template haste, I forgot about that option. – chris Feb 28 '12 at 21:57
  • 1
    @chris: You're right that you don't need perfect forwarding when calling a C-compatible API. So just drop `std::forward` and the rvalue-reference. – Ben Voigt Feb 28 '12 at 22:03
  • That works fine, thanks. The only problem now is that the error message directs me to the `wrap` function. I tried putting in a variadic macro to call the `wrap` function with the right locations and have it set the error manually, but now the compiler's complaining about `__VA_ARGS__` not being defined. The other C++0x stuff all works, do I need to upgrade something else? (GNU GCC + CodeBlocks) – chris Feb 28 '12 at 22:46
  • 1
    @chris: I don't know what you're missing with the macro. Have you read http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html ? – Ben Voigt Feb 28 '12 at 22:52
  • Yeah, I'm looking in several different places. It might be that I need the `-std=c99` option, which I enabled for the global settings now, but it might have to be in projects only. – chris Feb 28 '12 at 22:57
  • WOW never mind, I didn't even simplify the problem down to the simplest variadic macro possible. Now that I did, I know it's just an error in how mine is defined. – chris Feb 28 '12 at 23:06