网上有人研究了很多关于1ms定时的,据说Sleep(1)时实际是10多ms,那么我们做个测试
我们给出如下一些Sleep的实现
typedef LONG(__stdcall *__PFN_NtDelayExecution)(BOOLEAN Alertable, PLARGE_INTEGER Interval);
void DSSleep(DWORD dwMs)
{
static __PFN_NtDelayExecution fn = 0;
static bool bInited = false;
if (!bInited)
{
HMODULE hnt = ::GetModuleHandleW(L"ntdll.dll");
fn = (__PFN_NtDelayExecution)(::GetProcAddress(hnt, "NtDelayExecution"));
bInited = true;
}
if (fn)
{
LARGE_INTEGER ll;
ll.QuadPart = -1 * ((LONGLONG)dwMs) * 10000;
fn(TRUE, &ll);
}
else
{
::Sleep(dwMs);
}
}
void USSleep(DWORD dwUs)
{
static __PFN_NtDelayExecution fn = 0;
static bool bInited = false;
if (!bInited)
{
HMODULE hnt = ::GetModuleHandleW(L"ntdll.dll");
fn = (__PFN_NtDelayExecution)(::GetProcAddress(hnt, "NtDelayExecution"));
bInited = true;
}
if (fn)
{
LARGE_INTEGER ll;
ll.QuadPart = -1 * ((LONGLONG)dwUs) * 10;
fn(TRUE, &ll);
}
else
{
::Sleep(dwUs);
}
}
class CEventWaitObj
{
public:
HANDLE hEvent = 0;
CEventWaitObj() { hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); }
~CEventWaitObj() { if (hEvent)CloseHandle(hEvent); }
void Wait(DWORD dwMs) {
if (hEvent)
{
WaitForSingleObject(hEvent, dwMs);
}
else
{
Sleep(dwMs);
}
}
};
CEventWaitObj g_waitObj;
void MsSleep(int nMs)
{
g_waitObj.Wait(nMs);
}
然后写一个测试代码
DWORD64 dwStart = GetMicroSecondTickCount();
Sleep(1);
DWORD dwCost = DiffMicroSecondTickCount(dwStart, GetMicroSecondTickCount());
Debug_View(L"Sleep: cost=%lfms", 0.001*dwCost);
dwStart = GetMicroSecondTickCount();
DSSleep(1);
dwCost = DiffMicroSecondTickCount(dwStart, GetMicroSecondTickCount());
Debug_View(L"DSSleep: cost=%lfms", 0.001*dwCost);
dwStart = GetMicroSecondTickCount();
USSleep(1);
dwCost = DiffMicroSecondTickCount(dwStart, GetMicroSecondTickCount());
Debug_View(L"USSleep: cost=%lfms", 0.001*dwCost);
dwStart = GetMicroSecondTickCount();
MsSleep(1);
dwCost = DiffMicroSecondTickCount(dwStart, GetMicroSecondTickCount());
Debug_View(L"MsSleep: cost=%lfms", 0.001*dwCost);
运行看一下结果
看到Sleep函数并不准
我们看看微软介绍
提到了timeBeginPeriod这个函数,我们使用一下看看
void TestStringConvDlg::OnBnClickedBTimercapd()
{
TIMECAPS caps = {};
MMRESULT mmr = 0;
if (TIMERR_NOERROR == (mmr = timeGetDevCaps(&caps, sizeof(caps))))
{
Debug_View(L"TIMER: max=%d, min=%d", caps.wPeriodMax, caps.wPeriodMin);
mmr = timeBeginPeriod(caps.wPeriodMin);
Debug_View(L"set timer perioid=%d, ret=%d", caps.wPeriodMin, mmr);
}
else
{
Debug_View(L"!timeGetDevCaps mmr=%d", mmr);
}
}
看到了,居然能够实现我们的预期功能了,这个函数在winmm.dll,于是我们找到xp泄露的代码,代码实现上其实是调用的
NTSTATUS NtSetTimerResolution ( IN ULONGRequestedResolution, IN BOOLEANSet, OUT PULONGActualResolution );
这样我们可以不用导入winmm.dll直接使用这个函数
按照微软的介绍,这个函数在XP上能够设置最小1ms的分辨率,我们试试
typedef NTSTATUS(__stdcall* PFN_NtSetTimerResolution)(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution);
void TestStringConvDlg::OnBnClickedBNttimerres()
{
// TODO: 在此添加控件通知处理程序代码
PFN_NtSetTimerResolution fn = (PFN_NtSetTimerResolution)::GetProcAddress(GetModuleHandle(L"NTDLL"), "NtSetTimerResolution");
if (fn)//一般都能设置成1ms 有的能设置成0.5ms
{
ULONG Now = 0;
LONG ns = fn(10000, TRUE, &Now);
if (ns == 0)
{
Debug_View(L"NtSetTimerResolution: Now=%d", Now);
Now = 0;
ns = fn(1, TRUE, &Now);
Debug_View(L"set NtTimerRes 1 =%x, Accual=%d", ns, Now);
}
else
{
Debug_View(L"!NtSetTimerResolution %x", ns);
}
}
else
{
Debug_View(L"no NtSetTimerResolution");
}
}
Win10上测试能够实现0.5ms的精度,USSleep可以实现最小的Sleep间隔!