It had been awhile since I've given a talk on COM Interop & PInvoke, but last week I did just that for a developer lab in Redmond.  For this, I dug up my old ".NET Visualization for Windows Media Player" example, which uses C#, COM Interop, and some APIs in System.Drawing (in other words, Windows Forms).  Of course, this compelled me to update it with WPF!  So I used Karsten's ScreenSpaceLines3D sample and simply made the height of the pyramid dependent on the current audio's frequency, making it feel "alive."  Here's the source code (compatible with the September CTP) and here's the result:

      

You can see that the XAML is quite simple (and doesn't need anything special for Windows Media Player integration):

   <UserControl x:Class="AvalonControlLibrary1.UserControl1"
      xmlns="http://schemas.microsoft.com/winfx/avalon/2005" xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"> 
   <UserControl.FixedTemplate>
     <Grid Background="VerticalGradient white gray">
       <Viewport3D Name="myViewport3D">
         <Viewport3D.Camera>
           <PerspectiveCamera FarPlaneDistance="5" LookAtPoint="0,0,0" Up="0,1,0" NearPlaneDistance=".1" Position=".5,.5,1" FieldOfView="45" />
         </
Viewport3D.Camera>
       <
Viewport3D.Models>
         <
Model3DGroup>
           <
Model3DGroup.Children>
             <!--
The axes -->
             <
ScreenSpaceLines3D Color="Red" Thickness="1" Points="20,0,0, -20,0,0" />
             <
ScreenSpaceLines3D Color="Green" Thickness="1" Points="0,20,0, 0,-20,0" />
             <
ScreenSpaceLines3D Color="Blue" Thickness="1" Points="0,0,20, 0,0,-20" />
             <!--
The pyramid -->
             <
ScreenSpaceLines3D x:Name="line1" Color="Black" Thickness="3" Points="0,0,0, .1,0,.1" />
             <
ScreenSpaceLines3D x:Name="line2" Color="Black" Thickness="3" Points="0,0,0, -.1,0,-.1" />
             <
ScreenSpaceLines3D x:Name="line3" Color="Black" Thickness="3" Points="0,0,0, .1,0,-.1" />
             <
ScreenSpaceLines3D x:Name="line4" Color="Black" Thickness="3" Points="0,0,0, -.1,0,.1" />
             <!--
The base -->
             <
ScreenSpaceLines3D Color="Black" Thickness="3" Points=".1,0,.1, -.1,0,.1, -.1, 0, -.1, .1, 0, -.1, .1, 0, .1" />
           </
Model3DGroup.Children>
         </
Model3DGroup>
       </
Viewport3D.Models>
       </
Viewport3D>
     </
Grid>
   </
UserControl.FixedTemplate>
   </
UserControl>

So what needs to be done in the code-behind to make this work?

  • Make your class implement the IWMPEffects COM interface.  I manually defined this interface in C# based on the IDL definition in the Media Player SDK.
  • Mark your class as ComVisible(true), since by default assemblies in WPF projects are marked ComVisible(false).
  • Register your DLL so Windows Media Player will find it.  This involves adding some keys under HKLM\Software\Microsoft\MediaPlayer\Objects\Effects.

Although IWMPEffects has several members, the most important one is Render.  Windows Media Player calls your Render method regularly, giving you data about the audio, a handle to the device context (HDC) on which you need to draw, and a rectangle defining the bounds.  Since WPF supports interoperability with HWNDs, I first needed to get an HWND from the HDC by PInvoking to WindowFromDC.  From there, I could set my UserControl as the RootVisual for the HwndSource and update the UI appropriately.  I think the obvious next step is to use data binding, but for now I've left that as an exercise for the reader. :)

Although self-registering code is a no-no, I gave the class custom registration & unregistration functions so all you need to do is run "REGASM /codebase AvalonControlLibrary1.dll" after building to see the results inside Windows Media Player.  Enjoy!