Creating a function for displaying status updates with printf style string formatting on an MFC control

Viewed 253

I needed to display status updates on a dialog box, and wanted to be able to send printf style formatted strings to it. In addition I would like that function to call a similar function which will add the formatted data to a log file.

Assuming my Static control is called IDC_MYSTATUSBAR, the function looks like this:

void MyDialog::ShowStatus(LPCWSTR lpText, ...)
{
    CString sMsg;
    va_list ptr;
    va_start(ptr, lpText);
    if (*ptr == 0)
       sMsg = lpText;
    else
       sMsg.FormatV(lpText, ptr);
    va_end(ptr);
    CWinApp *myApp = AfxGetApp();
    if (myApp)
    {
        CWnd *pWnd = myApp->m_pMainWnd;
        if (pWnd)
        {
            ::SetDlgItemText(pWnd->GetSafeHwnd(), IDC_MYSTATUSBAR, sMsg.GetString());
        }
    }
    myLogFunction(L"%s", sMsg.GetString());
}

The function is called as follow:

ShowStatus(L"The results are %d.", 100);

or

ShowStatus(L"Server returned the following result: %s", L"result");

However other modules and libraries calls my function by having a member variable called logfunc which is defined as:

typedef void(*logFunc)(LPCWSTR lpText, ...);

and in that case, the code from the other module will look like:

logFunc m_logfunc;      
if (m_logfunc) m_logfunc(L"Internet Time: (%d) %s Local Time = %s", result, TimeResult.FormatGmt(L"%d.%m.%Y %H:%M"), CurrentTime.FormatGmt(L"%d.%m.%Y %H:%M"));
        

In most cases it works perfectly and the output looks like this: enter image description here

However, sometimes the % switches are shown instead of the data and then it looks like: enter image description here

I tried to understand further why it fails, so the function call that fails is:

int ct = GetCurrentTransactionNo();
if (m_logfunc)
    m_logfunc(L"Checking transaction no. %d.", ct);

But if I change it to:

int ct = 15;
if (m_logfunc)
    m_logfunc(L"Checking transaction no. %d.", ct);

I get a correct formattted string. enter image description here

3 Answers

I think as others have implied is that the issue is with undefined behaviour.

You have already ascertained that the issue with with values of 0 that cause the problem. Well, 0 can also be considered as a nullptr (or null pointer).

I am not in a position to verify my explanation but think that your parameter list of 0 might be construed as a null pointer (no arguments) and thus the return value is the format string unchanged.

Maybe you should consider having a bespoke resolution for when the value you are passing is 0 and handle it in a different way. For example (I am not sure if you can use an overloaded function with same name):

void MyDialog::ShowStatus2(LPCWSTR lpText, int iValue)
{
    CString sMsg;
    sMsg.Format(lpText, iValue);
    CWinApp *myApp = AfxGetApp();
    if (myApp)
    {
        CWnd *pWnd = myApp->m_pMainWnd;
        if (pWnd)
        {
            ::SetDlgItemText(pWnd->GetSafeHwnd(), IDC_MYSTATUSBAR, sMsg.GetString());
        }
    }
    myLogFunction(L"%s", sMsg.GetString());
}

I have done that and in the problematic cases I can see that after calling FormatV sMsg contains some "%" characters. In other words FormatV fails, but I don't know why.

When you are in debug mode you might find it useful to look at what the value of ptr is as that contains the parsed arguments. When the passed in value for this argument is 0 to need to see if ptr is null or not.

When the first parameter passed to the function is 0 or "", *ptr (which is in fact *ptr[0] or *(ptr + 0), hence the first block in memory) will hold null, thus the function printed the string literally, without formatting it with FormatV().

You need to replace the following block

if (*ptr == 0)
    sMsg = lpText;
else
    sMsg.FormatV(lpText, ptr);

with just

sMsg.FormatV(lpText, ptr);

You can solve this with parameter packs, introduced in C++11

class Temp
{

public:
  template< typename ... Type >
  static void ShowStatus( LPCWSTR lpText, Type ... args )
  {
    CString sMsg;
    sMsg.Format( lpText, args... );
    MessageBox( NULL, sMsg.GetString(), L"", MB_OK );
  }
};


int main()
{
  int ct = 0;
  wchar_t test[] = L"this is a test";
  Temp::ShowStatus( L"%d", ct );
  Temp::ShowStatus( L"%s", test );
  Temp::ShowStatus( L"%d %s", ct, test );
}
Related