Thursday, January 12, 2012

Timing fun: timeBeginPeriod/timeEndPeriod

I will start from: The multimedia timer services allow an application to schedule periodic timer events — that is, the application can request and receive timer messages at application-specified intervals.
it is quite interesting to see quite interesting limitation:
You must match each call to timeBeginPeriod with a call to timeEndPeriod, specifying the same minimum resolution in both calls. An application can make multiple timeBeginPeriod calls as long as each call is matched with a call to timeEndPeriod.

from timeBeginPeriod MSDN description and a bit more:
You must match each call to timeBeginPeriod with a call to timeEndPeriod, specifying the same minimum resolution in both calls. An application can make multiple timeBeginPeriod calls as long as each call is matched with a call to timeEndPeriod.

from timeEndPeriod MSDN description

Funny, right? "Must much each call...the same minimum resolution in both call ..."
Let me show details that will help you to understand why and it details:
(disassembled but C-liked, by HexRay - not everything been changed but only a major logic )
MMRESULT __stdcall timeBeginPeriod(UINT uPeriod)
{
  UINT v1; // esi@1
  char *v2; // eax@2
  __int16 v3; // cx@3
  int v4; // eax@8
  MMRESULT v5; // esi@10
  MMRESULT result; // eax@11

  v1 = uPeriod;
  if ( uPeriod < TDD_MAXRESOLUTION )
  {
    result = 97;
  }
  else
  {
    JUMPOUT(uPeriod, dword_41B28FE4, loc_41B0A0A1);
    EnterCriticalSection(&ResolutionCritSec);
    v2 = (char *)&word_41B28FF6[v1 - TDD_MAXRESOLUTION];
    if ( *(_WORD *)v2 == -1 )
    {
      LeaveCriticalSection(&ResolutionCritSec);
      result = 97;
    }
    else
    {
      v3 = *(_WORD *)v2 + 1;
      *(_WORD *)v2 = v3;
      if ( v3 != 1 || v1 >= saved_value_2 )
      {
        v5 = 0;
      }
      else
      {
        if ( WPP_GLOBAL_Control != &WPP_GLOBAL_Control
          && *((_DWORD *)WPP_GLOBAL_Control + 7) & 0x400000
          && *((_BYTE *)WPP_GLOBAL_Control + 25) >= 5u )
          WPP_SF_P(
            *((_DWORD *)WPP_GLOBAL_Control + 4),
            *((_DWORD *)WPP_GLOBAL_Control + 5),
            16,
            (int)dword_41B02720,
            v1);
        v4 = 10000 * v1;
        uPeriod = 10000 * v1;
        if ( 10000 * v1 < MinimumTime )
        {
          v4 = MinimumTime;
          uPeriod = MinimumTime;
        }
        if ( NtSetTimerResolution(&uPeriod, v4, 1, &uPeriod) < 0 )
        {
          if ( WPP_GLOBAL_Control != &WPP_GLOBAL_Control && *((_DWORD *)WPP_GLOBAL_Control + 7) & 0x400000 )
          {
            if ( *((_BYTE *)WPP_GLOBAL_Control + 25) >= 1u )
              WPP_SF_P(
                *((_DWORD *)WPP_GLOBAL_Control + 4),
                *((_DWORD *)WPP_GLOBAL_Control + 5),
                17,
                (int)dword_41B02720,
                v1);
          }
          --word_41B28FF6[v1 - TDD_MAXRESOLUTION];
          v5 = 97;
        }
        else
        {
          saved_value = v1;
          v5 = 0;
          saved_value_2 = (uPeriod + 9900) / 0x2710;
        }
      }
      LeaveCriticalSection(&ResolutionCritSec);
      result = v5;
    }
  }
  return result;
}



//----- (41B09FEB) --------------------------------------------------------
MMRESULT __stdcall timeEndPeriod(UINT uPeriod)
{
  UINT v1; // esi@1
  char *v2; // eax@3
  __int16 v3; // cx@4
  int v4; // ecx@9
  MMRESULT v5; // esi@10
  MMRESULT result; // eax@11
  char v7; // [sp+4h] [bp-4h]@9

  v1 = uPeriod;
  if ( uPeriod < TDD_MAXRESOLUTION )
  {
    result = 97;
  }
  else
  {
    if ( uPeriod >= dword_41B28FE4 )
    {
      result = 0;
    }
    else
    {
      EnterCriticalSection(&ResolutionCritSec);
      v2 = (char *)&unk_41B28FF6 + 2 * (v1 - TDD_MAXRESOLUTION);
      if ( *(_WORD *)v2 )
      {
        v3 = *(_WORD *)v2 - 1;
        *(_WORD *)v2 = v3;
        if ( !v3 && v1 == saved_value )
        {
          while ( v1 < dword_41B28FE4 && !*(_WORD *)v2 )
          {
            ++v1;
            v2 += 2;
          }
          NtSetTimerResolution(dword_41B28FE4, 10000 * saved_value_2, 0, &v7);
          saved_value_2 = dword_41B28FE4;
          saved_value = v1;
          if ( v1 < dword_41B28FE4 )
          {
            if ( WPP_GLOBAL_Control != &WPP_GLOBAL_Control
              && *((_DWORD *)WPP_GLOBAL_Control + 7) & 0x400000
              && *((_BYTE *)WPP_GLOBAL_Control + 25) >= 5u )
              WPP_SF_P(
                *((_DWORD *)WPP_GLOBAL_Control + 4),
                *((_DWORD *)WPP_GLOBAL_Control + 5),
                20,
                (int)dword_41B02720,
                v1);
            if ( NtSetTimerResolution(v4, 10000 * v1, 1, &uPeriod) < 0 )
            {
              if ( WPP_GLOBAL_Control != &WPP_GLOBAL_Control && *((_DWORD *)WPP_GLOBAL_Control + 7) & 0x400000 )
              {
                if ( *((_BYTE *)WPP_GLOBAL_Control + 25) >= 1u )
                  WPP_SF_P(
                    *((_DWORD *)WPP_GLOBAL_Control + 4),
                    *((_DWORD *)WPP_GLOBAL_Control + 5),
                    21,
                    (int)dword_41B02720,
                    v1);
              }
            }
            else
            {
              saved_value_2 = (uPeriod + 9999) / 0x2710;
            }
          }
        }
        v5 = 0;
      }
      else
      {
        v5 = 97;
      }
      LeaveCriticalSection(&ResolutionCritSec);
      result = v5;
    }
  }
  return result;
}

note several things:
- usage of saved_value ( and saved_value_2 )
- note usage of TDD_MAXRESOLUTION and error returns details
- an implicit usage of EnterCriticalSection to be good thread save
(will skip the rest as been less relevant for now)

you already noticed usage of
if ( !v3 && v1 == saved_value )
inside timeEndPeriod, right :)?

That would describe and answer: "Must much each call...the same minimum resolution in both call ..."
IMHO, purely architectural issue...

Now about timeGetDevCaps function to determine the minimum and maximum timer resolutions supported by the timer servicesand TDD_MAXRESOLUTION,
well, its code shows everything faster than I would ever describe:
MMRESULT __stdcall timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc)
{
  MMRESULT result; // eax@3

  if ( cbtc >= 8 && ptc )
  {
    ptc->wPeriodMin = TDD_MAXRESOLUTION;
    ptc->wPeriodMax = 1000000;
    result = 0;
  }
  else
  {
    result = 97;
  }
  return result;

PS: WineHQ does the things differently...

PS2: With HexRay: some time (coffee break) for HexRay processing, 10 sec of looking and understanding full story
Assembler, w/o HexRay: 1 min (mostly scrolling back and forth) - so 6x slower? :)

No comments:

Post a Comment