none
WPP tracing macro with templated function RRS feed

  • Question

  • I'm converting some existing tracing macros into using WPP, with the same macro usages. One macro calls some other function, check the result, and log some error information if the result is not as expected, like the following

    CHECK_RESULT(SomeFunctionCall());

    So I implemented this CHECK_RESULT macro with WPP:

    // USEPREFIX(CHECK_RESULT, "!STDPREFIX!");

    // FUNC CHECK_RESULT(CHECK_RESULT_EXPRESSION);

    // USESUFFIX(CHECK_RESULT, " failed with hr = 0x%X", hr);

    and the trace-if logic is implemented by the WPP PRE and POST macros:

    #define WPP_CHECK_RESULT_EXPRESSION_PRE(expr) hr = (expr); if (FAILED(hr)) {

    #define WPP_CHECK_RESULT_EXPRESSION_POST(expr) ; goto end; }

    This seems to be working fine with regular function calls to be checked. But when the function call in the macro is a templated function, tracewpp.exe will report the following error when doing WPP preprocessing:

    error: (ParseSrcCallback)CHECK_RESULT doesn't take ((args))

    Am I doing in a wrong way, or is this some limitation of WPP? many thanks!!!!!

    Monday, December 3, 2012 1:05 AM

Answers

  • I am 99.99% sure wpp was not designed to be used in templates code

    d -- This posting is provided "AS IS" with no warranties, and confers no rights.

    Monday, December 3, 2012 3:41 AM
  • Ah, ok I see what's happening -- it's a weird intersection of several corner-cases.  Forget WPP for a moment... C++'s preprocessor doesn't know about template parameters, so you can't say

    SOME_MACRO( MyClass<A, B>::F() )

    since the comma is interpreted to split macro parameters, which isn't what you want.  The well-known workaround (which your code illustrates) is to add an extra set of parentheses, like

    SOME_MACRO( (MyClass<A, B>::F()) )

    However, this runs afoul of a special WPP feature.  Because many old codebases used to add extra parentheses to their trace statements

    #define MyTrace(x) KdPrint(x)
    MyTrace( ("error %08x", ntstatus) )

    WPP supports a special mode where it consumes two sets of parentheses. This mode is activated by defining the WPP macro with a double set of parentheses

    // FUNC CHECK_RESULT((CHECK_RESULT_EXPR));

    If you use double parentheses in a trace marco, but the macro was defined with a single set of parentheses, WPP notices and throws an error to say "oh, you probably meant to use the ((args)) syntax instead."  That's the error you're seeing.


    One way to work around it is to change your WPP marco to add the double parentheses.  Then add three (!!) pairs of parentheses around your expression: one for WPP to eat, one for C++'s preprocessor, and one for the actual macro invocation

    // FUNC CHECK_RESULT((CHECK_RESULT_EXPR));
    CHECK_RESULT( ((CDerived<X, X>::Create(x, x, &x))) );

    Aside from the forest of parentheses, this has the downside of requiring you to add double-parentheses around everything, even simple cases like

    CHECK_RESULT( (S_OK) );

    Alternatively, you could shuffle around this callsite to avoid putting "<,>" in there.  It's sufficient to have the preprocessor add it back later (after WPP has already parsed the file -- recall that WPP runs before the C++ preprocessor).  A couple tricks are

    #define MYCLASS CDerived<X, X>
    CHECK_RESULT( MYCLASS::Create(x, x, &x) );
    
    #define COMMA ,
    CHECK_RESULT( CDerived<X COMMA X>::Create(x, x, &x) );
    Finally, all you need to hide the comma from the C++ preprocessor is parentheses around it.  So you can get by with this odd syntax
    CHECK_RESULT( (CDerived<X, X>::Create)(x, x, &x) );

    Personally, I like that workaround the most.

    Tuesday, December 4, 2012 3:18 AM

All replies

  • I am 99.99% sure wpp was not designed to be used in templates code

    d -- This posting is provided "AS IS" with no warranties, and confers no rights.

    Monday, December 3, 2012 3:41 AM
  • I can invoke templated classes and templated functions from a parameter to a WPP macro.  I can also call WPP macros from within a templated class and within templated functions.  Can you give a more complete example of what's not working for you?

    // Global template function
    template<typename T>
    HRESULT F(T t)
    {
        HRESULT hr = S_OK;
        CHECK_RESULT(t, "function template parameter");
    
    Cleanup:
        return hr;
    }
    
    // Template class
    template<typename T1>
    struct C
    {
        // Template member function
        template<typename T2>
        static HRESULT F(T1 t1, T2 t2)
        {
            HRESULT hr = S_OK;
            CHECK_RESULT(t1, "class template parameter");
            CHECK_RESULT(t2, "function template parameter");
        
        Cleanup:
            return hr;
        }
    };
    
    HRESULT Test()
    {
        HRESULT hr;
    
        CHECK_RESULT(F<HRESULT>(S_FALSE), "calling a global template function");
        CHECK_RESULT(C<HRESULT>::F<HRESULT>(S_FALSE, S_OK), "calling a template class");
    
    Cleanup:
    
        return hr;
    }

    With WPP configuration:

    //begin_wpp config
    //FUNC CHECK_RESULT{LEVEL=TRACE_LEVEL_ERROR,COMPNAME=TR_DEFAULT}(HREXPR,MSG,...);
    //USESUFFIX (CHECK_RESULT, " [hr=%!HRESULT!]", hr);
    //end_wpp
    
    #define WPP_LEVEL_COMPNAME_HREXPR_PRE(level,comp,expr) do { hr = expr; if (FAILED(hr)) {
    #define WPP_LEVEL_COMPNAME_HREXPR_POST(level,comp,expr) ; goto Cleanup; } } while(0)
    

    Monday, December 3, 2012 10:25 PM
  • My template function is something like this:

    template<typename CBase, typename T1 = Struct1>
    Class CDerived : public CBase
    {
        template<typename CRet, typename P1, typename P2>
        static HRESULT Create(
            P1 p1,
            P2 p2,
            TComPtr<CRet> &spNewInstance
            )
        {
            HRESULT hr;
            
            // object instantiation code
    
            return hr;
        }
    }

    WPP configuration is like this:

    // begin_wpp config
    // USEPREFIX(CHKECK_RESULT, "%!STDPREIFX!");
    // FUNC CHECK_RESULT(CHECK_RESULT_EXPR);
    // USESUFFIX(CHECK_RESULT, " failed (0x%X)", hr);
    // end_wpp
    
    #define WPP_CHECK_RESULT_EXPR_PRE(expr) hr = (expr); if (FAILED(hr)) {
    #define WPP_CHECK_RESULT_EXPR_POST(expr) ; goto end; }

    And the code using the tracing macro:

    HRESULT CObject::SomeFunction()
    {
        HRESULT hr = S_OK;
    
        // ...
    
        // tracing the template function
        CHECK_RESULT((CDerived<CBaseObject, Struct1>::Create(
            p1,
            p2,
            m_spObj
            )));
    
        // ...
    
    end:
        return hr;
    }

    This will give the following error when running WPP preprocessing:

    error: (ParseSrcCallback)CHECK_RESULT doesn't take ((args))

    which might come from the additional layer of () around CDerived<>::Create(), so I removed that, and tracewpp.exe gives the following error:

    errpr: (ParseSrcCallBack)CHECK_RESULT requires 1 arguments. (Found only 2)

    So that means my template function got expanded into two arguments?

    Thank you for your help! Much appreciated :)

    Tuesday, December 4, 2012 1:43 AM
  • Ah, ok I see what's happening -- it's a weird intersection of several corner-cases.  Forget WPP for a moment... C++'s preprocessor doesn't know about template parameters, so you can't say

    SOME_MACRO( MyClass<A, B>::F() )

    since the comma is interpreted to split macro parameters, which isn't what you want.  The well-known workaround (which your code illustrates) is to add an extra set of parentheses, like

    SOME_MACRO( (MyClass<A, B>::F()) )

    However, this runs afoul of a special WPP feature.  Because many old codebases used to add extra parentheses to their trace statements

    #define MyTrace(x) KdPrint(x)
    MyTrace( ("error %08x", ntstatus) )

    WPP supports a special mode where it consumes two sets of parentheses. This mode is activated by defining the WPP macro with a double set of parentheses

    // FUNC CHECK_RESULT((CHECK_RESULT_EXPR));

    If you use double parentheses in a trace marco, but the macro was defined with a single set of parentheses, WPP notices and throws an error to say "oh, you probably meant to use the ((args)) syntax instead."  That's the error you're seeing.


    One way to work around it is to change your WPP marco to add the double parentheses.  Then add three (!!) pairs of parentheses around your expression: one for WPP to eat, one for C++'s preprocessor, and one for the actual macro invocation

    // FUNC CHECK_RESULT((CHECK_RESULT_EXPR));
    CHECK_RESULT( ((CDerived<X, X>::Create(x, x, &x))) );

    Aside from the forest of parentheses, this has the downside of requiring you to add double-parentheses around everything, even simple cases like

    CHECK_RESULT( (S_OK) );

    Alternatively, you could shuffle around this callsite to avoid putting "<,>" in there.  It's sufficient to have the preprocessor add it back later (after WPP has already parsed the file -- recall that WPP runs before the C++ preprocessor).  A couple tricks are

    #define MYCLASS CDerived<X, X>
    CHECK_RESULT( MYCLASS::Create(x, x, &x) );
    
    #define COMMA ,
    CHECK_RESULT( CDerived<X COMMA X>::Create(x, x, &x) );
    Finally, all you need to hide the comma from the C++ preprocessor is parentheses around it.  So you can get by with this odd syntax
    CHECK_RESULT( (CDerived<X, X>::Create)(x, x, &x) );

    Personally, I like that workaround the most.

    Tuesday, December 4, 2012 3:18 AM
  • Incidentally, I have a few side remarks regarding this:

    // begin_wpp config
    // USEPREFIX(CHKECK_RESULT, "%!STDPREIFX!");
    // FUNC CHECK_RESULT(CHECK_RESULT_EXPR);
    // USESUFFIX(CHECK_RESULT, " failed (0x%X)", hr);
    // end_wpp
    
    #define WPP_CHECK_RESULT_EXPR_PRE(expr) hr = (expr); if (FAILED(hr)) {
    #define WPP_CHECK_RESULT_EXPR_POST(expr) ; goto end; }
    1. There's a couple typos on the USEPREFIX line.
    2. WPP supports %!HRESULT!, which gives a more readable version of 0x%X.  (In my opinion, this is one of the killer features of WPP, compared to printf.)
    3. The _PRE and _POST macros aren't safe.  If you have code like the below, the wrong thing will happen.
    if (something)
        CHECK_RESULT(something());

    You can prevent that by inserting extra do { } while (0) into the _PRE and _POST:

    #define WPP_CHECK_RESULT_EXPR_PRE(expr) do { hr = (expr); if (FAILED(hr)) {
    #define WPP_CHECK_RESULT_EXPR_POST(expr) ; goto end; } } while (0)
    Tuesday, December 4, 2012 3:22 AM
  • That makes this all so clear to me! Thanks! I also like the COMMA trick (partially because I can't afford to use double parentheses for every macro), and will try it tomorrow :)
    Tuesday, December 4, 2012 5:42 AM
  • These are indeed very helpful remarks. Thanks for pointing out!!!
    Tuesday, December 4, 2012 5:43 AM