March, 2007

Larry Osterman's WebLog

Confessions of an Old Fogey
  • Larry Osterman's WebLog

    Where did the second parties go?

    • 37 Comments

    We were chatting at lunch the other day about 3rd parties building solutions on the audio engine.

    One of the people in my group asked "Why do we call them 3rd parties?"

     

    It's one of those "things that make you go hmm".

    There's general consensus in the business world that the people/companies who build a platform are first party developers (it doesn't matter if the platform is Windows, Photoshop, or Quake III).

    There's also general consensus that the people who build solutions ON those platforms (so applications on Windows, Photoshop Plugins on Photoshop, <pick your favorite game> on the Quake III engine) are called 3rd party developers.

    So we've covered 1st and 3rd party developers, what about the 2nd party developers?

    Wikipedia's definition for 3rd party developers is consistent with mine, but they describe 2nd party developers as developers operating under contract to 1st party developers -but they also say it's not a part of standard business practices.

     

    So my question is: "Whatever happened to the second party developers?  Where did they go?"

    Bonus question: From Microsoft's perspective, Adobe is a 3rd party developer, even though they build a platform.  If I'm a developer working on a Photoshop plugin, am I 4th party developer from the eyes of Microsoft?

  • Larry Osterman's WebLog

    How do I change the master volume in Windows Vista

    • 32 Comments

    It's actually easier in Vista than it was in XP.  For Vista, we recognized that one of the key customer scenarios was going to be setting the master volume, and since we'd removed the old mechanism that was used to set the volume, we knew we had to provide an easier mechanism for Vista.

    Just for grins,  I threw together a tiny app that demonstrates it.  To save space, all error checking was removed.

    #include <stdio.h>
    #include <windows.h>
    #include <mmdeviceapi.h>
    #include <endpointvolume.h>

    void Usage()
    {
      printf("Usage: \n");
      printf(" SetVolume [Reports the current volume]\n");
      printf(" SetVolume -d <new volume in decibels> [Sets the current default render device volume to the new volume]\n");
      printf(" SetVolume -f <new volume as an amplitude scalar> [Sets the current default render device volume to the new volume]\n");

    }
    int _tmain(int argc, _TCHAR* argv[])
    {
      HRESULT hr;
      bool decibels = false;
      bool scalar = false;
      double newVolume;
      if (argc != 3 && argc != 1)
      {
        Usage();
        return -1;
      }
      if (argc == 3)
      {
        if (argv[1][0] == '-')
        {
          if (argv[1][1] == 'f')
          {
            scalar = true;
          }
          else if (argv[1][1] == 'd')
          {
            decibels = true;
          }
        }
        else
        {
          Usage();
          return -1;
        }

        newVolume = _tstof(argv[2]);
      }

      // -------------------------
      CoInitialize(NULL);
      IMMDeviceEnumerator *deviceEnumerator = NULL;
      hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator);
      IMMDevice *defaultDevice = NULL;

      hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
      deviceEnumerator->Release();
      deviceEnumerator = NULL;

      IAudioEndpointVolume *endpointVolume = NULL;
      hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
      defaultDevice->Release();
      defaultDevice = NULL; 

      // -------------------------
      float currentVolume = 0;
      endpointVolume->GetMasterVolumeLevel(&currentVolume);
      printf("Current volume in dB is: %f\n", currentVolume);

      hr = endpointVolume->GetMasterVolumeLevelScalar(&currentVolume);
      printf("Current volume as a scalar is: %f\n", currentVolume);
      if (decibels)
      {
        hr = endpointVolume->SetMasterVolumeLevel((float)newVolume, NULL);
      }
      else if (scalar)
      {
        hr = endpointVolume->SetMasterVolumeLevelScalar((float)newVolume, NULL);
      }
      endpointVolume->Release();

      CoUninitialize();
      return 0;
    }

    This program has essentially 3 parts.  The first parses the command line, the second retrieves an endpoint volume interface on the default endpoint, the third retrieves the current volume and sets the volume.

    I'm going to ignore the first part, it's the same junk you'll see in any CS 101 class. 

    The second part instantiates an MMDeviceEnumerator object which implements the IMMDeviceEnumerator interface.  The IMMDeviceEnumerator interface is the gateway object to the new audio subsystem - it can be used to enumerate audio endpoints and retrieve information about the various endpoints.  In this case, I'm only interested in the GetDefaultAudioEndpoint method, it returns an IMMDevice object that points to the current endpoint.

    Again, there are a bunch of things I can do with an IMMDevice object, but I'm only really interested in the "Activate" method.  The idea is that each MMDevice object supports lots of different interfaces, you "Activate" the interface to access the functionality associated with that object.  Again, in this case, I'm only interested in the IAudioEndpointVolume interface - there are other interfaces, like IDeviceTopology, and IAudioClient that can be activated from the endpoint.

    The IAudioEndpointVolume interface is where the good stuff lives, right now I'm only interested in four methods, which retrieve (and set) the current endpoint volume in either decibels or as a scalar value. 

    The decibels version of the IAudioEndointVolume interface instructs the driver to set the desired master volume (input or output) to the decibel value specified, it's intended to be used for applications that want to have exact control over the output dB value of the audio solution.

    The scalar version is a bit more complicated.  It's intended for use in applications that have volume sliders, and provides a linear volume taper (represented as a floating point value between 0.0 and 1.0).  In other words, the perceived volume when you set the scalar version of the API to .5 is twice as loud as when set to .25 and is half as loud as when set to 1.0.

  • Larry Osterman's WebLog

    No sound on a Toshiba M7 after a Vista install (aka: things that make you go "Huh?")

    • 31 Comments

    We recently had a bug reported to us internally.  The user of a Toshiba M7 had installed Vista on his machine (which was previously running XP) and discovered that he didn't get any more sounds from his machine after the upgrade.

    We tried everything we could to figure out his problem - the audio system was sending samples to the sound card, the sound card was updating its internal position register, everything looked great.

    Usually, at this point, we start asking the impolitic questions, like:

    "Sometimes some dirt collects between the plug and the internal connectors on the sound card - could you please unplug the speakers and plug them back in?" (this is the polite way of asking "Did you remember to plug your speakers in?").

    "Sometimes a set of speakers only turn on the speaker when they detect a signal being sent to them, could you try wiggling the volume knob to see if it fixes the problem?" (I actually have one of these in my office, it's excruciatingly annoying).

    "Is it possible there's an external volume control on your speakers?  What's it set to?" (this is the polite question that catches the people who accidentally hit the mute button on their speakers or turned the volume down - we get a surprising number of these).

    Unfortunately, in this case none of these worked.  So we had to dig deeper.  For some reason (I'm not sure why), someone asked the user to boot back to XP and see if he could get sound working on XP.  He booted back to XP and it worked.  He then booted back to Vista, and...

    The sounds worked!

    He mentioned to us that when he'd booted back to XP, the sound driver reported that the volume control was muted, so he un-muted it before booting to Vista.  Just for grins, we asked him to mute the volume control on XP and boot into Vista and yup, the problem had reappeared.  Somehow muting the sound card on XP caused it to be muted in Vista.

    We got on the horn with the manufacturer of the system and the manufacturer of the sound card and they informed us that for various and sundry reasons, the XP audio driver twiddled some hardware registers that were hidden from the OS to cause the sound card to mute.  The Vista driver for the sound card didn't know about those special hardware registers, so it didn't know that the sound card was muted, so Vista didn't know it was muted.

    Needless to say, this is quite annoying - the design of the XP driver for this machine made it really easy for the customer to have a horrible experience when running Vista, which is never good.  It's critical that the OS know what's going on in the hardware (in other words, back doors are bad).  When a customer has this experience, they don't blame their system vendor or their audio driver, they blame Vista.

     

    The good news is that there’s a relatively easy workaround for people with an M7 – make sure that your machine is un-muted before you upgrade, the bad news is that this is a relatively popular computer (at least at Microsoft) and sufficient numbers of people have discovered the problem that it’s made one of our internal FAQs.

  • Larry Osterman's WebLog

    A snapshot of a developers morning...

    • 23 Comments

    Wednesday evening, I went to a local computer store and bought a new 250G SATA drive for my dev machine.  Yesterday morning, I tried to install it.

    I was a little bit apprehensive - my dev machine already has 2 IDE hard disks in it, and although the motherboard has two SATA connectors on it, I was quite concerned about what was going to happen when I finally put a disk into the SATA connector.  I've heard stories that many systems only let you use one or the other.

     

    Fortunately I put the new disk in, cabled it up and powered the machine back up.  And it worked!  Yipee!!!

     

    Now it was time to put things back together.  Because I was concerned about being able to use the disk, I had tested it out with the disk just cabled up and not installed, so it was time for me to find a home for the drive inside the chassis.

    And that's where the problems started.

    My machine only has 3 3 1/2 inch drive bays, and I was already using two of them.  Fortunately there was a floppy drive bay available so I decided to put the new drive in the floppy drive bay (this machine has no floppy drive).

    Unfortunately the drive was a smidge too large to fit into the bay - the drive would physically fit, but the CPU's heat sink got in the way when I tried to line the drive up with the drive bay.

    And now I made my big mistake.  I thought "Hey, no problem - I'll just take the CPU out, put the drive in and put the CPU back".

     

    At this point anyone who's ever built a computer should see what's about to happen.

     

    The first two steps worked great - the CPU came out just fine (although the rather weird clips that held the heat sink took a couple of seconds to figure out) and the drive fit in the bay just fine.

    Then I tried to put the CPU back, and I realized what a horrible mistake I'd made.

    You see, the socket for the CPU was a ZIF socket (in other words it was one of the sockets with a little lever that you pick up before inserting the CPU - that way you can't bend pins while inserting the chip into the socket).  But the heat sink extended out beyond the edge of the CPU by easily 3/4ths of an inch in each direction.  So there was no way I could insert the CPU back into the socket without first removing the CPU from the heat sink.

     

    For those of you that don't know, the connection between the CPU and the heat sink is critical to the performance of the machine - if there isn't a good thermal connection between the two, your CPU will melt.

    Unfortunately I didn't have any thermal paste on hand (for some reason it's not one of the bazillion things in my office and I didn't pick any up at the computer shop because I didn't know I'd need it).

     

    So here I am, my development machine is literally in pieces on the floor of my office, the CPU is literally sitting on my desk (undoubtedly picking up a passive static charge that was going to nuke it the instant I touched it).  And I'm not getting any work done. Oh, and it's only 8:00AM - there's almost nobody around that can help me.

     

    Fortunately we have a wonderful support group here at Microsoft, I opened a ticket with them and "Beckey the wonder-tech" was in my office within 45 minutes with a syringe filled with thermal paste.  10 minutes later my machine was reassembled with brand spanking new thermal paste between the CPU and heat sink and everything was wonderful.

    Vista recognized my new drive, I expanded the existing dynamic drive that holds my source enlistments and I now have 250G more disk space to fill up with stuff.

     

    Hmm, I wonder how long it would take to do a timebuild[1] on my dev machine?

     

     

    [1] A timebuild is Windows-speak for a complete build of windows that results in a bootable OS installation (as opposed to taking individual files and copying them onto an existing installation).

  • Larry Osterman's WebLog

    What's wrong with this code sample...

    • 22 Comments

    Today, Michael Howard posted a link to updated documentation that contains the new list of banned APIs that is in place for Windows Vista.

     

    This is GREAT, and I'm really glad to see it - we've excised all of these functions from our code, others should do it as well.

     

    But then as I was reading through the article, I noticed this code in the example of how to fix a function that uses strcpy:

    HRESULT Function(char *s1, char *s2) {
        char temp[32];
        HRESULT hr = StringCchCopy(temp,sizeof(temp),s1);
        if (FAILED(hr)) return hr;
        return StringCchCat(temp,sizeof(temp),s2);
    }

    Why did I cringe the second I saw this?

  • Larry Osterman's WebLog

    FPO

    • 22 Comments

    I was chatting with one of the perf guys last week and he mentioned something that surprised me greatly.  Apparently he's having perf issues that appear to be associated with a 3rd party driver.  Unfortunately, he's having problems figuring out what's going wrong because the vendor wrote the driver used FPO (and hasn't provided symbols), so the perf guy can't track the root cause of the problem.

    The reason I was surprised was that I didn't realize that ANYONE was using FPO any more.

    What's FPO?

    To know the answer, you have to go way back into prehistory.

    Intel's 8088 processor had an extremely limited set of registers (I'm ignoring the segment registers), they were:

    AX BX CX DX IP
    SI DI BP SP FLAGS

    With such a limited set of registers, the registers were all assigned specific purposes.  AX, BX, CX, and DX were the "General Purpose" registers, SI and DI were "Index" registers, SP was the "Stack Pointer", BP was the "Frame Pointer", IP was the "Instruction Pointer", and FLAGS was a read-only register that contained several bits that were indicated information about the processors' current state (whether the result of the previous arithmetic or logical instruction was 0, for instance).

    The BX, SI, DI and BP registers were special because they could be used as "Index" registers.  Index registers are critically important to a compiler, because they are used to access memory through a pointer.  In other words, if you have a structure that's located at offset 0x1234 in memory, you can set an index register to the value 0x1234 and access values relative to that location.  For example:

    MOV    BX, [Structure]
    MOV    AX, [BX]+4

    Will set the BX register to the value of the memory pointed to by [Structure] and set the value of AX to the WORD located at the 4th byte relative to the start of that structure.

    One thing to note is that the SP register wasn't an index register.  That meant that to access variables on the stack, you needed to use a different register, that's where the BP register came from - the BP register was dedicated to accessing values on the stack.

    When the 386 came out, they stretched the various registers to 32bits, and they fixed the restrictions that only BX, SI, DI and BP could be used as index registers.

    EAX EBX ECX EDX EIP
    ESI EDI EBP ESP FLAGS

    This was a good thing, all of a sudden, instead of being constrained to 3 index registers, the compiler could use 6 of them.

    Since index registers are used for structure access, to a compiler they're like gold - more of them is a good thing, and it's worth almost any amount of effort to gain more of them.

    Some extraordinarily clever person realized that since ESP was now an index register the EBP register no longer had to be dedicated for accessing variables on the stack.  In other words, instead of:

    MyFunction:
        PUSH    EBP
        MOV     EBP, ESP
        SUB      ESP, <LocalVariableStorage>
        MOV     EAX, [EBP+8]
          :
          :
        MOV     ESP, EBP
        POP      EBP
        RETD

    to access the 1st parameter on the stack (EBP+0 is the old value of EBP, EBP+4 is the return address), you can instead do:

    MyFunction:
        SUB      SP, <LocalVariableStorage>
        MOV     EAX, [ESP+4+<LocalVariableStorage>]
          :
          :
        ADD     SP, <LocalVariableStorage>
        RETD

    This works GREAT - all of a sudden, EBP can be repurposed and used as another general purpose register!  The compiler folks called this optimization "Frame Pointer Omission", and it went by the acronym FPO.

    But there's one small problem with FPO.

    If you look at the pre-FPO example for MyFunction, you'd notice that the first instruction in the routine was PUSH EBP followed by a MOV EBP, ESP.  That had an interesting and extremely useful side effect.  It essentially created a singly linked list that linked the frame pointer for each of the callers to a function.  From the EBP for a routine, you could recover the entire call stack for a function.  This was unbelievably useful for debuggers - it meant that call stacks were quite reliable, even if you didn't have symbols for all the modules being debugged.  Unfortunately, when FPO was enabled, that list of stack frames was lost - the information simply wasn't being tracked.

    To solve the is problem, the compiler guys put the information that was lost when FPO was enabled into the PDB file for the binary.  Thus, when you had symbols for the modules, you could recover all the stack information.

    FPO was enabled for all Windows binaries in NT 3.51, but was turned off for Windows binaries in Vista because it was no longer necessary - machines got sufficiently faster since 1995 that the performance improvements that were achieved by FPO weren't sufficient to counter the pain in debugging and analysis that FPO caused.

     

    Edit: Clarified what I meant by "FPO was enabled in NT 3.51" and "was turned off in Vista", thanks Steve for pointing this out.

  • Larry Osterman's WebLog

    How does my existing app change the master volume on Vista?

    • 15 Comments

    After I posted the "How do I set the master volume in Vista", DanT commented:

    Thanks Larry.  I'll have to send a link to this to the people at Griffin so they can fix my PowerMate.  I loved this volume knob on XP.  On Vista it only changes the volume for itself which is somewhat less than useful.

    It turns out that we expected that this would be a common issue for our customers, so we built a mechanism into Vista to allow legacy applications to control the hardware using the old mixer APIs (I hinted about this in my original volume posts, but at the time we'd not finalized our plans, so I couldn't comment any more about it).

    On Vista, the mixer APIs detect if your application is running in XP compatibility mode, and if your application is running in XP mode, it disables the virtualized mixer and instead allows the application to directly interact with the hardware.

     

    Now this won't work for all applications (the XP mixer used to lie about the capabilities of the hardware (for instance it would create a fake volume control for audio hardware that didn't have a hardware volume control (like my laptop)), the Vista mixer won't lie), but it can be used to get existing applications (like the PowerMate application listed above) working on Vista.

  • Larry Osterman's WebLog

    Why does the Media Center volume control change the master volume, not the per-application volume?

    • 14 Comments

    This morning, Steve Robinson asked:

    Also, why does the volume control on Media centre effect the speakers, and not the per-app volume?

    The answer?

    It's all about the 10' experience.

     

    The Media Center team and the WEX Sound team went around and around about this issue during the Vista development cycle, we spent a lot of time thinking about what was the best experience - this wasn't a situation where the MC team and the Sound team disagreed on the experience, in this situation, there is real ambiguity in the experience, and we had to pick one.

    You see, Media Center has two totally different user experiences - the first is the "10 foot (10') experience", which occurs when Media Center is driving a TV (aka full screen mode).  The second is the "2 foot (2') experience", which occurs when Media Center is windowed.  Media Center is designed to work well in both experiences.

    When running in the 10' experience, the primary interface with the computer is a remote control interacting with with Media Center to control volume.  People expect that a remote control works just like the remote control for their TV: when you hit the Mute button on a MC remote control from the couch (10’), most users expect ALL sound from the TV to stop, not just streams from the MC app.  If a remote control only stopped the TV or Music stream coming from the Media Center PC, but Windows system sounds or Outlook new mail sounds, or other sounds kept pouring out of your TV occasionally, most users would be shocked as they would expect their Remote Control to turn off the whole device, not just one application (MC) running on that machine.

    On the other hand, you want the 2' mode to be per-application, since the MC application is sharing the desktop with other applications.

    At first blush, there's an easy and obvious solution - in 10' mode, MC controls the hardware volume, in 2' mode, it controls the per-app volume.  That SOUNDS easy, but it turns out that it's fraught with potential pitfalls - first off, what happens when you switch between 2' and 10' mode?  You want the perceived volume to remain unchanged, but in 2' mode, you've set the per-app volume, now you must set the master volume to match the 2' volume, and this isn't always trivial to do.

    An additional complication is that under some circumstances, MC will launch external applications to render content (for instance, some files are rendered using WMP).  If MC used per-application volume control, it would also have to control the volume of those external applications, and again that could be "complicated".

     

    At the end of the day, the fact that the primary UX for MC is the 10' experience clinched the deal - the fact that most people are expected to use MC in full screen mode coupled with the difficulty of getting per-app volume perfect (and there are more issues than the two I mentioned above) meant that for Vista, Media Center uses a system-wide volume.

  • Larry Osterman's WebLog

    What's wrong with this code sample, the answer

    • 13 Comments

    Yesterday, I posted a question about a security sample I ran into the other day.  I mentioned that the function made me cringe the instant I saw it.

    Mike Dunn and Sys64378 danced around the root of what made the function cringeworthy, but Alan nailed it first.

    The reason I cringed when looking at the code is that it worked by accident, not by design.

    Here's the code again (C&P because it's short):

    HRESULT Function(char *s1, char *s2) {
        char temp[32];
        HRESULT hr = StringCchCopy(temp,sizeof(temp),s1);
        if (FAILED(hr)) return hr;
        return StringCchCat(temp,sizeof(temp),s2);
    }

    The code as written is correct (yes, grue, Luciano and others pointed out that it actually doesn't do anything, but neither did the original code (this is supposed to be a secure version of an existing function).

    However the problem in the code becomes blindingly obvious when you follow Sys64738's suggestion:

    HRESULT Function(TCHAR *s1, TCHAR *s2) {
        TCHAR temp[32];
        HRESULT hr = StringCchCopy(temp,sizeof(temp),s1);
        if (FAILED(hr)) return hr;
        return StringCchCat(temp,sizeof(temp),s2);
    }

    The problem is that the function depends on the fact that temp[] is declared as an array of type "char".  Once you redeclare temp (and s1 and s2) as TCHAR's (which is a likely first step in a conversion to unicode), then it would INTRODUCE a security hole the instant that someone set UNICODE=1.

    Mike Dunn's answer (use StringCchCopyA and StringCchCatA) would absolutely work in this situation (because when you enabled UNICODE, then you would get a compiler error).

    But I think a far better solution would be:

    HRESULT Function(char *s1, char *s2) {
        char temp[32];
        HRESULT hr = StringCbCopy(temp,sizeof(temp),s1);
        if (FAILED(hr)) return hr;
        return StringCbCat(temp,sizeof(temp),s2);
    }

    If you use the Cb version of the safe String functions then you're safe regardless of the type of temp.

     

    Things that weren't wrong (IMHO):

    Checking for null s1 and s2.  First off, this is sample code (and thus the checks would distract from the point being made), and secondly, I'm a firm believer that you don't check for correctness of input values - IMHO it's far better to crash when a caller violates your API contract than to try to resolve the issue.

  • Larry Osterman's WebLog

    The last change for the cheesy OSD application - preventing volume recursion.

    • 13 Comments

    Yesterday, at the close of my article about adding notifications support to the cheesy OSD application, I mentioned that there was one remaining problem.

    The problem is actually a big deal - as it's written right now, the application can't filter out changes made by the application.  You see, your notification routine should only listen to changes that were made by applications other than your own - you already know about your applications changes.

    This problem is compounded by the use of floating point numbers for the volume values - it's possible to get into what is affectionately known as "the floating point rounding spiral of death" if the right values happen to be selected.  If you're not familiar with floating point rounding issues, then you REALLY should go and read the MOST excellent "What Every Computer Scientist Should Know About Floating-Point Arithmetic" by David Goldberg.

    Here's the simple version of how the "floating point rounding spiral of death" works.  Imagine you've got an application with a volume slider.  When a volume change notification is received by the application, it changes the position of the slider to match the new volume.  Simple enough, right? 

    When a volume change notification is received, the volume notification handler looks at the value in the notification, and if it doesn't match the current position of the slider, it sets the slider's position to match the value in the notification.  Setting the slider generates a notification that the slider's position changed (this is the way the slider common control works, as I understand it, it's impossible to distinguish the notification generated by a program setting the slider from the user changing the slider's position).  So the slider's "set the current position" logic looks at the position of the slider and compares it against the current volume.  If they're different, the slider's code updates the system volume, which triggers a notification, which checks to see if the value in the notification doesn't match the position of the slider, etc...

    This works great IF there's no rounding in the system.  But we're dealing with floating point integers here.  One of the characteristics of floating point numbers is that it's essentially impossible to compare two floating point numbers for equality - because of rounding errors, the values can only be compared relative to some amount of precision.

    Let's consider what happens with our application.  It receives a notification that someone has changed the volume to 0.49999999937.  It compares it against the current value of the volume (0.5) and decides that they're not equal.  So it changes the slider to appropriate relative position, which rounds down to 0.4.  The slider change logic compares the current position of the slider (0.4) against the current volume (0.49999999937) and sets the volume to .4.  That triggers a notification that the master volume is 0.39999, the slider change logic compares the current position of the slider (0.4) against the current volume (0.39999), resets the slider to .3 and you watch in horror as the volume quickly spirals to 0.  What's worse, once this happens, you can't fix it - you move the slider up and it spirals down to 0 again.  Watching this effect in action can be quite comical (if rather annoying).

    Nitpickers corner: Yeah, careful coding could avoid this particular example, and yeah, I'm using made-up numbers, these values are unlikely to cause rounding errors, and certainly not as quickly as I'm showing.  It doesn't matter.  Even with careful coding, the fact that you can't distinguish an external notification from user generated input is a huge deal.

    If you could differentiate between notifications generated by your application from notifications generated by someone else's application, this problem would go away - you simply update your UX on external notifications and you throw away the notifications generated by your UX changes.  That cuts the loop off after the first notification is received.

    When people realize that this problem exists, their first suggestion is to say "That's stupid - why does the system tell you about changes made by your process?  The system should be smart enough to figure this out and simply not tell the process about its own changes".  Unfortunately what the users consider an "application" doesn't neatly fit into the windows process model.  But what do you do about applications like the shell or the sidebar?  They host 3rd party code - "Applications" as far as the user is concerned. If the system filtered notifications by process, what would happen you had two different volume control gadgets in the sidebar?  It would be "bad" if the system didn't tell one of them about changes made by the other.

    For the volume logic, we chose to solve the problem slightly differently.  If you remember, the OSD application's call to VolumeStepUp specified a NULL parameter.  That parameter is known as the "Event Context" - every volume related "Set" operation takes one.  The system passes this EventContext value through the system all the way to the notification handler for volume changes.  An application can specify whatever value it wants in the EventContext and when it receives a notification, it can check the EventContext value to see if the application generated the notification.

     

    I'm not going to show the code changes for this - basically you define a GUID and change the VolumeStepUp (and VolumeStepDown) calls to specify this GUID for the event context, and then in the OnNotify method of the CVolumeNotification, if the EventContext member value of the NotificationData parameter matches the GUID, return without updating the UX.

  • Larry Osterman's WebLog

    Other fun things to do with the EndpointVolume interfaces

    • 12 Comments

    Last week I posted a code snippet that showed how to change the master volume in Vista.  That snippet doesn't really show the coolness that exists within the IAudioEndpointVolume interface.

    One of my favorite features is the support for OSD's (On Screen Displays).  To show it off, I write a tiny little OSD program that shows off just two of the APIs in question - VolumeStepUp and VolumeStepDown. 

    If you hit '+' and '-' it'll increase and decrease the volume, I also included a cheesy OSD so you can see the effects of the APIs.

    Before people complain about how much code is here, this is the complete source file, and there's a lot more code to deal with the console than there is to deal with the volume. 

    #include "stdafx.h"
    #include <windows.h>
    #include <mmdeviceapi.h>
    #include <endpointvolume.h>
    #include <strsafe.h>

    int _tmain(int argc, _TCHAR* argv[])
    {
        HRESULT hr;

        CoInitialize(NULL);
        IMMDeviceEnumerator *deviceEnumerator = NULL;;
        HANDLE      handle;

        //
        //    Clear the screen.  Code stolen from: http://support.microsoft.com/kb/319257.
        //
        handle = GetStdHandle(STD_OUTPUT_HANDLE);
        DWORD writtenChars = 0;
        CONSOLE_SCREEN_BUFFER_INFO consoleInfo;            
        COORD home;
        home.X = home.Y = 0;
        GetConsoleScreenBufferInfo(handle, &consoleInfo);
        FillConsoleOutputCharacter(handle, L' ', consoleInfo.dwSize.X * consoleInfo.dwSize.Y, home, &writtenChars);
        SetConsoleCursorPosition(handle, home);

        //
        //    Set the console to raw mode. 
        //
        DWORD oldMode;
        GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldMode);
        SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldMode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT));

        // 
        //    Instantiate an endpoint volume object.
        //
        hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator);
        IMMDevice *defaultDevice = NULL;

        hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
        deviceEnumerator->Release();
        deviceEnumerator = NULL;

        IAudioEndpointVolume *endpointVolume = NULL;
        hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
        defaultDevice->Release();
        defaultDevice = NULL;

        wchar_t inputChar = '\0';
        while (inputChar != '\r')
        {
            UINT currentStep, stepCount;
            DWORD read, written;
            COORD writeCoord;
            wchar_t outputString[256];
            StringCbCopy(outputString, sizeof(outputString), L"Volume: ");

            // 
            //    Calculate the cheesy OSD.
            //
            endpointVolume->GetVolumeStepInfo(&currentStep, &stepCount);
            for (size_t i = 0 ; i < stepCount ; i += 1)
            {
                if (i <= currentStep)
                {
                    StringCbCat(outputString, sizeof(outputString), L"=");
                }
                else
                {
                    StringCbCat(outputString, sizeof(outputString), L"-");
                }
            }
            writeCoord.X = 0;
            writeCoord.Y = 0;
            WriteConsoleOutputCharacter(handle, outputString, (DWORD)wcslen(outputString), writeCoord, &written);

            ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &inputChar, 1, &read, NULL);
            if (inputChar == '+')
            {
                endpointVolume->VolumeStepUp(NULL);
            }
            else if (inputChar == '-')
            {
                endpointVolume->VolumeStepDown(NULL);
            }
        }

        endpointVolume->Release();
        SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldMode);

        CoUninitialize();
        return 0;
    }

    This program's pretty simple right now, the next step is to add a bit of magic to it (metering).

  • Larry Osterman's WebLog

    Why does KB 118626 use AccessCheck to check if you're a member of the administrators group?

    • 8 Comments

     

    Eagle Eyed reader Jens Geyer sent me an email yesterday asking:

    There's a KB article, where there is an example code how to determine if the current process/thread is running from admin account. The sample code changed in the newest revision of that KB article.

    http://support.microsoft.com/kb/118626 

    The old version was testing the group list of the token, whether one of the groups is the predefined admin group. THe new code does something different: They create a SD, with a DACL containing exactly one Access-Allowed-ACE with the Admins Group SID. Then an AccessCheck() is performed on this SD to determine the function result.

    My question now is: What is the reason behind the change? Whats wrong with the old code?

    Unfortunately the KB article does not mention anything about the reasons. I hope, you can help or at least know someone, who can.

    Humorously enough, I wrote the sample code for the original KB article many, many years ago, someone picked up the code and added it to a KB article.

    In pseudo-code, the sample did:

    if (!OpenThreadToken(&htoken))
        OpenProcessToken(&htoken);

    GetTokenInformation(TokenGroups, &groups)

    foreach (groupSid in groups)
    {
        if (groupSid == BuiltinAdministratorsSid)
        {
            return TRUE;
        }
    }
    return FALSE

    In other words, it iterated over the token's groups and looked to see if the group was active in the token.

    This worked great at the time it was written (NT4), but by the time that Windows 2000 came out, the sample ceased to be accurate.  The thing that broke the sample was the addition of the concept of a "Restricted Token".  Essentially a restricted token is a token which has had several of the groups and privileges disabled in the token.  Vista's UAC feature is based on restricted tokens, as were Windows XP's Safer APIs.

    The problem with the previous code is that it didn't take restricted tokens into account.  Fortunately the AccessCheck function does.  So the KB people updated the sample code to reflect this change - first off they pointed to the CheckTokenMembership API, and for those users that can't use that API, they put up a replacement version of the function in the original KB (in pseudo-code):

    if (!OpenThreadToken(&htoken))
        OpenProcessToken(&htoken);

    psidAdministrators = AllocateAndInitializeSid(...);

    pACL = new BYTE[ACLSize];

    InitializeACL(pACL);

    AddAccessAllowedACE(pACL, READ_ACCESS, psidAdministrators);

    SetSecurityDescriptorDACL(psd, pACL);

    return (AccessCheck(psd, htoken, READ_ACCESS));

    And now you know "The Rest of the Story" :)

     

  • Larry Osterman's WebLog

    Fun with the endpoint volume interfaces - closing the loop

    • 6 Comments

    Yesterday, I added support for metering to the cheesy OSD application, today I want to add in the one thing that's been missing: notification of external volume changes.

    The good news is that once again, it's pretty easy to add support.  All you need to do is to define a class to handle the notification:

    class CVolumeNotification : public IAudioEndpointVolumeCallback 
    { 
        LONG m_RefCount; 
        ~CVolumeNotification(void) {}; 
    public: 
        CVolumeNotification(void) : m_RefCount(1) 
        { 
        } 
        STDMETHODIMP_(ULONG)AddRef() { return InterlockedIncrement(&m_RefCount); } 
        STDMETHODIMP_(ULONG)Release()  
        { 
            LONG ref = InterlockedDecrement(&m_RefCount);  
            if (ref == 0) 
                delete this; 
            return ref; 
        } 
        STDMETHODIMP QueryInterface(REFIID IID, void **ReturnValue) 
        { 
            if (IID == IID_IUnknown || IID== __uuidof(IAudioEndpointVolumeCallback))  
            { 
                *ReturnValue = static_cast<IUnknown*>(this); 
                AddRef(); 
                return S_OK; 
            } 
            *ReturnValue = NULL; 
            return E_NOINTERFACE; 
        } 
    
        STDMETHODIMP OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA NotificationData) 
        { 
            wchar_t outputString[256]; 
            DWORD written; 
            COORD writeCoord; 
            StringCbPrintf(outputString, sizeof(outputString), L"Volume Changed: %f", NotificationData->fMasterVolume); 
    
            writeCoord.X = 0; 
            writeCoord.Y = 3; 
            WriteConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), outputString, (DWORD)wcslen(outputString), writeCoord, &written); 
            return S_OK; 
        } 
    }; 

    This function is mostly COM goo to handle references, the guts of the function simply print out the new master volume.  If I was writing a real OSD, I'd have the volume callback object keep a reference to the endpoint volume interface and update the OSD with the current step information, but for a cheesy application like this one, it'll do.  Please note that it writes the notification to line 3 - that's to handle the case where the application runs in a window that is narrower than 100 characters - in that case, the meter wraps to line 2 :).

    Of course that's not all you need to do - you need to instantiate a volume notification object and register it for notifications (I've included some lines from the previous versions for context).

        hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume); 
    
        CVolumeNotification *volumeNotification = new CVolumeNotification(); 
    
        hr = endpointVolume->RegisterControlChangeNotify(volumeNotification); 
    
        hr = defaultDevice->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&context._Meter); 
    

    and finally, to clean things up (again, I've included some lines from yesterday for context):

        context._Meter->Release(); 
        // 
        //    Remove our notification. 
        // 
        endpointVolume->UnregisterControlChangeNotify(volumeNotification); 
    
        endpointVolume->Release(); 
        volumeNotification->Release(); 
        SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldMode); 

    There's still one minor problem with this sample, but it's a big one that will absolutely hit people who try to use this functionality in a real application.  I'll talk about that and show how to fix it next.

    And again, this is a poor sample - there is no attempt at useful things like error recovery and the like.  It's just to show how this stuff works.

  • Larry Osterman's WebLog

    Fun with the endpoint volume interfaces - let's add metering...

    • 3 Comments

    Yesterday I posted a quick&dirty OSD (complete with cheesy text graphics).  Today I'm going to add support for metering to the application.

    Start with yesterdays application and add (this is the routine that actually implements the meter):

    struct TimerContext 
    { 
        IAudioMeterInformation *_Meter; 
    }; 
    
    const int TimerPeriodicityMS = 100; 
    void CALLBACK TimerMeterCallback(PTP_CALLBACK_INSTANCE CallbackInstance, PVOID Context, PTP_TIMER Timer) 
    { 
        TimerContext *timerContext = (TimerContext *)Context; 
        wchar_t outputString[256]; 
        float peakValue; 
        DWORD written; 
        COORD writeCoord; 
        StringCbCopy(outputString, sizeof(outputString), L"Meter: "); 
    
        timerContext->_Meter->GetPeakValue(&peakValue); 
        for (size_t i = 0 ; i < peakValue*100; i += 1) 
        { 
            StringCbCat(outputString, sizeof(outputString), L"*"); 
        } 
        for (size_t i = (size_t)(peakValue*100) ; i < 100; i += 1) 
        { 
            StringCbCat(outputString, sizeof(outputString), L"."); 
        } 
        writeCoord.X = 0; 
        writeCoord.Y = 1; 
        WriteConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), outputString, (DWORD)wcslen(outputString), writeCoord, &written); 
    } 

    at the start of main(), add: 

        HANDLE      handle; 
        TP_CALLBACK_ENVIRON callbackEnvironment; 
        PTP_CLEANUP_GROUP cleanupGroup; 
        PTP_TIMER timer; 
        TimerContext context; 
    
        InitializeThreadpoolEnvironment(&callbackEnvironment); 
        cleanupGroup = CreateThreadpoolCleanupGroup(); 
        SetThreadpoolCallbackCleanupGroup(&callbackEnvironment, cleanupGroup, NULL); 
    
        timer = CreateThreadpoolTimer(TimerMeterCallback, &context, &callbackEnvironment); 
    
    

    and just before the while() loop, add: (please note that the code that releases the default device came from yesterday's program.

        hr = defaultDevice->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&context._Meter); 
        defaultDevice->Release(); 
        defaultDevice = NULL; 
        // Set a 100 millisecond timer. 
        LARGE_INTEGER timerPeriodicityLI; 
        FILETIME timerPeriodicity; 
        timerPeriodicityLI.QuadPart = -1*(TimerPeriodicityMS* 10000 ); 
    
        timerPeriodicity.dwLowDateTime = timerPeriodicityLI.LowPart; 
        timerPeriodicity.dwHighDateTime = timerPeriodicityLI.HighPart; 
                 
        SetThreadpoolTimer(timer, &timerPeriodicity, TimerPeriodicityMS, 10); 
    

    and finally, at the end of main(), add:

        CloseThreadpoolCleanupGroupMembers(cleanupGroup, FALSE, NULL); 
        CloseThreadpoolCleanupGroup(cleanupGroup); 
        cleanupGroup = NULL; 
        DestroyThreadpoolEnvironment(&callbackEnvironment); 
    
        context._Meter->Release(); 
    

    that's it - now, if you compile the application, there will be a 2nd line which displays metering information about the sounds currently being played (it helps if your console window is more than 100 characters wide).

     

    The meat of the code is the line ...Meter->GetPeakValue() - this retrieves the highest sample since the last time the API was called.  There are also multi channel versions of the API if you want to display per-channel bars :).

     

    I'm also using the new Vista threadpool APIs, after using them for a while, I've grown to really like them - they allow significantly more control than the previous APIs did.

     

    Some self criticism: Again, I've removed all error handling for clarity - this is NOT production ready code.  For instance the code that generates the meter is indescribably inefficient,  there is no question it could be considerably improved (not repainting every 10ms would be a good start as would not scanning through the string to append one character).

Page 1 of 1 (14 items)