【问题标题】:C++ beginner how to use GetSystemTimeAsFileTimeC++初学者如何使用GetSystemTimeAsFileTime
【发布时间】:2015-12-04 21:14:22
【问题描述】:

我有一个程序可以从系统时钟中读取当前时间并将其保存到文本文件中。我之前使用了有效的 GetSystemTime 函数,但时间并不完全一致,例如:一次是 32567.789,下一次是时间倒退的 32567.780。

我正在使用这个程序将时间节省多达每秒 10 次。我读到 GetSystemTimeAsFileTime 函数更准确。我的问题是,如何将当前代码转换为使用 GetSystemTimeAsFileTime 函数?我尝试使用 FileTimeToSystemTime 函数,但也有同样的问题。

SYSTEMTIME st;
GetSystemTime(&st);

WORD sec = (st.wHour*3600) + (st.wMinute*60) + st.wSecond; //convert to seconds in a day
lStr.Format( _T("%d   %d.%d\n"),GetFrames() ,sec, st.wMilliseconds);

std::wfstream myfile;  
myfile.open("time.txt", std::ios::out | std::ios::in | std::ios::app );
if (myfile.is_open())
    {
     myfile.write((LPCTSTR)lStr, lStr.GetLength());
     myfile.close();
    }
else {lStr.Format( _T("open file failed: %d"), WSAGetLastError());
}           

编辑要添加更多信息,代码会从每秒运行 10 次的相机捕获图像,并将图像拍摄的时间保存到文本文件中。当我从第二个中减去文本文件的第一个条目时,依此类推,例如:条目 2-1 3-2 4-3 等我得到这个图,其中 x 轴是条目数,y 轴是减去的价值观。

所有这些都应该在大多数都在 0.12 左右。但是,您可以看到其中很多变化,有些甚至是负面的。这不是由于相机的原因,因为相机有自己的内部时钟并且没有变化。它与捕获系统时间有关。我想要的是以最高分辨率和尽可能少的噪声提取系统时间的最准确方法。

编辑 2 我已采纳您的建议并再次运行该程序。结果如下:

如您所见,它比以前好多了,但仍然不对。我觉得很奇怪,它似乎是非常渐进的。我也只是绘制了时间,这就是结果,其中 x 是条目,y 是时间:

有没有人知道是什么导致时间每 30 帧左右消失一次?

【问题讨论】:

  • 您从哪里得知它们更准确?我希望它们是相同的(尽管FILETIME 版本可能会更快/更高效,因为它不必进行日/月/年计算)。
  • 它只是不够精确,无法满足您的需求。在 Win8 及更高版本上使用 GetSystemTimePreciseAsFileTime(),在旧版本上使用 timeBeginPeriod(1)。
  • 是的,我在 Windows 7 上,我将 timeBeginPeriod(1) 和 timeEndPeriod 放在代码块周围,它似乎没有任何区别
  • 我添加了另一个编辑

标签: c++ winapi time


【解决方案1】:

以相当准确的方式从 Windows 中获取系统时间也是我很喜欢的事情...我发现在 Chrome 上运行的 Javascript 代码似乎比使用 C++ 代码产生的计时器结果更一致,所以我去查看 Chrome 源代码。一个有趣的起点是time_win.cc in the Chrome source 顶部的 cmets。那里提供的 Mozilla bug 和 Dobb 博士文章的链接也很有趣。

根据 Mozilla 和 Chrome 的源代码,以及上面的链接,我为自己使用生成的代码是 here。如您所见,代码很多!

基本思想是获取绝对当前时间非常昂贵。 Windows 确实提供了一个访问成本低廉的高分辨率计时器,但它只为您提供相对时间,而不是绝对时间。我的代码所做的是将问题分成两部分:

1) 准确获取系统时间。这是在 CalibrateNow() 中。基本技巧是调用 timeBeginPeriod(1) 获取准确的时间,然后调用 GetSystemTimeAsFileTime() 直到结果发生变化,这意味着 timeBeginPeriod() 调用已经生效。这为我们提供了准确的系统时间,但这是一项相当昂贵的操作(并且 timeBeginPeriod() 调用会影响其他进程),因此我们不想每次都需要时间时都这样做。该代码还调用 QueryPerformanceCounter() 来获取当前的高分辨率计时器值。

bool NeedCalibration = true;
LONGLONG CalibrationFreq = 0;
LONGLONG CalibrationCountBase = 0;
ULONGLONG CalibrationTimeBase = 0;

void CalibrateNow(void)
{
  // If the timer frequency is not known, try to get it
  if (CalibrationFreq == 0)
  {
    LARGE_INTEGER freq;
    if (::QueryPerformanceFrequency(&freq) == 0)
      CalibrationFreq = -1;
    else
      CalibrationFreq = freq.QuadPart;
  }

  if (CalibrationFreq > 0)
  {
    // Get the current system time, accurate to ~1ms
    FILETIME ft1, ft2;
    ::timeBeginPeriod(1);
    ::GetSystemTimeAsFileTime(&ft1);
    do
    {
      // Loop until the value changes, so that the timeBeginPeriod() call has had an effect
      ::GetSystemTimeAsFileTime(&ft2);
    }
    while (FileTimeToValue(ft1) == FileTimeToValue(ft2));
    ::timeEndPeriod(1);

    // Get the current timer value
    LARGE_INTEGER counter;
    ::QueryPerformanceCounter(&counter);

    // Save calibration values
    CalibrationCountBase = counter.QuadPart;
    CalibrationTimeBase = FileTimeToValue(ft2);
    NeedCalibration = false;
  }
}

2) 当我们想要当前时间时,通过调用 QueryPerformanceCounter() 获取高分辨率计时器,并使用自上次 CalibrateNow() 调用以来该计时器中的更改来计算出准确的“现在”。这在我的代码中的 Now() 中。这也会定期调用 CalibrateNow() 以确保系统时间不会倒退或漂移。

FILETIME GetNow(void)
{
  for (int i = 0; i < 4; i++)
  {
    // Calibrate if needed, and give up if this fails
    if (NeedCalibration)
      CalibrateNow();
    if (NeedCalibration)
      break;

    // Get the current timer value and use it to compute now
    FILETIME ft;
    ::GetSystemTimeAsFileTime(&ft);
    LARGE_INTEGER counter;
    ::QueryPerformanceCounter(&counter);
    LONGLONG elapsed = ((counter.QuadPart - CalibrationCountBase) * 10000000) / CalibrationFreq;
    ULONGLONG now = CalibrationTimeBase + elapsed;

    // Don't let time go back
    static ULONGLONG lastNow = 0;
    now = max(now,lastNow);
    lastNow = now;

    // Check for clock skew
    if (LONGABS(FileTimeToValue(ft) - now) > 2 * GetTimeIncrement())
    {
      NeedCalibration = true;
      lastNow = 0;
    }

    if (!NeedCalibration)
      return ValueToFileTime(now);
  }

  // Calibration has failed to stabilize, so just use the system time
  FILETIME ft;
  ::GetSystemTimeAsFileTime(&ft);
  return ft;
}

虽然有点毛茸茸,但效果比我希望的要好。这似乎在我测试过的 Windows(即 Windows XP)上也能很好地工作。

【讨论】:

  • 很棒的答案!因此,如果我在 now() 计算的文件时间上使用FileTimeToSystemTime,我会保持该准确性还是会回到相对时间?
  • 我尝试在我的程序中实现它,但我似乎无法改进时间
  • 嗯,很难知道问题出在哪里。如果您尝试发布一个完整的示例并尝试实现上述内容,则可以尝试一下。
  • 我对此想得越多,在我看来,您就越需要确保您的触发器与您想象的一样有规律(也就是说,无论给您 X 轴值,大概是什么与您的相机有关。我会尝试只记录高分辨率计时器(来自 QueryPerformanceCounter())并绘制图形。虽然计时器只是一个相对数字,而不是绝对时间,但它应该让您了解您的变化是否看到真的来自确定当前时间的问题,或者每次触发代码的任何事情都缺乏规律性。
【解决方案2】:

我相信您正在寻找 GetSystemTimePreciseAsFileTime() 函数甚至 QueryPerformanceCounter() - 是保证产生单调值的东西的缩写。

【讨论】:

  • 谢谢,你有例子吗?我似乎无法用 GetSystemTimeAsFileTime 替换它,我在 Windows 7 上
  • @oodan123,看我的回答。我已经举了一个例子。您可以取消注释代码并改为注释 GetSystemTimeAsFileTime() 函数
  • 我无法使用该功能,因为它仅在 Windows 8 或更高版本上我尝试了 timeBeginPeriod(1) 但也没有做任何事情
【解决方案3】:

首先,你想得到FILETIME如下

FILETIME fileTime;
GetSystemTimeAsFileTime(&fileTime);
// Or for higher precision, use
// GetSystemTimePreciseAsFileTime(&fileTime);

根据FILETIME的documentation,

不建议您从 FILETIME 结构中添加和减去值以获得相对时间。相反,您应该将文件时间的低位和高位部分复制到 ULARGE_INTEGER 结构中,对 QuadPart 成员执行 64 位运算,然后将 LowPart 和 HighPart 成员复制到 FILETIME 结构中。

那么,你接下来应该做的是

ULARGE_INTEGER theTime;
theTime.LowPart = fileTime.dwLowDateTime;
theTime.HighPart = fileTime.dwHighDateTime;

__int64 fileTime64Bit = theTime.QuadPart;

就是这样。 fileTime64Bit 变量现在包含您要查找的时间。

如果您想获取 SYSTEMTIME 对象,则可以执行以下操作:

SYSTEMTIME systemTime;
FileTimeToSystemTime(&fileTime, &systemTime);

【讨论】:

  • 感谢您的回答。它确实获得了系统时间,但是获得时间的时间有所不同。我已对我的问题进行了编辑。
  • 如果您尝试稍微更改代码以使其在开始时打开文件一次并在最后关闭文件怎么办。我很好奇追加操作的打开/关闭文件是否导致开销或其他原因。
  • 谢谢,我会试试并告诉你
  • 是的,不幸的是没有区别
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-13
  • 1970-01-01
  • 2013-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多