我正在尝试用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 \微软\ MediaPlayer的\对象\影响
新密钥: WmpTestPlugin
在新密钥下添加一个新字符串 value:名称:classid值: {C476FF24-5E5C-419D-9110-05EC2EED8511}
所以我们构建了一个符合接口的插件,在GAC中注册并告诉媒体播放器它在那里。
如果您打开媒体播放器并右键单击可视化空间,则会出现一个菜单。在那个菜单中将是我们的新插件。当您将鼠标移动到新项目时,WMP将在插件上调用 GetPresetCount (这将记录到该文件)。然后WMP应该调用 GetPresetTitle ,但它永远不会为我做。
如果从菜单栏中打开tools \ options并选择Plugins选项卡,则可以选择新插件。如果您点击属性,WMP将调用 GetCapabilities ,然后 DisplayPropertyPage ,并会显示插件中的消息框。然后WMP崩溃了。在C ++版本中 FinalConstruct()在 CComCoClass 界面上调用 - 我没有这个,也不知道它是什么。我认为它可能比我使用的水平更低?
我已经尝试了许多方法来实现这一点,包括更改方法声明。请有人看看并提供帮助。我在网上寻找解决方案并没有找到任何结果。
感谢阅读, 纳努克
答案 0 :(得分:6)
在查看一些Windows Shell互操作代码后,我发现我应该一直使用[ComImport]而不是[ComVisible]作为接口。为了使签名等于已经注册的签名,我使用了[PreserveSig]。接口继承还需要通过重新声明父级中的基接口来提供一些帮助。
我希望这有助于某人。
这是工人阶级
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace WmpTestPlugin
{
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("695386EC-AA3C-4618-A5E1-DD9A8B987632")]
public interface IWmpEffects2 : IWmpEffects
{
[PreserveSig]
new int Render(ref TimedLevel pLevels, IntPtr Hdc, ref RECT pRC);
[PreserveSig]
new int MediaInfo(int lChannelCount, int lSampleRate, string bstrTitle);
[PreserveSig]
new int GetCapabilities(ref int pdwCapabilities);
[PreserveSig]
new int GetTitle(ref string bstrTitle);
[PreserveSig]
new int GetPresetTitle(int nPreset, ref string bstrPresetTitle);
[PreserveSig]
new int GetPresetCount(ref int count);
[PreserveSig]
new int SetCurrentPreset(int currentpreset);
[PreserveSig]
new int GetCurrentPreset(ref int currentpreset);
[PreserveSig]
new int DisplayPropertyPage(IntPtr hwndOwner);
[PreserveSig]
new int GoFullScreen(bool fFullscreen);
[PreserveSig]
new int RenderFullScreen(ref TimedLevel pLevels);
[PreserveSig]
int SetCore(IntPtr pPlayer);
[PreserveSig]
int Create(IntPtr hwndParent);
[PreserveSig]
int Destroy();
[PreserveSig]
int NotifyNewMedia(IntPtr pMedia);
[PreserveSig]
int OnWindowMessage(int Msg, int WParam, int LParam, ref int plResultParam);
[PreserveSig]
int RenderWindowed(ref TimedLevel pData, bool fRequiredRender);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("D3984C13-C3CB-48e2-8BE5-5168340B4F35")]
public interface IWmpEffects
{
[PreserveSig]
int Render(ref TimedLevel pLevels, IntPtr Hdc, ref RECT pRC);
[PreserveSig]
int MediaInfo(int lChannelCount, int lSampleRate, string bstrTitle);
[PreserveSig]
int GetCapabilities(ref int pdwCapabilities);
[PreserveSig]
int GetTitle(ref string bstrTitle);
[PreserveSig]
int GetPresetTitle(int nPreset, ref string bstrPresetTitle);
[PreserveSig]
int GetPresetCount(ref int count);
[PreserveSig]
int SetCurrentPreset(int currentpreset);
[PreserveSig]
int GetCurrentPreset(ref int currentpreset);
[PreserveSig]
int DisplayPropertyPage(IntPtr hwndOwner);
[PreserveSig]
int GoFullScreen(bool fFullscreen);
[PreserveSig]
int RenderFullScreen(ref TimedLevel pLevels);
}
//[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;
public int Width { get { return this.Right - this.Left; } }
public int Height { get { return this.Bottom - this.Top; } }
}
}