创建用于在 MFC 控件上使用 printf 样式字符串格式显示状态更新的函数

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

我需要在对话框中显示状态更新,并希望能够向其发送 printf 样式的格式化字符串。此外,我希望该函数调用一个类似的函数,将格式化的数据添加到日志文件中。

假设我的 Static 控件被调用 IDC_MYSTATUSBAR,函数如下所示:

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());
}

函数调用如下:

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

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

但是其他模块和库通过一个名为 logfunc 的成员变量调用我的函数,该变量定义为:

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

在这种情况下,来自另一个模块的代码将如下所示:

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"));
        

在大多数情况下,它工作得很好,输出如下所示:

但是,有时会显示 % 开关而不是数据,然后它看起来像:

我试图进一步了解它失败的原因,所以失败的函数调用是:

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

但是如果我把它改成:

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

我得到了正确格式化的字符串。

我认为正如其他人所暗示的那样,问题出在未定义的行为上。

您已经确定问题是由 0 的值引起的。好吧,0也可以被认为是一个nullptr(或者空指针)。

我无法验证我的解释,但认为您的 0 参数列表可能被解释为空指针(无参数),因此 return 值是格式字符串不变。

当您传递的值为 0 时,也许您应该考虑定制解决方案并以不同的方式处理它。例如(我不确定你是否可以使用同名的重载函数):

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.

当您处于调试模式时,您可能会发现查看 ptr 的值很有用,因为它包含已解析的参数。当此参数的传入值为0时需要查看ptr是否为null。

当传递给函数的第一个参数是 0 或 "" 时,*ptr(实际上是 *ptr[0]*(ptr + 0),因此内存中的第一个块)将保持null,因此该函数按字面打印字符串,而不使用 FormatV().

进行格式化

您需要替换以下块

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

只有

sMsg.FormatV(lpText, ptr);

您可以使用 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 );
}