title: C# Com Interop with Windows Media Player Visualisation (With Sample Code)
body: <p>I am attempting to create a Windows Media Player (WMP) Visualization plugin in C#. I am quite new to exposing C# to COM and may have missed something basic. I have persisted with this for 3 days (about 20 hours) and not got past the single issue I will describe below.</p>
  
  <p>For those who don't know, WMP visualizations are the pretty images that show in media player while listening to music.</p>
  
  <p><strong>In a nutshell</strong>: WMP will call certain methods on my C# COM interface, but not others.</p>
  
  <p>I have WMP 11 installed</p>
  
  <p>I downloaded the Latest <a href="http://msdn.microsoft.com/en-us/windows/bb980924.aspx" rel="nofollow noreferrer">Windows SDK</a> which contains a C++ plugin wizard to compile a working visualization sample.  This sample registers and works without issues in WMP.</p>
  
  <p>The dev kit contains a C++ header file named effects.h that contains 2 interfaces that must be implemented to get the plugin to work with WMP. It doesn't appear to much more complicated than that.</p>
  
  <p>Here they are</p>
  
  <pre><code>MIDL_INTERFACE("D3984C13-C3CB-48e2-8BE5-5168340B4F35")
  IWMPEffects : public IUnknown
  {
  public:
      virtual /* [helpstring][local] */ HRESULT STDMETHODCALLTYPE Render( 
          /* [in] */ TimedLevel *pLevels,
          /* [in] */ HDC hdc,
          /* [in] */ RECT *prc) = 0;
  
      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE MediaInfo( 
          /* [in] */ LONG lChannelCount,
          /* [in] */ LONG lSampleRate,
          /* [in] */ BSTR bstrTitle) = 0;
  
      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetCapabilities( 
          /* [out] */ DWORD *pdwCapabilities) = 0;
  
      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetTitle( 
          /* [out] */ BSTR *bstrTitle) = 0;
  
      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetPresetTitle( 
          /* [in] */ LONG nPreset,
          /* [out] */ BSTR *bstrPresetTitle) = 0;
  
      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetPresetCount( 
          /* [out] */ LONG *pnPresetCount) = 0;
  
      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE SetCurrentPreset( 
          /* [in] */ LONG nPreset) = 0;
  
      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetCurrentPreset( 
          /* [out] */ LONG *pnPreset) = 0;
  
      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE DisplayPropertyPage( 
          /* [in] */ HWND hwndOwner) = 0;
  
      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GoFullscreen( 
          /* [in] */ BOOL fFullScreen) = 0;
  
      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE RenderFullScreen( 
          /* [in] */ TimedLevel *pLevels) = 0;
  
  };
  
  MIDL_INTERFACE("695386EC-AA3C-4618-A5E1-DD9A8B987632")
  IWMPEffects2 : public IWMPEffects
  {
  public:
      virtual HRESULT STDMETHODCALLTYPE SetCore( 
          /* [in] */ IWMPCore *pPlayer) = 0;
  
      virtual HRESULT STDMETHODCALLTYPE Create( 
          /* [in] */ HWND hwndParent) = 0;
  
      virtual HRESULT STDMETHODCALLTYPE Destroy( void) = 0;
  
      virtual HRESULT STDMETHODCALLTYPE NotifyNewMedia( 
          /* [in] */ IWMPMedia *pMedia) = 0;
  
      virtual HRESULT STDMETHODCALLTYPE OnWindowMessage( 
          /* [in] */ UINT msg,
          /* [in] */ WPARAM WParam,
          /* [in] */ LPARAM LParam,
          /* [in] */ LRESULT *plResultParam) = 0;
  
      virtual HRESULT STDMETHODCALLTYPE RenderWindowed( 
          /* [in] */ TimedLevel *pData,
          /* [in] */ BOOL fRequiredRender) = 0;
  
  };
  </code></pre>
  
  <p>As I mentioned, my COM knowledge my not be the best. This is what I did to port it to C#.</p>
  
  <p>I converted the interfaces to following</p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Runtime.InteropServices;
  
  namespace WmpTestPlugin
  {
  
      [ComVisible(true)]
      [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
      [Guid("D3984C13-C3CB-48e2-8BE5-5168340B4F35")]
      public interface IWmpEffects
      {
          int Render(ref TimedLevel pLevels, IntPtr Hdc, ref RECT pRC);
          int MediaInfo(int lChannelCount, int lSampleRate, string bstrTitle);
          int GetCapabilities(ref int pdwCapabilities);
          int GetTitle(ref string bstrTitle);
          int GetPresetTitle([In] int nPreset, [MarshalAs(UnmanagedType.BStr)] ref string bstrPresetTitle);
          int GetPresetCount(ref int count);
          int SetCurrentPreset(int currentpreset);
          int GetCurrentPreset(ref int currentpreset);
          int DisplayPropertyPage(IntPtr hwndOwner);
          int GoFullScreen(bool fFullscreen);
          int RenderFullScreen(ref TimedLevel pLevels);
      }
  
      [ComVisible(true)]
      [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
      [Guid("695386EC-AA3C-4618-A5E1-DD9A8B987632")]
      public interface IWmpEffects2 : IWmpEffects
      {
          int SetCore(IntPtr pPlayer);
          int Create(IntPtr hwndParent);
          int Destroy();
          int NotifyNewMedia(IntPtr pMedia);
          int OnWindowMessage(int Msg, int WParam, int LParam, ref int plResultParam);
          int RenderWindowed(ref TimedLevel pData, bool fRequiredRender);
      }
  
      [ComVisible(true)]
      [StructLayout(LayoutKind.Sequential)]
      public struct Data
      {
          [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
          public byte[] Data0;
          [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
          public byte[] Data1;
      }
  
      [ComVisible(true)]
      public enum PlayerState
      {
          Stop_State,
          Pause_State,
          Play_State
      }
  
      [ComVisible(true)]
      [StructLayout(LayoutKind.Sequential)]
      public struct TimedLevel
      {
          public Data Frequency;
          public Data Waveform;
          public PlayerState State;
          public long TimeStamp;
      }
  
      [ComVisible(true)]
      [StructLayout(LayoutKind.Sequential)]
      public struct RECT
      {
          public int Left;
          public int Top;
          public int Right;
          public int Bottom;
      }
  }
  </code></pre>
  
  <p>The code for the class implementing the interfaces is as follows</p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Runtime.InteropServices;
  using WmpTestPlugin;
  using System.IO;
  using System.Windows.Forms;
  
  
  
      [ComVisible(true)]
      [Guid("C476FF24-5E5C-419d-9110-05EC2EED8511")]
      //[ProgId("WmpTestPlugin.WmpTest")]
      [ClassInterface(ClassInterfaceType.None)]
      public class TestPlugin : IWmpEffects2
      {
          [DllImport("user32.dll", EntryPoint = "GetClientRect")]
          private static extern bool getClientRect(IntPtr windowHandle, ref IntPtr rectangle);
  
          private const int EFFECT_CANGOFULLSCREEN = 1;
          private const int EFFECT_HASPROPERTYPAGE = 2;
  
          private const int S_OK = 0;
          private const int S_FALSE = 1;
          private const int E_ABORT = unchecked((int)0x80004004);
          private const int E_ACCESSDENIED = unchecked((int)0x80070005);
          private const int E_FAIL = unchecked((int)0x80004005);
          private const int E_HANDLE = unchecked((int)0x80070006);
          private const int E_INVALIDARG = unchecked((int)0x80070057);
          private const int E_NOINTERFACE = unchecked((int)0x80004002);
          private const int E_NOTIMPL = unchecked((int)0x80004001);
          private const int E_OUTOFMEMORY = unchecked((int)0x8007000E);
          private const int E_POINTER = unchecked((int)0x80004003);
          private const int E_UNEXPECTED = unchecked((int)0x8000FFFF);
  
  
          public TestPlugin()
          {
              _parentHwnd = IntPtr.Zero;
              _preset = 0;
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Construct{1}", DateTime.Now.ToString(), Environment.NewLine));
  
          }
  
          ~TestPlugin()
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Deconstruct{1}", DateTime.Now.ToString(), Environment.NewLine));
          }
  
          #region IWmpEffects2 Members
  
          /// &lt;summary&gt;
          /// Set WMP core interface
          /// &lt;/summary&gt;
          /// &lt;param name="pPlayer"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int SetCore(IntPtr pPlayer)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : SetCore{1}", DateTime.Now.ToString(), Environment.NewLine));
  
              // release any existing WMP core interfaces
              //ReleaseCore();
  
              if (pPlayer == IntPtr.Zero)
                  return S_OK;
  
              _playerCore = pPlayer;
  
              //connect up any events
              return S_OK;
          }
  
          /// &lt;summary&gt;
          /// Invoked when the visualization should be initialized.
          ///
          /// If hwndParent != NULL, RenderWindowed() will be called and the visualization
          /// should draw into the window specified by hwndParent. This will be the
          /// behavior when the visualization is hosted in a window.
          ///
          /// If hwndParent == NULL, Render() will be called and the visualization
          /// should draw into the DC passed to Render(). This will be the behavior when
          /// the visualization is hosted windowless (like in a skin for example).
          /// &lt;/summary&gt;
          /// &lt;param name="hwndParent"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int Create(IntPtr hwndParent)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Create{1}", DateTime.Now.ToString(), Environment.NewLine));
  
              _parentHwnd = hwndParent;
              return S_OK;
          }
  
          /// &lt;summary&gt;
          /// Invoked when the visualization should be released.
          /// Any resources allocated for rendering should be released.
          /// &lt;/summary&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int Destroy()
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Destroy{1}", DateTime.Now.ToString(), Environment.NewLine));
              _parentHwnd = IntPtr.Zero;
              return S_OK;
          }
  
          /// &lt;summary&gt;
          /// Invoked when a new media stream begins playing.
          /// The visualization can inspect this object for properties (like name or artist)
          /// that might be interesting for visualization.
          /// &lt;/summary&gt;
          /// &lt;param name="pMedia"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int NotifyNewMedia(IntPtr pMedia)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : NotifyNewMedia{1}", DateTime.Now.ToString(), Environment.NewLine));
              return S_OK;
          }
  
          /// &lt;summary&gt;
          /// Window messages sent to the parent window.
          /// &lt;/summary&gt;
          /// &lt;param name="Msg"&gt;&lt;/param&gt;
          /// &lt;param name="WParam"&gt;&lt;/param&gt;
          /// &lt;param name="LParam"&gt;&lt;/param&gt;
          /// &lt;param name="plResultParam"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int OnWindowMessage(int Msg, int WParam, int LParam, ref int plResultParam)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : OnWindowMessage{1}", DateTime.Now.ToString(), Environment.NewLine));
              // return S_OK only if the plugin has handled the window message
              // return S_FALSE to let the defWindowProc handle the message
  
              //if (_parentHwnd == IntPtr.Zero)
                  //m_NonWindowedRenderer.OnWindowMessage(&amp;m_RenderContext, msg, WParam, LParam, plResultParam);
              //else
              //    m_WindowedRenderer.OnWindowMessage(&amp;m_RenderContext, msg, WParam, LParam, plResultParam);
  
              return S_FALSE;
          }
  
          /// &lt;summary&gt;
          /// Called when an effect should render itself to the screen.
          /// The fRequiredRender flag specifies if an update is required, otherwise the
          /// update is optional. This allows visualizations that are fairly static (for example,
          /// album art visualizations) to only render when the parent window requires it,
          /// instead of n times a second for dynamic visualizations.
          /// &lt;/summary&gt;
          /// &lt;param name="pData"&gt;&lt;/param&gt;
          /// &lt;param name="fRequiredRender"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int RenderWindowed(ref TimedLevel pData, bool fRequiredRender)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : RenderWindowed{1}", DateTime.Now.ToString(), Environment.NewLine));
              //windowed
  
              // NULL parent window should not happen 
              if (_parentHwnd == IntPtr.Zero)
                  return E_UNEXPECTED;
  
              //RECT rect = new RECT();
  
              //TestPlugin.getClientRect(_parentHwnd, ref rect);
  
              //do render//
  
              return S_OK;
          }
  
          #endregion
  
          #region IWmpEffects Members
  
          /// &lt;summary&gt;
          /// Called when an effect should render itself to the screen.
          /// &lt;/summary&gt;
          /// &lt;param name="pLevels"&gt;&lt;/param&gt;
          /// &lt;param name="Hdc"&gt;&lt;/param&gt;
          /// &lt;param name="pRC"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int Render(ref TimedLevel pLevels, IntPtr Hdc, ref RECT pRC)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Render{1}", DateTime.Now.ToString(), Environment.NewLine));
              //not windowed
  
  
              //do render
              return S_OK;
          }
  
          /// &lt;summary&gt;
          /// Everytime new media is loaded, this method is called to pass the
          /// number of channels (mono/stereo), the sample rate of the media, and the
          /// title of the media
          /// &lt;/summary&gt;
          /// &lt;param name="lChannelCount"&gt;&lt;/param&gt;
          /// &lt;param name="lSampleRate"&gt;&lt;/param&gt;
          /// &lt;param name="bstrTitle"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int MediaInfo(int lChannelCount, int lSampleRate, string bstrTitle)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : MediaInfo{1}", DateTime.Now.ToString(), Environment.NewLine));
              return S_OK;
          }
  
          /// &lt;summary&gt;
          /// Returns the capabilities of this effect. Flags that can be returned are:
          /// EFFECT_CANGOFULLSCREEN  -- effect supports full-screen rendering
          /// EFFECT_HASPROPERTYPAGE  -- effect supports a property page
          /// &lt;/summary&gt;
          /// &lt;param name="pdwCapabilities"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int GetCapabilities(ref int pdwCapabilities)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GetCapabilities{1}", DateTime.Now.ToString(), Environment.NewLine));
              //no capabilities
              pdwCapabilities = EFFECT_HASPROPERTYPAGE;
  
              return S_OK;
          }
  
          /// &lt;summary&gt;
          /// Invoked when a host wants to obtain the title of the effect
          /// &lt;/summary&gt;
          /// &lt;param name="bstrTitle"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int GetTitle(ref string bstrTitle)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GetTitle{1}", DateTime.Now.ToString(), Environment.NewLine));
              bstrTitle = "Test Wmp C# Plugin";
              return S_OK;
          }
  
          /// &lt;summary&gt;
          /// Invoked when a host wants to obtain the title of the given preset
          /// &lt;/summary&gt;
          /// &lt;param name="nPreset"&gt;&lt;/param&gt;
          /// &lt;param name="bstrPresetTitle"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int GetPresetTitle(int nPreset, ref string bstrPresetTitle)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GetPresetTitle{1}", DateTime.Now.ToString(), Environment.NewLine));
              //bstrPresetTitle = "Default";
              return S_OK;
          }
  
          /// &lt;summary&gt;
          /// Invoked when a host wants to obtain the number of supported presets
          /// &lt;/summary&gt;
          /// &lt;param name="count"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int GetPresetCount(ref int count)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GetPresetCount{1}", DateTime.Now.ToString(), Environment.NewLine));
              count = 1;
              return S_OK;
          }
  
  
  
          /// &lt;summary&gt;
          /// Invoked when a host wants to obtain the index of the current preset
          /// &lt;/summary&gt;
          /// &lt;param name="currentpreset"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int SetCurrentPreset(int currentpreset)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : SetCurrentPreset{1}", DateTime.Now.ToString(), Environment.NewLine));
              _preset = currentpreset;
              return S_OK;
          }
  
          /// &lt;summary&gt;
          /// Invoked when a host wants to obtain the index of the current preset
          /// &lt;/summary&gt;
          /// &lt;param name="currentpreset"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int GetCurrentPreset(ref int currentpreset)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GetCurrentPreset{1}", DateTime.Now.ToString(), Environment.NewLine));
              currentpreset = _preset;
              return S_OK;
          }
  
          /// &lt;summary&gt;
          /// Invoked when a host wants to display the property page for the effect
          /// &lt;/summary&gt;
          /// &lt;param name="hwndOwner"&gt;&lt;/param&gt;
          /// &lt;returns&gt;&lt;/returns&gt;
          public int DisplayPropertyPage(IntPtr hwndOwner)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : DisplayPropertyPage, Owner={1}{2}", DateTime.Now.ToString(), hwndOwner.ToString(), Environment.NewLine));
              MessageBox.Show("Hello Me!");
              return S_OK;
          }
  
          public int GoFullScreen(bool fFullscreen)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : GoFullScreen{1}", DateTime.Now.ToString(), Environment.NewLine));
              return S_OK;
          }
  
          public int RenderFullScreen(ref TimedLevel pLevels)
          {
              File.AppendAllText("C:\\wmp.txt", string.Format("{0} : RenderFullScreen{1}", DateTime.Now.ToString(), Environment.NewLine));
              return S_OK;
          }
  
          #endregion
  
          private IntPtr _parentHwnd;
          private int _preset;
  
          private IntPtr _playerCore;
  
      }
  </code></pre>
  
  <p>As you can see my code is pretty empty, nothing more than stubs really. My debugging is simple but does the job.</p>
  
  <p>Once this is compiled with a strong name it can be registered with:</p>
  
  <p>regasm assemblyname.dll /tlb</p>
  
  <p>then dropped in the gac.</p>
  
  <blockquote>
    <p>Open regedit and add the the following
    info:</p>
    
    <p>Under
    <strong>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MediaPlayer\Objects\Effects</strong></p>
    
    <p>New Key: <strong>WmpTestPlugin</strong></p>
    
    <p>Under the new key add a new string
    value: Name:  classid Value:
    {C476FF24-5E5C-419d-9110-05EC2EED8511}</p>
  </blockquote>
  
  <p>So we have built a plugin that conforms to the interfaces, registered it in the GAC and told media player it's there.</p>
  
  <p>If you open media player and right-click on the visualization space a menu will appear. In that menu will be our new plugin. When you move your mouse down to the new item WMP will call <strong>GetPresetCount</strong> on the plugin (this will log to the file). Then WMP is supposed to call <strong>GetPresetTitle</strong> but it NEVER does for me.</p>
  
  <p>If you open tools\options from the menu bar and select the Plugins tab you can select the new plugin. If you click Properties WMP will call <strong>GetCapabilities</strong> then <strong>DisplayPropertyPage</strong> and a message box from the plugin will appear. WMP then crashes. In the C++ version <strong>FinalConstruct()</strong> is called on a <strong>CComCoClass</strong> interface - I don't have this and don't know what it is. I think it may be lower level than I am using??</p>
  
  <p>I have tried many many things to get this to work including changing the method declarations.  Please can someone look at it and help.  I've looked all over the web for a solution and found nothing.</p>
  
  <p>Thanks for reading,
    Nanook</p>