C# Com 与 Windows Media Player 可视化的互操作(带有示例代码)
我正在尝试用 C# 创建 Windows Media Player (WMP) 可视化插件。我对将 C# 暴露给 COM 还很陌生,可能错过了一些基本的东西。我已经坚持了 3 天(大约 20 小时),但没有解决我将在下面描述的一个问题。
对于那些不知道的人来说,WMP 可视化是在听音乐时在媒体播放器中显示的漂亮图像。
简而言之:WMP 将调用我的 C# COM 接口上的某些方法,但不会调用其他方法。
我安装了 WMP 11
我下载了最新的 Windows SDK,其中包含用于编译工作可视化示例的 C++ 插件向导。该示例在 WMP 中注册并运行没有问题。
该开发工具包包含一个名为effects.h 的C++ 头文件,其中包含2 个接口,必须实现这些接口才能使插件与WMP 配合使用。看起来并没有比这更复杂的事情。
正如
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;
};
我所提到的,我的 COM 知识并不是最好的。这就是我将其移植到 C# 所做的事情。
我将接口转换为以下
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;
}
}
实现接口的类的代码如下
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
/// <summary>
/// Set WMP core interface
/// </summary>
/// <param name="pPlayer"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 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).
/// </summary>
/// <param name="hwndParent"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when the visualization should be released.
/// Any resources allocated for rendering should be released.
/// </summary>
/// <returns></returns>
public int Destroy()
{
File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Destroy{1}", DateTime.Now.ToString(), Environment.NewLine));
_parentHwnd = IntPtr.Zero;
return S_OK;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="pMedia"></param>
/// <returns></returns>
public int NotifyNewMedia(IntPtr pMedia)
{
File.AppendAllText("C:\\wmp.txt", string.Format("{0} : NotifyNewMedia{1}", DateTime.Now.ToString(), Environment.NewLine));
return S_OK;
}
/// <summary>
/// Window messages sent to the parent window.
/// </summary>
/// <param name="Msg"></param>
/// <param name="WParam"></param>
/// <param name="LParam"></param>
/// <param name="plResultParam"></param>
/// <returns></returns>
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(&m_RenderContext, msg, WParam, LParam, plResultParam);
//else
// m_WindowedRenderer.OnWindowMessage(&m_RenderContext, msg, WParam, LParam, plResultParam);
return S_FALSE;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="pData"></param>
/// <param name="fRequiredRender"></param>
/// <returns></returns>
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
/// <summary>
/// Called when an effect should render itself to the screen.
/// </summary>
/// <param name="pLevels"></param>
/// <param name="Hdc"></param>
/// <param name="pRC"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 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
/// </summary>
/// <param name="lChannelCount"></param>
/// <param name="lSampleRate"></param>
/// <param name="bstrTitle"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 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
/// </summary>
/// <param name="pdwCapabilities"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to obtain the title of the effect
/// </summary>
/// <param name="bstrTitle"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to obtain the title of the given preset
/// </summary>
/// <param name="nPreset"></param>
/// <param name="bstrPresetTitle"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to obtain the number of supported presets
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to obtain the index of the current preset
/// </summary>
/// <param name="currentpreset"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to obtain the index of the current preset
/// </summary>
/// <param name="currentpreset"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to display the property page for the effect
/// </summary>
/// <param name="hwndOwner"></param>
/// <returns></returns>
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;
}
如您所见,我的代码非常空,实际上只不过是存根。我的调试很简单,但可以完成工作。
一旦使用强名称进行编译,就可以将其注册为:
regasm assemblyname.dll /tlb
,然后将其放入 gac 中。
打开regedit并添加以下内容 信息:
下 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MediaPlayer\Objects\Effects
新密钥:WmpTestPlugin
在新键下添加一个新字符串 值:名称:classid 值: {C476FF24-5E5C-419d-9110-05EC2EED8511}
因此,我们构建了一个符合接口的插件,将其注册到 GAC 中并告诉媒体播放器它在那里。
如果您打开媒体播放器并右键单击可视化空间,将会出现一个菜单。在该菜单中将是我们的新插件。当您将鼠标向下移动到新项目时,WMP 将调用插件上的 GetPresetCount (这将记录到文件中)。然后 WMP 应该调用 GetPresetTitle 但它从来没有为我做。
如果您从菜单栏中打开“工具\选项”并选择“插件”选项卡,则可以选择新插件。如果您单击“属性”,WMP 将调用 GetCapability,然后调用 DisplayPropertyPage,并且会出现来自插件的消息框。然后 WMP 就崩溃了。在 C++ 版本中,FinalConstruct() 是在 CComCoClass 接口上调用的 - 我没有这个接口,也不知道它是什么。我认为它可能比我使用的级别低?
我尝试了很多方法来使其正常工作,包括更改方法声明。请有人看一下并提供帮助。我在网上寻找解决方案,但一无所获。
感谢您的阅读, 纳努克
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.
For those who don't know, WMP visualizations are the pretty images that show in media player while listening to music.
In a nutshell: WMP will call certain methods on my C# COM interface, but not others.
I have WMP 11 installed
I downloaded the Latest Windows SDK which contains a C++ plugin wizard to compile a working visualization sample. This sample registers and works without issues in WMP.
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.
Here they are
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;
};
As I mentioned, my COM knowledge my not be the best. This is what I did to port it to C#.
I converted the interfaces to following
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;
}
}
The code for the class implementing the interfaces is as follows
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
/// <summary>
/// Set WMP core interface
/// </summary>
/// <param name="pPlayer"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 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).
/// </summary>
/// <param name="hwndParent"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when the visualization should be released.
/// Any resources allocated for rendering should be released.
/// </summary>
/// <returns></returns>
public int Destroy()
{
File.AppendAllText("C:\\wmp.txt", string.Format("{0} : Destroy{1}", DateTime.Now.ToString(), Environment.NewLine));
_parentHwnd = IntPtr.Zero;
return S_OK;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="pMedia"></param>
/// <returns></returns>
public int NotifyNewMedia(IntPtr pMedia)
{
File.AppendAllText("C:\\wmp.txt", string.Format("{0} : NotifyNewMedia{1}", DateTime.Now.ToString(), Environment.NewLine));
return S_OK;
}
/// <summary>
/// Window messages sent to the parent window.
/// </summary>
/// <param name="Msg"></param>
/// <param name="WParam"></param>
/// <param name="LParam"></param>
/// <param name="plResultParam"></param>
/// <returns></returns>
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(&m_RenderContext, msg, WParam, LParam, plResultParam);
//else
// m_WindowedRenderer.OnWindowMessage(&m_RenderContext, msg, WParam, LParam, plResultParam);
return S_FALSE;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="pData"></param>
/// <param name="fRequiredRender"></param>
/// <returns></returns>
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
/// <summary>
/// Called when an effect should render itself to the screen.
/// </summary>
/// <param name="pLevels"></param>
/// <param name="Hdc"></param>
/// <param name="pRC"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 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
/// </summary>
/// <param name="lChannelCount"></param>
/// <param name="lSampleRate"></param>
/// <param name="bstrTitle"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 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
/// </summary>
/// <param name="pdwCapabilities"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to obtain the title of the effect
/// </summary>
/// <param name="bstrTitle"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to obtain the title of the given preset
/// </summary>
/// <param name="nPreset"></param>
/// <param name="bstrPresetTitle"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to obtain the number of supported presets
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to obtain the index of the current preset
/// </summary>
/// <param name="currentpreset"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to obtain the index of the current preset
/// </summary>
/// <param name="currentpreset"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Invoked when a host wants to display the property page for the effect
/// </summary>
/// <param name="hwndOwner"></param>
/// <returns></returns>
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;
}
As you can see my code is pretty empty, nothing more than stubs really. My debugging is simple but does the job.
Once this is compiled with a strong name it can be registered with:
regasm assemblyname.dll /tlb
then dropped in the gac.
Open regedit and add the the following
info:Under
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MediaPlayer\Objects\EffectsNew Key: WmpTestPlugin
Under the new key add a new string
value: Name: classid Value:
{C476FF24-5E5C-419d-9110-05EC2EED8511}
So we have built a plugin that conforms to the interfaces, registered it in the GAC and told media player it's there.
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 GetPresetCount on the plugin (this will log to the file). Then WMP is supposed to call GetPresetTitle but it NEVER does for me.
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 GetCapabilities then DisplayPropertyPage and a message box from the plugin will appear. WMP then crashes. In the C++ version FinalConstruct() is called on a CComCoClass interface - I don't have this and don't know what it is. I think it may be lower level than I am using??
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.
Thanks for reading,
Nanook
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
在查看一些 Windows Shell 互操作代码后,我发现我应该对接口使用 [ComImport] 而不是 [ComVisible]。为了使签名等于已经注册的签名,我使用了 [PreserveSig]。接口继承还需要通过在父级中重新声明基接口来获得一些帮助。
我希望这对某人有帮助。
这里是工人阶级
After looking at some Windows Shell interop code I discovered that I should have been using [ComImport] and NOT [ComVisible] for the interfaces. To make the signatures equal to that already registered I used [PreserveSig]. The interface inheritence also needed some help by redeclaring the base interface within the parent.
I hope this helps someone.
Here is the working class