views:

316

answers:

1

I need to use FormatMessage() for a project, but I don't like its scary interface. Does anyone know of a facade that tidies it up while still allowing for the replacement parameters?

I just read the second part of the FastFormat introduction, and am considering writing an extension for FormatMessage() (or asking the FastFormat project team if they've got one in the works), but I'm keen to get something asap, so if there's anything else decent out there I'd probably grab that instead.

What I want is to be able to write code such as:

HINSTANCE netevent = ::LoadLibrary("netevent.dll");
std::string msg = LookupError(netevent, EVENT_SERVICE_START_FAILED_II,
    "child-svr", "parent-svr", "ship happens");
::puts(msg.c_str());

Which would give the result:

The child-svr service depends on the parent-svr service which failed to start be cause of the following error:
ship happens

The current wrapper I've built has the interface:

std::string LookupError(HINSTANCE hinst, DWORD id, ...);

There are two problems with this:

  • It's not type-safe, since it's easy to pass any type - int, std::string, void* - that is not const char*
  • It's easy to mismatch the number of arguments with the number required by the format string representing the error

Given the abilities of FastFormat in terms of type-safety, I want to know if there's a way to follow its mechanisms to deal with FormatMessage().

+1  A: 

As the number of parameters to be inserted into the format string cannot be checked by the compiler, it is impossible to make this truly type-safe at compile time.

You can get most of the way there by just having a few overloads for different numbers of inserted parameters, and then specifying the inserted values with something flexible like boost::any. So the overload for two parameters would be:

std::string FormatMessage(HINSTANCE hinst, DWORD id, const boost::any &arg1, const boost::any &arg2);

When you retrieve the value from arg1, boost will throw if you try to get the wrong type, so you just need to examine the format string and try to get the required type from each argument.

Alternatively you could use templates and std::ostringstream (or boost::lexical_cast) for a very flexible version; again there would be overloads to allow the number of arguments to vary, so here's the single-argument version:

template <class TArg1>
std::string FormatMessage(HINSTANCE hinst, DWORD id, const TArg1 &arg1)
{
    std::ostringstream arg1Stream;
    arg1Stream << arg1;
    std::string arg1String = arg1Stream.str();

    DWORD_PTR argArray = reinterpret_cast<DWORD_PTR>(arg1String.c_str());

    // ... etc
}

That way you can get a string from each argument as long as the passed type can be streamed, and nothing else should be required as long as the format strings expect only strings to insert.

Daniel Earwicker
Interesting idea. FormatMessage() requires either a va_list of args, or an array of 32-bit args. So in the first case we'd need a variadic function, though that could be invokved inside a function such as the one you describe, and the boost::any would handle the type-safety. Alternatively, we could build the array of arguments directly. But one of the reasons I was interested in FastFormat is its ability to work with arguments of arbitrary type, which would mean that any type could be passed, and then converted to char* before invoking FormatMessage(). I don't think boost::any would permit it
dcw
See update, something more flexible.
Daniel Earwicker