4
votes

Question

I have a method to wrap functions by replacing them with a macro so that I can log the call and the return code. Here's an example which works:

int rc;
int foo(int a, int b);
int bar(int a, char *b, int *c);

void    LogRet(char *fn, char *file, char *from, int ln, int ret)
{
    printf("%s.%s.%d: %s()  ret:%08x\n", file, from, ln, fn, ret);
}   

#define foo(args, ...)  (rc = (foo)(args, ##__VA_ARGS__), LogRet("foo", __FILE__, __FUNCTION__, __LINE__, rc), rc)

#define bar(args, ...)  (rc = (bar)(args, ##__VA_ARGS__), LogRet("bar", __FILE__, __FUNCTION__, __LINE__, rc), rc)

The macro for the function it substitutes calls the function and logs the function name and where it was called from as well as the return code. Wrapping any function uses the same syntax and just needs the function name replaced in the macro 3 times. What I would like to do is create wrapper macro where the macro redefine for foo would be something similar to:

#define foo(args, ...) WRAPPPER(foo)

I understand the basics of stringify and double stringify but I can't even get the WRAPPER macro to do the real function call. Ideally, I'd like to get it down to single WRAP(foo) statement. The reason is, I have around 100 or more functions I'd like to wrap and it would like to do it simple from one simple force include file. I'm coming to the conclusion that it's not possible but I thought I'd ask here before abandoning the idea. I'm using clang and vc++ if that makes any difference but it would be nice to have this on any compiler as I debug a lot of different systems.


Adaptation of Jonathan Leffler's answer

As I'm new here I wasn't sure if this should be a separate answer or and edit update. This is essentially Jonathan Leffler's answer. It's in a functional example. While the 2 functions it calls are pointless, the goal was to have a macro that could wrap any function with any arg list. My main use is in logging the use flow in a large code library that has a problem. Also, my original sample has one glaring weakness. By storing the return code in a global it is not thread safe without cumbersome preparation in TLS. The global is now removed and the macro no longer uses the sequence operator to return the value, it is preserved and returned by the logging function. Also, as Augurar pointed out and shown in Jonathan's example. If the macro is declared in the same file as the function declaration, it requires parentheses.

#include <stdio.h>
#include <string.h>

int foo(int a, int b);
int bar(int a, char *b, int *c);


#if defined (_DEBUG)  || defined (DEBUG)
// Short version of __FILE__ without path requires runtime parsing
#define __SFILE__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
#ifdef WIN32
  #define WRAPPER(func, ...) LogRet(#func, __SFILE__, __FUNCTION__, __LINE__, (func)(__VA_ARGS__))
#else
  #define WRAPPER(func, ...) LogRet(#func, __SFILE__, __func__, __LINE__, (func)(__VA_ARGS__))
#endif

inline  int LogRet(const char *fn, const char *file, const char *from, int ln, int ret)
{
    printf("%s.%s.%d: %s()  ret:%08x\n", file, from, ln, fn, ret);
    return ret;
}   

#define foo(...) WRAPPER(foo, __VA_ARGS__)
#define bar(...) WRAPPER(bar, __VA_ARGS__)

#endif


int main(void)
{
    int x = foo(1, 2);
    bar(2, "doubled", &x);
    
    return 0;
}   

#ifdef foo
  #undef foo
  #undef bar
#endif

// If and only if the function definition is in the same file with the macros, you must either undefine the macros or
// parenthesize the function  e.g.   int (foo)(int a, int b) { ... }

int foo(int a, int b)   
{
    printf("%d + %d = %d\n", a, b, a + b);
    return a + b;
}   

int (bar)(int a, char *b, int *c)
{
    printf("%d  %s = %d\n", *c, b, a * *c);
    return *c * a;
}

Release build output:

1 + 2 = 3
3  doubled = 6

Debug build output:

1 + 2 = 3
test.cpp.main.35: foo()  ret:00000003
3  doubled = 6
test.cpp.main.36: bar()  ret:00000006

The main benefit is not having to find every occurrence of foo() or bar() in the code to insert a debug print to log the call and result or whatever debug code you want to insert.

1
It's a little unclear what you're trying to do. In the first code block, it seems like the foo macro is recursively defined. Also, including your attempted definition of WRAPPER (even if unsuccessful) would be helpful.augurar
The foo macro replaces the foo() function call with a "debug" version that calls that actual function it's replacing. This is why foo() in the macro is used as (foo)() which will prevent it's expansion as a macro and call the actual function. The macro then prints a debug message with the name of the function and it's return code and then insures that real code gets the return code. And the reason this is done in a macro is that it means there's no change to the code at all to get a logged debug run. The macros can be easily included in a debug build or not. The sample macros do work.ChocoBilly
Ah, I see. I suppose you have to parenthesize the function name when declaring it as well. Awkward, but I guess it works.augurar
No need to parenthesize the function declaration or code any differently. If the macro is defined the preprocessor knows the difference between "foo" and "(foo)". A common use is to catch malloc() when you can't easily override the one in the lib. You would define the macro malloc as your function and then when your code has done whatever debug tracking needed, call malloc with (malloc)(size); This works on every compiler I've ever used. The parens explicitly tell the compiler not to use the macro. The macros are typically in an #if DEBUG or #if MYDEBUG and the beauty is no code changes.ChocoBilly
@augurar I misspoke. You are correct about the parenthesizing the function declaration if the macro is defined in the same source file as the function declaration. Since I commonly use these for libraries where I don't have the source this rarely comes up. I hope I didn't confuse anyone.ChocoBilly

1 Answers

3
votes

This code looks as if it meets your requirements:

#include <stdio.h>

int rc;
int foo(int a, int b);
int bar(int a, char *b, int *c);

extern void LogRet(const char *fn, const char *file, const char *from, int ln, int ret);

void LogRet(const char *fn, const char *file, const char *from, int ln, int ret)
{
    printf("%s.%s.%d: %s()  ret:%08x\n", file, from, ln, fn, ret);
}

#define foo(args, ...)  (rc = (foo)(args, ##__VA_ARGS__), LogRet("foo", __FILE__, __FUNCTION__, __LINE__, rc), rc)

#define bar(args, ...)  (rc = (bar)(args, ##__VA_ARGS__), LogRet("bar", __FILE__, __FUNCTION__, __LINE__, rc), rc)

extern void caller1(void);

void caller1(void)
{
    int d;
    int e = foo(1, 2);
    int f = bar(3, "abc", &d);
    printf("%s(): %d + %d + %d = %d\n", __func__, d, e, f, d + e + f);
}

#undef foo
#undef bar

#define WRAPPER(func, ...) ((rc = (func)(__VA_ARGS__)), LogRet(#func, __FILE__, __func__, __LINE__, rc), rc)

#define foo(...) WRAPPER(foo, __VA_ARGS__)
#define bar(...) WRAPPER(bar, __VA_ARGS__)

extern void caller2(void);

void caller2(void)
{
    int d;
    int e = foo(2, 3);
    int f = bar(3, "abc", &d);
    printf("%s(): %d + %d + %d = %d\n", __func__, d, e, f, d + e + f);
}

int (foo)(int a, int b)
{
    return (a + b) % 3;
}

int (bar)(int a, char *b, int *c)
{
    int d = b[a];
    *c = d;
    return a + d;
}

int main(void)
{
    caller1();
    caller2();
    return 0;
}

Example output:

wrapper.c.caller1.23: foo()  ret:00000000
wrapper.c.caller1.24: bar()  ret:00000003
caller1(): 0 + 0 + 3 = 3
wrapper.c.caller2.41: foo()  ret:00000002
wrapper.c.caller2.42: bar()  ret:00000003
caller2(): 0 + 2 + 3 = 5

Pre-processed source code (excluding the output from #include <stdio.h>):

# 2 "wrapper.c" 2

int rc;
int foo(int a, int b);
int bar(int a, char *b, int *c);

extern void LogRet(const char *fn, const char *file, const char *from, int ln, int ret);

void LogRet(const char *fn, const char *file, const char *from, int ln, int ret)
{
    printf("%s.%s.%d: %s()  ret:%08x\n", file, from, ln, fn, ret);
}





extern void caller1(void);

void caller1(void)
{
    int d;
    int e = (rc = (foo)(1, 2), LogRet("foo", "wrapper.c", __FUNCTION__, 23, rc), rc);
    int f = (rc = (bar)(3, "abc", &d), LogRet("bar", "wrapper.c", __FUNCTION__, 24, rc), rc);
    printf("%s(): %d + %d + %d = %d\n", __func__, d, e, f, d + e + f);
}
# 36 "wrapper.c"
extern void caller2(void);

void caller2(void)
{
    int d;
    int e = ((rc = (foo)(2, 3)), LogRet("foo", "wrapper.c", __func__, 41, rc), rc);
    int f = ((rc = (bar)(3, "abc", &d)), LogRet("bar", "wrapper.c", __func__, 42, rc), rc);
    printf("%s(): %d + %d + %d = %d\n", __func__, d, e, f, d + e + f);
}

int (foo)(int a, int b)
{
    return (a + b) % 3;
}

int (bar)(int a, char *b, int *c)
{
    int d = b[a];
    *c = d;
    return a + d;
}

int main(void)
{
    caller1();
    caller2();
    return 0;
}

Tested with GCC 4.8.2 on Mac OS X 10.9.2.